miden_node_utils/
genesis.rs1use 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#[derive(Debug)]
14pub struct GenesisBlock {
15 block: SignedBlock,
16 protocol_config: ProtocolConfig,
17}
18
19impl GenesisBlock {
20 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 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#[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
118pub 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
124pub 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;