miden_node_utils/
genesis.rs1use std::fmt;
2use std::path::Path;
3
4use anyhow::Context;
5use miden_protocol::block::{BlockNumber, SignedBlock};
6use miden_protocol::protocol_config::ProtocolConfig;
7use miden_protocol::utils::serde::{
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13 SliceReader,
14};
15
16#[derive(Debug)]
20pub struct GenesisBlock {
21 block: SignedBlock,
22 protocol_config: ProtocolConfig,
23}
24
25impl GenesisBlock {
26 pub fn new(block: SignedBlock, protocol_config: ProtocolConfig) -> anyhow::Result<Self> {
28 anyhow::ensure!(
29 block.header().block_num() == BlockNumber::GENESIS,
30 "expected genesis block number (0), got {}",
31 block.header().block_num(),
32 );
33 anyhow::ensure!(
34 block.signatures().is_empty(),
35 "genesis block must not carry signatures, got {}",
36 block.signatures().len(),
37 );
38 block.validate(None).context("genesis block validation failed")?;
39 let expected = block.header().protocol_config_commitment();
40 let actual = protocol_config.to_commitment();
41 anyhow::ensure!(
42 actual == expected,
43 "genesis protocol configuration commitment mismatch: expected {expected}, got {actual}",
44 );
45 Ok(Self { block, protocol_config })
46 }
47
48 pub fn inner(&self) -> &SignedBlock {
49 &self.block
50 }
51
52 pub fn into_inner(self) -> SignedBlock {
54 self.block
55 }
56
57 pub fn protocol_config(&self) -> &ProtocolConfig {
58 &self.protocol_config
59 }
60
61 pub fn into_parts(self) -> (SignedBlock, ProtocolConfig) {
62 (self.block, self.protocol_config)
63 }
64}
65
66impl Serializable for GenesisBlock {
67 fn write_into<W: ByteWriter>(&self, target: &mut W) {
68 self.block.write_into(target);
69 self.protocol_config.write_into(target);
70 }
71}
72
73impl Deserializable for GenesisBlock {
74 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
75 let block = source.read()?;
76 let protocol_config = source.read()?;
77 Self::new(block, protocol_config)
78 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
79 }
80}
81
82#[derive(clap::ValueEnum, Clone, Copy, Debug, Eq, PartialEq)]
84pub enum OfficialNetwork {
85 Devnet,
86 Testnet,
87}
88
89impl OfficialNetwork {
90 pub const fn as_str(self) -> &'static str {
91 match self {
92 Self::Devnet => "devnet",
93 Self::Testnet => "testnet",
94 }
95 }
96
97 pub fn genesis_block_url(self) -> String {
98 format!("https://genesis.{}.miden.io", self.as_str())
99 }
100}
101
102impl fmt::Display for OfficialNetwork {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.write_str(self.as_str())
105 }
106}
107
108pub fn read_genesis_block(path: &Path) -> anyhow::Result<GenesisBlock> {
110 let bytes = fs_err::read(path).context("failed to read genesis block file")?;
111 deserialize_genesis_block(&bytes)
112}
113
114pub async fn fetch_genesis_block(network: OfficialNetwork) -> anyhow::Result<GenesisBlock> {
116 let url = network.genesis_block_url();
117 let response = reqwest::get(url.as_str())
118 .await
119 .with_context(|| format!("failed to fetch genesis block from {url}"))?
120 .error_for_status()
121 .with_context(|| format!("failed to fetch genesis block from {url}"))?;
122 let bytes = response
123 .bytes()
124 .await
125 .with_context(|| format!("failed to read genesis block response from {url}"))?;
126
127 deserialize_genesis_block(&bytes)
128}
129
130fn deserialize_genesis_block(bytes: &[u8]) -> anyhow::Result<GenesisBlock> {
131 let mut reader = SliceReader::new(bytes);
132 let genesis = GenesisBlock::read_from(&mut reader).context(
133 "failed to deserialize genesis block and protocol configuration; the genesis may have been produced by an incompatible node version; regenerate genesis.dat",
134 )?;
135 anyhow::ensure!(!reader.has_more_bytes(), "unexpected trailing bytes in genesis file");
136 Ok(genesis)
137}
138
139#[cfg(test)]
140mod tests;