@@ -2,11 +2,12 @@ use std::path::{Path, PathBuf};
22
33use anyhow:: Context ;
44use miden_node_store:: Store ;
5- use miden_node_store:: genesis:: config :: { AccountFileWithName , GenesisConfig } ;
5+ use miden_node_store:: genesis:: GenesisBlock ;
66use miden_node_utils:: clap:: GrpcOptionsInternal ;
7+ use miden_node_utils:: fs:: ensure_empty_directory;
78use miden_node_utils:: grpc:: UrlExt ;
8- use miden_node_utils :: signer :: BlockSigner ;
9- use miden_node_validator :: ValidatorSigner ;
9+ use miden_protocol :: block :: ProvenBlock ;
10+ use miden_protocol :: utils :: serde :: Deserializable ;
1011use url:: Url ;
1112
1213use super :: {
@@ -15,35 +16,21 @@ use super::{
1516 ENV_STORE_NTX_BUILDER_URL ,
1617 ENV_STORE_RPC_URL ,
1718} ;
18- use crate :: commands:: {
19- ENV_BLOCK_PROVER_URL ,
20- ENV_ENABLE_OTEL ,
21- ENV_GENESIS_CONFIG_FILE ,
22- ValidatorKey ,
23- } ;
19+ use crate :: commands:: { ENV_BLOCK_PROVER_URL , ENV_ENABLE_OTEL } ;
2420
2521#[ expect( clippy:: large_enum_variant, reason = "single use enum" ) ]
2622#[ derive( clap:: Subcommand ) ]
2723pub enum StoreCommand {
28- /// Bootstraps the blockchain database with the genesis block.
29- ///
30- /// The genesis block contains a single public faucet account. The private key for this
31- /// account is written to the `accounts-directory` which can be used to control the account.
24+ /// Bootstraps the blockchain database with a pre-existing genesis block.
3225 ///
33- /// This key is not required by the node and can be moved .
26+ /// The genesis block file should be produced by `miden- node validator bootstrap` .
3427 Bootstrap {
3528 /// Directory in which to store the database and raw block data.
3629 #[ arg( long, env = ENV_DATA_DIRECTORY , value_name = "DIR" ) ]
3730 data_directory : PathBuf ,
38- /// Directory to write the account data to.
39- #[ arg( long, value_name = "DIR" ) ]
40- accounts_directory : PathBuf ,
41- /// Use the given configuration file to construct the genesis state from.
42- #[ arg( long, env = ENV_GENESIS_CONFIG_FILE , value_name = "GENESIS_CONFIG" ) ]
43- genesis_config_file : Option < PathBuf > ,
44- /// Configuration for the Validator key used to sign genesis block.
45- #[ command( flatten) ]
46- validator_key : ValidatorKey ,
31+ /// Path to the pre-signed genesis block file produced by the validator.
32+ #[ arg( long, value_name = "FILE" ) ]
33+ genesis_block : PathBuf ,
4734 } ,
4835
4936 /// Starts the store component.
@@ -84,22 +71,12 @@ pub enum StoreCommand {
8471}
8572
8673impl StoreCommand {
87- /// Executes the subcommand as described by each variants documentation.
74+ /// Executes the subcommand as described by each variant's documentation.
8875 pub async fn handle ( self ) -> anyhow:: Result < ( ) > {
8976 match self {
90- StoreCommand :: Bootstrap {
91- data_directory,
92- accounts_directory,
93- genesis_config_file,
94- validator_key,
95- } => {
96- Self :: bootstrap (
97- & data_directory,
98- & accounts_directory,
99- genesis_config_file. as_ref ( ) ,
100- validator_key,
101- )
102- . await
77+ StoreCommand :: Bootstrap { data_directory, genesis_block } => {
78+ ensure_empty_directory ( & data_directory) ?;
79+ bootstrap_store ( & data_directory, & genesis_block)
10380 } ,
10481 StoreCommand :: Start {
10582 rpc_url,
@@ -172,93 +149,16 @@ impl StoreCommand {
172149 . await
173150 . context ( "failed while serving store component" )
174151 }
152+ }
175153
176- async fn bootstrap (
177- data_directory : & Path ,
178- accounts_directory : & Path ,
179- genesis_config : Option < & PathBuf > ,
180- validator_key : ValidatorKey ,
181- ) -> anyhow:: Result < ( ) > {
182- // Parse genesis config (or default if not given).
183- let config = genesis_config
184- . map ( |file_path| {
185- GenesisConfig :: read_toml_file ( file_path) . with_context ( || {
186- format ! ( "failed to parse genesis config from file {}" , file_path. display( ) )
187- } )
188- } )
189- . transpose ( ) ?
190- . unwrap_or_default ( ) ;
191-
192- // Create directories if they do not already exist.
193- for directory in & [ accounts_directory, data_directory] {
194- if fs_err:: exists ( directory) ? {
195- let is_empty = fs_err:: read_dir ( directory) ?. next ( ) . is_none ( ) ;
196- // If the directory exists and is empty, we store the files there
197- if !is_empty {
198- anyhow:: bail!( format!( "{} exists but it is not empty." , directory. display( ) ) ) ;
199- }
200- } else {
201- fs_err:: create_dir ( directory) . with_context ( || {
202- format ! (
203- "failed to create {} at {}" ,
204- directory
205- . file_name( )
206- . unwrap_or( std:: ffi:: OsStr :: new( "directory" ) )
207- . display( ) ,
208- directory. display( )
209- )
210- } ) ?;
211- }
212- }
213-
214- // Bootstrap with KMS key or local key.
215- let signer = validator_key. into_signer ( ) . await ?;
216- match signer {
217- ValidatorSigner :: Kms ( signer) => {
218- Self :: bootstrap_accounts_and_store (
219- config,
220- signer,
221- accounts_directory,
222- data_directory,
223- )
224- . await
225- } ,
226- ValidatorSigner :: Local ( signer) => {
227- Self :: bootstrap_accounts_and_store (
228- config,
229- signer,
230- accounts_directory,
231- data_directory,
232- )
233- . await
234- } ,
235- }
236- }
237-
238- /// Builds the genesis state of the chain, writes accounts to file, and bootstraps the store.
239- async fn bootstrap_accounts_and_store (
240- config : GenesisConfig ,
241- signer : impl BlockSigner ,
242- accounts_directory : & Path ,
243- data_directory : & Path ,
244- ) -> anyhow:: Result < ( ) > {
245- // Build genesis state with the provided signer.
246- let ( genesis_state, secrets) = config. into_state ( signer) ?;
247-
248- // Write accounts to file.
249- for item in secrets. as_account_files ( & genesis_state) {
250- let AccountFileWithName { account_file, name } = item?;
251- let accountpath = accounts_directory. join ( name) ;
252- // do not override existing keys
253- fs_err:: OpenOptions :: new ( )
254- . create_new ( true )
255- . write ( true )
256- . open ( & accountpath)
257- . context ( "key file already exists" ) ?;
258- account_file. write ( accountpath) ?;
259- }
154+ /// Reads a genesis block from disk, validates it, and bootstraps the store.
155+ pub fn bootstrap_store ( data_directory : & Path , genesis_block_path : & Path ) -> anyhow:: Result < ( ) > {
156+ // Read and deserialize the genesis block file.
157+ let bytes = fs_err:: read ( genesis_block_path) . context ( "failed to read genesis block" ) ?;
158+ let proven_block = ProvenBlock :: read_from_bytes ( & bytes)
159+ . context ( "failed to deserialize genesis block from file" ) ?;
160+ let genesis_block =
161+ GenesisBlock :: try_from ( proven_block) . context ( "genesis block validation failed" ) ?;
260162
261- // Bootstrap store.
262- Store :: bootstrap ( genesis_state, data_directory) . await
263- }
163+ Store :: bootstrap ( & genesis_block, data_directory)
264164}
0 commit comments