Skip to main content

miden_node_utils/
genesis.rs

1use 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/// A validated genesis block and its protocol configuration.
17///
18/// The block is the chain's trust root. Obtain it from a trusted source.
19#[derive(Debug)]
20pub struct GenesisBlock {
21    block: SignedBlock,
22    protocol_config: ProtocolConfig,
23}
24
25impl GenesisBlock {
26    /// Validates the genesis block and its protocol configuration.
27    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    /// Returns the block and discards the protocol configuration.
53    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/// Official Miden networks with a hosted genesis block.
83#[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
108/// Reads a trusted genesis block and its protocol configuration from disk.
109pub 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
114/// Downloads a trusted genesis block and its protocol configuration for an official Miden network.
115pub 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;