reth_cli_commands/init_state/
mod.rs

1//! Command that initializes the node from a genesis file.
2
3use crate::common::{AccessRights, CliHeader, CliNodeTypes, Environment, EnvironmentArgs};
4use alloy_consensus::BlockHeader as AlloyBlockHeader;
5use alloy_primitives::{B256, U256};
6use clap::Parser;
7use reth_chainspec::{EthChainSpec, EthereumHardforks};
8use reth_cli::chainspec::ChainSpecParser;
9use reth_db_common::init::init_from_state_dump;
10use reth_node_api::NodePrimitives;
11use reth_primitives_traits::{BlockHeader, SealedHeader};
12use reth_provider::{
13    BlockNumReader, DatabaseProviderFactory, StaticFileProviderFactory, StaticFileWriter,
14};
15use std::{io::BufReader, path::PathBuf, str::FromStr, sync::Arc};
16use tracing::info;
17
18pub mod without_evm;
19
20/// Initializes the database with the genesis block.
21#[derive(Debug, Parser)]
22pub struct InitStateCommand<C: ChainSpecParser> {
23    #[command(flatten)]
24    pub env: EnvironmentArgs<C>,
25
26    /// JSONL file with state dump.
27    ///
28    /// Must contain accounts in following format, additional account fields are ignored. Must
29    /// also contain { "root": \<state-root\> } as first line.
30    /// {
31    ///     "balance": "\<balance\>",
32    ///     "nonce": \<nonce\>,
33    ///     "code": "\<bytecode\>",
34    ///     "storage": {
35    ///         "\<key\>": "\<value\>",
36    ///         ..
37    ///     },
38    ///     "address": "\<address\>",
39    /// }
40    ///
41    /// Allows init at a non-genesis block. Caution! Blocks must be manually imported up until
42    /// and including the non-genesis block to init chain at. See 'import' command.
43    #[arg(value_name = "STATE_DUMP_FILE", verbatim_doc_comment)]
44    pub state: PathBuf,
45
46    /// Specifies whether to initialize the state without relying on EVM historical data.
47    ///
48    /// When enabled, and before inserting the state, it creates a dummy chain up to the last EVM
49    /// block specified. It then, appends the first block provided block.
50    ///
51    /// - **Note**: **Do not** import receipts and blocks beforehand, or this will fail or be
52    ///   ignored.
53    #[arg(long, default_value = "false")]
54    pub without_evm: bool,
55
56    /// Header file containing the header in an RLP encoded format.
57    #[arg(long, value_name = "HEADER_FILE", verbatim_doc_comment)]
58    pub header: Option<PathBuf>,
59
60    /// Total difficulty of the header.
61    #[arg(long, value_name = "TOTAL_DIFFICULTY", verbatim_doc_comment)]
62    pub total_difficulty: Option<String>,
63
64    /// Hash of the header.
65    #[arg(long, value_name = "HEADER_HASH", verbatim_doc_comment)]
66    pub header_hash: Option<String>,
67}
68
69impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitStateCommand<C> {
70    /// Execute the `init` command
71    pub async fn execute<N>(self) -> eyre::Result<()>
72    where
73        N: CliNodeTypes<
74            ChainSpec = C::ChainSpec,
75            Primitives: NodePrimitives<BlockHeader: BlockHeader + CliHeader>,
76        >,
77    {
78        info!(target: "reth::cli", "Reth init-state starting");
79
80        let Environment { config, provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
81
82        let static_file_provider = provider_factory.static_file_provider();
83        let provider_rw = provider_factory.database_provider_rw()?;
84
85        if self.without_evm {
86            // ensure header, total difficulty and header hash are provided
87            let header = self.header.ok_or_else(|| eyre::eyre!("Header file must be provided"))?;
88            let header = without_evm::read_header_from_file::<
89                <N::Primitives as NodePrimitives>::BlockHeader,
90            >(header)?;
91
92            let header_hash =
93                self.header_hash.ok_or_else(|| eyre::eyre!("Header hash must be provided"))?;
94            let header_hash = B256::from_str(&header_hash)?;
95
96            let total_difficulty = self
97                .total_difficulty
98                .ok_or_else(|| eyre::eyre!("Total difficulty must be provided"))?;
99            let total_difficulty = U256::from_str(&total_difficulty)?;
100
101            let last_block_number = provider_rw.last_block_number()?;
102
103            if last_block_number == 0 {
104                without_evm::setup_without_evm(
105                    &provider_rw,
106                    SealedHeader::new(header, header_hash),
107                    total_difficulty,
108                    |number| {
109                        let mut header =
110                            <<N::Primitives as NodePrimitives>::BlockHeader>::default();
111                        header.set_number(number);
112                        header
113                    },
114                )?;
115
116                // SAFETY: it's safe to commit static files, since in the event of a crash, they
117                // will be unwound according to database checkpoints.
118                //
119                // Necessary to commit, so the header is accessible to provider_rw and
120                // init_state_dump
121                static_file_provider.commit()?;
122            } else if last_block_number > 0 && last_block_number < header.number() {
123                return Err(eyre::eyre!(
124                    "Data directory should be empty when calling init-state with --without-evm-history."
125                ));
126            }
127        }
128
129        info!(target: "reth::cli", "Initiating state dump");
130
131        let reader = BufReader::new(reth_fs_util::open(self.state)?);
132
133        let hash = init_from_state_dump(reader, &provider_rw, config.stages.etl)?;
134
135        provider_rw.commit()?;
136
137        info!(target: "reth::cli", hash = ?hash, "Genesis block written");
138        Ok(())
139    }
140}
141
142impl<C: ChainSpecParser> InitStateCommand<C> {
143    /// Returns the underlying chain being used to run this command
144    pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
145        Some(&self.env.chain)
146    }
147}