Skip to main content

miden_node_utils/
genesis.rs

1use std::fmt;
2use std::path::Path;
3
4use anyhow::Context;
5use miden_node_persistence::miden_protobuf::ConversionError;
6use miden_node_persistence::{PersistenceError, ProtobufValue};
7use miden_protocol::block::{BlockNumber, SignedBlock};
8use miden_protocol::protocol_config::ProtocolConfig;
9
10/// A validated genesis block and its protocol configuration.
11///
12/// The block is the chain's trust root. Obtain it from a trusted source.
13#[derive(Debug)]
14pub struct GenesisBlock {
15    block: SignedBlock,
16    protocol_config: ProtocolConfig,
17}
18
19impl GenesisBlock {
20    /// Validates the genesis block and its protocol configuration.
21    pub fn new(block: SignedBlock, protocol_config: ProtocolConfig) -> anyhow::Result<Self> {
22        anyhow::ensure!(
23            block.header().block_num() == BlockNumber::GENESIS,
24            "expected genesis block number (0), got {}",
25            block.header().block_num(),
26        );
27        anyhow::ensure!(
28            block.signatures().is_empty(),
29            "genesis block must not carry signatures, got {}",
30            block.signatures().len(),
31        );
32        block.validate(None).context("genesis block validation failed")?;
33        let expected = block.header().protocol_config_commitment();
34        let actual = protocol_config.to_commitment();
35        anyhow::ensure!(
36            actual == expected,
37            "genesis protocol configuration commitment mismatch: expected {expected}, got {actual}",
38        );
39        Ok(Self { block, protocol_config })
40    }
41
42    pub fn inner(&self) -> &SignedBlock {
43        &self.block
44    }
45
46    /// Returns the block and discards the protocol configuration.
47    pub fn into_inner(self) -> SignedBlock {
48        self.block
49    }
50
51    pub fn protocol_config(&self) -> &ProtocolConfig {
52        &self.protocol_config
53    }
54
55    pub fn into_parts(self) -> (SignedBlock, ProtocolConfig) {
56        (self.block, self.protocol_config)
57    }
58}
59
60#[derive(Debug, thiserror::Error)]
61#[error(transparent)]
62struct InvalidGenesis(anyhow::Error);
63
64impl ProtobufValue for GenesisBlock {
65    type Message = miden_node_persistence::generated::GenesisFile;
66
67    fn to_proto(&self) -> Self::Message {
68        Self::Message {
69            version: 1,
70            block: Some(self.block.to_proto()),
71            protocol_config: Some(self.protocol_config.to_proto()),
72        }
73    }
74
75    fn from_proto(message: Self::Message) -> Result<Self, PersistenceError> {
76        if message.version != 1 {
77            return Err(PersistenceError::UnsupportedVersion {
78                format: "genesis file",
79                version: message.version,
80            });
81        }
82        let block =
83            message.block.ok_or_else(|| ConversionError::message("missing genesis block"))?;
84        let config = message
85            .protocol_config
86            .ok_or_else(|| ConversionError::message("missing genesis protocol configuration"))?;
87        Self::new(SignedBlock::from_proto(block)?, ProtocolConfig::from_proto(config)?)
88            .map_err(|error| ConversionError::new(InvalidGenesis(error)).into())
89    }
90}
91
92/// Official Miden networks with a hosted genesis block.
93#[derive(clap::ValueEnum, Clone, Copy, Debug, Eq, PartialEq)]
94pub enum OfficialNetwork {
95    Devnet,
96    Testnet,
97}
98
99impl OfficialNetwork {
100    pub const fn as_str(self) -> &'static str {
101        match self {
102            Self::Devnet => "devnet",
103            Self::Testnet => "testnet",
104        }
105    }
106
107    pub fn genesis_block_url(self) -> String {
108        format!("https://genesis.{}.miden.io", self.as_str())
109    }
110}
111
112impl fmt::Display for OfficialNetwork {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        f.write_str(self.as_str())
115    }
116}
117
118/// Reads a trusted genesis block and its protocol configuration from disk.
119pub fn read_genesis_block(path: &Path) -> anyhow::Result<GenesisBlock> {
120    let bytes = fs_err::read(path).context("failed to read genesis block file")?;
121    deserialize_genesis_block(&bytes)
122}
123
124/// Downloads a trusted genesis block and its protocol configuration for an official Miden network.
125pub async fn fetch_genesis_block(network: OfficialNetwork) -> anyhow::Result<GenesisBlock> {
126    let url = network.genesis_block_url();
127    let response = reqwest::get(url.as_str())
128        .await
129        .with_context(|| format!("failed to fetch genesis block from {url}"))?
130        .error_for_status()
131        .with_context(|| format!("failed to fetch genesis block from {url}"))?;
132    let bytes = response
133        .bytes()
134        .await
135        .with_context(|| format!("failed to read genesis block response from {url}"))?;
136
137    deserialize_genesis_block(&bytes)
138}
139
140fn deserialize_genesis_block(bytes: &[u8]) -> anyhow::Result<GenesisBlock> {
141    miden_node_persistence::decode(bytes).context(
142        "failed to deserialize genesis block and protocol configuration; regenerate genesis.dat with the current node",
143    )
144}
145
146#[cfg(test)]
147mod tests;