Skip to main content

reth_db_common/
init.rs

1//! Reth genesis initialization utility functions.
2
3use alloy_consensus::BlockHeader;
4use alloy_genesis::GenesisAccount;
5use alloy_primitives::{
6    keccak256,
7    map::{AddressMap, B256Map, HashMap},
8    Address, B256, U256,
9};
10use reth_chainspec::EthChainSpec;
11use reth_codecs::Compact;
12use reth_config::config::EtlConfig;
13use reth_db_api::{
14    models::{storage_sharded_key::StorageShardedKey, ShardedKey},
15    tables,
16    transaction::DbTxMut,
17    BlockNumberList, DatabaseError,
18};
19use reth_etl::Collector;
20use reth_execution_errors::StateRootError;
21use reth_primitives_traits::{
22    Account, Bytecode, GotExpected, NodePrimitives, SealedHeader, StorageEntry,
23};
24use reth_provider::{
25    errors::provider::ProviderResult, providers::StaticFileWriter, BlockHashReader, BlockNumReader,
26    BundleStateInit, ChainSpecProvider, DBProvider, DatabaseProviderFactory, EitherWriter,
27    ExecutionOutcome, HashingWriter, HeaderProvider, HistoryWriter, MetadataProvider,
28    MetadataWriter, NodePrimitivesProvider, OriginalValuesKnown, ProviderError, RevertsInit,
29    RocksDBProviderFactory, StageCheckpointReader, StageCheckpointWriter, StateWriteConfig,
30    StateWriter, StaticFileProviderFactory, StorageSettings, StorageSettingsCache, TrieWriter,
31};
32use reth_stages_types::{StageCheckpoint, StageId};
33use reth_static_file_types::StaticFileSegment;
34use reth_trie::{
35    prefix_set::{TriePrefixSets, TriePrefixSetsMut},
36    IntermediateStateRootState, Nibbles, StateRoot as StateRootComputer, StateRootProgress,
37};
38use reth_trie_db::DatabaseStateRoot;
39
40type DbStateRoot<'a, TX, A> = StateRootComputer<
41    reth_trie_db::DatabaseTrieCursorFactory<&'a TX, A>,
42    reth_trie_db::DatabaseHashedCursorFactory<&'a TX>,
43>;
44
45use serde::{Deserialize, Serialize};
46use std::io::BufRead;
47use tracing::{debug, error, info, trace, warn};
48
49/// Default soft limit for number of bytes to read from state dump file, before inserting into
50/// database.
51///
52/// Default is 1 GB.
53pub const DEFAULT_SOFT_LIMIT_BYTE_LEN_ACCOUNTS_CHUNK: usize = 1_000_000_000;
54
55/// Approximate number of accounts per 1 GB of state dump file. One account is approximately 3.5 KB
56///
57/// Approximate is 285 228 accounts.
58//
59// (14.05 GB OP mainnet state dump at Bedrock block / 4 007 565 accounts in file > 3.5 KB per
60// account)
61pub const AVERAGE_COUNT_ACCOUNTS_PER_GB_STATE_DUMP: usize = 285_228;
62
63/// Soft limit for the number of flushed updates after which to log progress summary.
64const SOFT_LIMIT_COUNT_FLUSHED_UPDATES: usize = 1_000_000;
65
66/// Storage initialization error type.
67#[derive(Debug, thiserror::Error, Clone)]
68pub enum InitStorageError {
69    /// Genesis header found on static files but the database is empty.
70    #[error(
71        "static files found, but the database is uninitialized. If attempting to re-syncing, delete both."
72    )]
73    UninitializedDatabase,
74    /// An existing genesis block was found in the database, and its hash did not match the hash of
75    /// the chainspec.
76    #[error(
77        "genesis hash in the storage does not match the specified chainspec: chainspec is {chainspec_hash}, database is {storage_hash}"
78    )]
79    GenesisHashMismatch {
80        /// Expected genesis hash.
81        chainspec_hash: B256,
82        /// Actual genesis hash.
83        storage_hash: B256,
84    },
85    /// Provider error.
86    #[error(transparent)]
87    Provider(#[from] ProviderError),
88    /// State root error while computing the state root
89    #[error(transparent)]
90    StateRootError(#[from] StateRootError),
91    /// State root doesn't match the expected one.
92    #[error("state root mismatch: {_0}")]
93    StateRootMismatch(GotExpected<B256>),
94}
95
96impl From<DatabaseError> for InitStorageError {
97    fn from(error: DatabaseError) -> Self {
98        Self::Provider(ProviderError::Database(error))
99    }
100}
101
102/// Write the genesis block if it has not already been written
103pub fn init_genesis<PF>(factory: &PF) -> Result<B256, InitStorageError>
104where
105    PF: DatabaseProviderFactory
106        + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
107        + ChainSpecProvider
108        + StageCheckpointReader
109        + BlockNumReader
110        + MetadataProvider
111        + StorageSettingsCache,
112    PF::ProviderRW: StaticFileProviderFactory<Primitives = PF::Primitives>
113        + StageCheckpointWriter
114        + HistoryWriter
115        + HeaderProvider
116        + HashingWriter
117        + StateWriter
118        + TrieWriter
119        + MetadataWriter
120        + ChainSpecProvider
121        + StorageSettingsCache
122        + RocksDBProviderFactory
123        + NodePrimitivesProvider
124        + AsRef<PF::ProviderRW>,
125    PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
126{
127    init_genesis_with_settings(factory, StorageSettings::base())
128}
129
130/// Write the genesis block if it has not already been written with [`StorageSettings`].
131pub fn init_genesis_with_settings<PF>(
132    factory: &PF,
133    genesis_storage_settings: StorageSettings,
134) -> Result<B256, InitStorageError>
135where
136    PF: DatabaseProviderFactory
137        + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
138        + ChainSpecProvider
139        + StageCheckpointReader
140        + BlockNumReader
141        + MetadataProvider
142        + StorageSettingsCache,
143    PF::ProviderRW: StaticFileProviderFactory<Primitives = PF::Primitives>
144        + StageCheckpointWriter
145        + HistoryWriter
146        + HeaderProvider
147        + HashingWriter
148        + StateWriter
149        + TrieWriter
150        + MetadataWriter
151        + ChainSpecProvider
152        + StorageSettingsCache
153        + RocksDBProviderFactory
154        + NodePrimitivesProvider
155        + AsRef<PF::ProviderRW>,
156    PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
157{
158    let chain = factory.chain_spec();
159
160    let genesis = chain.genesis();
161    let hash = chain.genesis_hash();
162
163    // Get the genesis block number from the chain spec
164    let genesis_block_number = chain.genesis_header().number();
165
166    // Check if we already have the genesis header or if we have the wrong one.
167    match factory.block_hash(genesis_block_number) {
168        Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, _)) => {}
169        Ok(Some(block_hash)) => {
170            if block_hash == hash {
171                // Some users will at times attempt to re-sync from scratch by just deleting the
172                // database. Since `factory.block_hash` will only query the static files, we need to
173                // make sure that our database has been written to, and throw error if it's empty.
174                if factory.get_stage_checkpoint(StageId::Headers)?.is_none() {
175                    error!(target: "reth::storage", "Genesis header found on static files, but database is uninitialized.");
176                    return Err(InitStorageError::UninitializedDatabase)
177                }
178
179                let stored = factory.storage_settings()?.unwrap_or_else(StorageSettings::v1);
180                if stored != genesis_storage_settings {
181                    warn!(
182                        target: "reth::storage",
183                        ?stored,
184                        requested = ?genesis_storage_settings,
185                        "Storage settings mismatch detected. Using the stored settings from the existing database."
186                    );
187                }
188
189                debug!("Genesis already written, skipping.");
190                return Ok(hash)
191            }
192
193            return Err(InitStorageError::GenesisHashMismatch {
194                chainspec_hash: hash,
195                storage_hash: block_hash,
196            })
197        }
198        Err(e) => {
199            debug!(?e);
200            return Err(e.into());
201        }
202    }
203
204    debug!("Writing genesis block.");
205
206    // Make sure to set storage settings before anything writes
207    factory.set_storage_settings_cache(genesis_storage_settings);
208
209    let alloc = &genesis.alloc;
210
211    // use transaction to insert genesis header
212    let provider_rw = factory.database_provider_rw()?;
213
214    // Behaviour reserved only for new nodes should be set in the storage settings.
215    provider_rw.write_storage_settings(genesis_storage_settings)?;
216
217    // For non-zero genesis blocks, set expected_block_start BEFORE insert_genesis_state.
218    // When block_range is None, next_block_number() uses expected_block_start. By default,
219    // expected_block_start comes from find_fixed_range which returns the file range start (0),
220    // not the genesis block number. This would cause increment_block(N) to fail.
221    let static_file_provider = provider_rw.static_file_provider();
222    if genesis_block_number > 0 {
223        if genesis_storage_settings.storage_v2 {
224            static_file_provider
225                .get_writer(genesis_block_number, StaticFileSegment::AccountChangeSets)?
226                .user_header_mut()
227                .set_expected_block_start(genesis_block_number);
228        }
229        if genesis_storage_settings.storage_v2 {
230            static_file_provider
231                .get_writer(genesis_block_number, StaticFileSegment::StorageChangeSets)?
232                .user_header_mut()
233                .set_expected_block_start(genesis_block_number);
234        }
235    }
236
237    insert_genesis_hashes(&provider_rw, alloc.iter())?;
238    insert_genesis_history(&provider_rw, alloc.iter())?;
239
240    // Insert header
241    insert_genesis_header(&provider_rw, &chain)?;
242
243    insert_genesis_state(&provider_rw, alloc.iter())?;
244
245    // compute state root to populate trie tables
246    compute_state_root(&provider_rw, None)?;
247
248    // set stage checkpoint to genesis block number for all stages
249    let checkpoint = StageCheckpoint::new(genesis_block_number);
250    for stage in StageId::ALL {
251        provider_rw.save_stage_checkpoint(stage, checkpoint)?;
252    }
253
254    // Static file segments start empty, so we need to initialize the block range.
255    // For genesis blocks with non-zero block numbers, we use get_writer() instead of
256    // latest_writer() and set_block_range() to ensure static files start at the correct block.
257    let static_file_provider = provider_rw.static_file_provider();
258
259    static_file_provider
260        .get_writer(genesis_block_number, StaticFileSegment::Receipts)?
261        .user_header_mut()
262        .set_block_range(genesis_block_number, genesis_block_number);
263    static_file_provider
264        .get_writer(genesis_block_number, StaticFileSegment::Transactions)?
265        .user_header_mut()
266        .set_block_range(genesis_block_number, genesis_block_number);
267
268    if genesis_storage_settings.storage_v2 {
269        static_file_provider
270            .get_writer(genesis_block_number, StaticFileSegment::TransactionSenders)?
271            .user_header_mut()
272            .set_block_range(genesis_block_number, genesis_block_number);
273    }
274
275    // `commit_unwind`` will first commit the DB and then the static file provider, which is
276    // necessary on `init_genesis`.
277    provider_rw.commit()?;
278
279    Ok(hash)
280}
281
282/// Inserts the genesis state into the database.
283pub fn insert_genesis_state<'a, 'b, Provider>(
284    provider: &Provider,
285    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
286) -> ProviderResult<()>
287where
288    Provider: StaticFileProviderFactory
289        + DBProvider<Tx: DbTxMut>
290        + HeaderProvider
291        + StateWriter
292        + ChainSpecProvider
293        + AsRef<Provider>,
294{
295    let genesis_block_number = provider.chain_spec().genesis_header().number();
296    insert_state(provider, alloc, genesis_block_number)
297}
298
299/// Inserts state at given block into database.
300pub fn insert_state<'a, 'b, Provider>(
301    provider: &Provider,
302    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
303    block: u64,
304) -> ProviderResult<()>
305where
306    Provider: StaticFileProviderFactory
307        + DBProvider<Tx: DbTxMut>
308        + HeaderProvider
309        + StateWriter
310        + AsRef<Provider>,
311{
312    let capacity = alloc.size_hint().1.unwrap_or(0);
313    let mut state_init: BundleStateInit =
314        AddressMap::with_capacity_and_hasher(capacity, Default::default());
315    let mut reverts_init: AddressMap<_> =
316        AddressMap::with_capacity_and_hasher(capacity, Default::default());
317    let mut contracts: B256Map<Bytecode> =
318        B256Map::with_capacity_and_hasher(capacity, Default::default());
319
320    for (address, account) in alloc {
321        let bytecode_hash = if let Some(code) = &account.code {
322            match Bytecode::new_raw_checked(code.clone()) {
323                Ok(bytecode) => {
324                    let hash = bytecode.hash_slow();
325                    contracts.insert(hash, bytecode);
326                    Some(hash)
327                }
328                Err(err) => {
329                    error!(%address, %err, "Failed to decode genesis bytecode.");
330                    return Err(DatabaseError::Other(err.to_string()).into());
331                }
332            }
333        } else {
334            None
335        };
336
337        // get state
338        let storage = account
339            .storage
340            .as_ref()
341            .map(|m| {
342                m.iter()
343                    .map(|(key, value)| {
344                        let value = U256::from_be_bytes(value.0);
345                        (*key, (U256::ZERO, value))
346                    })
347                    .collect::<B256Map<_>>()
348            })
349            .unwrap_or_default();
350
351        reverts_init.insert(
352            *address,
353            (Some(None), storage.keys().map(|k| StorageEntry::new(*k, U256::ZERO)).collect()),
354        );
355
356        state_init.insert(
357            *address,
358            (
359                None,
360                Some(Account {
361                    nonce: account.nonce.unwrap_or_default(),
362                    balance: account.balance,
363                    bytecode_hash,
364                }),
365                storage,
366            ),
367        );
368    }
369    let all_reverts_init: RevertsInit = HashMap::from_iter([(block, reverts_init)]);
370
371    let execution_outcome = ExecutionOutcome::new_init(
372        state_init,
373        all_reverts_init,
374        contracts,
375        Vec::default(),
376        block,
377        Vec::new(),
378    );
379
380    provider.write_state(
381        &execution_outcome,
382        OriginalValuesKnown::Yes,
383        StateWriteConfig::default(),
384    )?;
385
386    trace!(target: "reth::cli", "Inserted state");
387
388    Ok(())
389}
390
391/// Inserts hashes for the genesis state.
392pub fn insert_genesis_hashes<'a, 'b, Provider>(
393    provider: &Provider,
394    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)> + Clone,
395) -> ProviderResult<()>
396where
397    Provider: DBProvider<Tx: DbTxMut> + HashingWriter,
398{
399    // insert and hash accounts to hashing table
400    let alloc_accounts = alloc.clone().map(|(addr, account)| (*addr, Some(Account::from(account))));
401    provider.insert_account_for_hashing(alloc_accounts)?;
402
403    trace!(target: "reth::cli", "Inserted account hashes");
404
405    let alloc_storage = alloc.filter_map(|(addr, account)| {
406        // only return Some if there is storage
407        account.storage.as_ref().map(|storage| {
408            (*addr, storage.iter().map(|(&key, &value)| StorageEntry { key, value: value.into() }))
409        })
410    });
411    provider.insert_storage_for_hashing(alloc_storage)?;
412
413    trace!(target: "reth::cli", "Inserted storage hashes");
414
415    Ok(())
416}
417
418/// Inserts history indices for genesis accounts and storage.
419///
420/// Writes to either MDBX or `RocksDB` based on storage settings configuration,
421/// using [`EitherWriter`] to abstract over the storage backend.
422pub fn insert_genesis_history<'a, 'b, Provider>(
423    provider: &Provider,
424    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)> + Clone,
425) -> ProviderResult<()>
426where
427    Provider: DBProvider<Tx: DbTxMut>
428        + HistoryWriter
429        + ChainSpecProvider
430        + StorageSettingsCache
431        + RocksDBProviderFactory
432        + NodePrimitivesProvider,
433{
434    let genesis_block_number = provider.chain_spec().genesis_header().number();
435    insert_history(provider, alloc, genesis_block_number)
436}
437
438/// Inserts account history indices for genesis accounts.
439pub fn insert_genesis_account_history<'a, 'b, Provider>(
440    provider: &Provider,
441    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
442) -> ProviderResult<()>
443where
444    Provider: DBProvider<Tx: DbTxMut>
445        + HistoryWriter
446        + ChainSpecProvider
447        + StorageSettingsCache
448        + RocksDBProviderFactory
449        + NodePrimitivesProvider,
450{
451    let genesis_block_number = provider.chain_spec().genesis_header().number();
452    insert_account_history(provider, alloc, genesis_block_number)
453}
454
455/// Inserts storage history indices for genesis accounts.
456pub fn insert_genesis_storage_history<'a, 'b, Provider>(
457    provider: &Provider,
458    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
459) -> ProviderResult<()>
460where
461    Provider: DBProvider<Tx: DbTxMut>
462        + HistoryWriter
463        + ChainSpecProvider
464        + StorageSettingsCache
465        + RocksDBProviderFactory
466        + NodePrimitivesProvider,
467{
468    let genesis_block_number = provider.chain_spec().genesis_header().number();
469    insert_storage_history(provider, alloc, genesis_block_number)
470}
471
472/// Inserts history indices for genesis accounts and storage.
473///
474/// Writes to either MDBX or `RocksDB` based on storage settings configuration,
475/// using [`EitherWriter`] to abstract over the storage backend.
476pub fn insert_history<'a, 'b, Provider>(
477    provider: &Provider,
478    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)> + Clone,
479    block: u64,
480) -> ProviderResult<()>
481where
482    Provider: DBProvider<Tx: DbTxMut>
483        + HistoryWriter
484        + StorageSettingsCache
485        + RocksDBProviderFactory
486        + NodePrimitivesProvider,
487{
488    insert_account_history(provider, alloc.clone(), block)?;
489    insert_storage_history(provider, alloc, block)?;
490    Ok(())
491}
492
493/// Inserts account history indices at the given block.
494pub fn insert_account_history<'a, 'b, Provider>(
495    provider: &Provider,
496    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
497    block: u64,
498) -> ProviderResult<()>
499where
500    Provider: DBProvider<Tx: DbTxMut>
501        + HistoryWriter
502        + StorageSettingsCache
503        + RocksDBProviderFactory
504        + NodePrimitivesProvider,
505{
506    provider.with_rocksdb_batch(|batch| {
507        let mut writer = EitherWriter::new_accounts_history(provider, batch)?;
508        let list = BlockNumberList::new([block]).expect("single block always fits");
509        for (addr, _) in alloc {
510            writer.upsert_account_history(ShardedKey::last(*addr), &list)?;
511        }
512        trace!(target: "reth::cli", "Inserted account history");
513        Ok(((), writer.into_raw_rocksdb_batch()))
514    })?;
515
516    Ok(())
517}
518
519/// Inserts storage history indices at the given block.
520pub fn insert_storage_history<'a, 'b, Provider>(
521    provider: &Provider,
522    alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
523    block: u64,
524) -> ProviderResult<()>
525where
526    Provider: DBProvider<Tx: DbTxMut>
527        + HistoryWriter
528        + StorageSettingsCache
529        + RocksDBProviderFactory
530        + NodePrimitivesProvider,
531{
532    provider.with_rocksdb_batch(|batch| {
533        let mut writer = EitherWriter::new_storages_history(provider, batch)?;
534        let list = BlockNumberList::new([block]).expect("single block always fits");
535        for (addr, account) in alloc {
536            if let Some(storage) = &account.storage {
537                for key in storage.keys() {
538                    writer.upsert_storage_history(StorageShardedKey::last(*addr, *key), &list)?;
539                }
540            }
541        }
542        trace!(target: "reth::cli", "Inserted storage history");
543        Ok(((), writer.into_raw_rocksdb_batch()))
544    })?;
545
546    Ok(())
547}
548
549/// Inserts header for the genesis state.
550pub fn insert_genesis_header<Provider, Spec>(
551    provider: &Provider,
552    chain: &Spec,
553) -> ProviderResult<()>
554where
555    Provider: StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
556        + DBProvider<Tx: DbTxMut>,
557    Spec: EthChainSpec<Header = <Provider::Primitives as NodePrimitives>::BlockHeader>,
558{
559    let (header, block_hash) = (chain.genesis_header(), chain.genesis_hash());
560    let static_file_provider = provider.static_file_provider();
561
562    // Get the actual genesis block number from the header
563    let genesis_block_number = header.number();
564
565    match static_file_provider.block_hash(genesis_block_number) {
566        Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, _)) => {
567            let difficulty = header.difficulty();
568
569            // For genesis blocks with non-zero block numbers, we need to ensure they are stored
570            // in the correct static file range. We use get_writer() with the genesis block number
571            // to ensure the genesis block is stored in the correct static file range.
572            let mut writer = static_file_provider
573                .get_writer(genesis_block_number, StaticFileSegment::Headers)?;
574
575            // For non-zero genesis blocks, we need to set block range to genesis_block_number and
576            // append header without increment block
577            if genesis_block_number > 0 {
578                writer
579                    .user_header_mut()
580                    .set_block_range(genesis_block_number, genesis_block_number);
581                writer.append_header_direct(header, difficulty, &block_hash)?;
582            } else {
583                // For zero genesis blocks, use normal append_header
584                writer.append_header(header, &block_hash)?;
585            }
586        }
587        Ok(Some(_)) => {}
588        Err(e) => return Err(e),
589    }
590
591    provider.tx_ref().put::<tables::HeaderNumbers>(block_hash, genesis_block_number)?;
592    provider.tx_ref().put::<tables::BlockBodyIndices>(genesis_block_number, Default::default())?;
593
594    Ok(())
595}
596
597/// Reads account state from a [`BufRead`] reader and initializes it at the highest block that can
598/// be found on database.
599///
600/// It's similar to [`init_genesis`] but supports importing state too big to fit in memory, and can
601/// be set to the highest block present. One practical usecase is to import OP mainnet state at
602/// bedrock transition block.
603pub fn init_from_state_dump<Provider>(
604    mut reader: impl BufRead,
605    provider_rw: &Provider,
606    etl_config: EtlConfig,
607) -> eyre::Result<B256>
608where
609    Provider: StaticFileProviderFactory
610        + DBProvider<Tx: DbTxMut>
611        + BlockNumReader
612        + BlockHashReader
613        + ChainSpecProvider
614        + StageCheckpointWriter
615        + HistoryWriter
616        + HeaderProvider
617        + HashingWriter
618        + TrieWriter
619        + StateWriter
620        + StorageSettingsCache
621        + RocksDBProviderFactory
622        + NodePrimitivesProvider
623        + AsRef<Provider>,
624{
625    if etl_config.file_size == 0 {
626        return Err(eyre::eyre!("ETL file size cannot be zero"))
627    }
628
629    let block = provider_rw.last_block_number()?;
630
631    let hash = provider_rw
632        .block_hash(block)?
633        .ok_or_else(|| eyre::eyre!("Block hash not found for block {}", block))?;
634    let header = provider_rw
635        .header_by_number(block)?
636        .map(SealedHeader::seal_slow)
637        .ok_or_else(|| ProviderError::HeaderNotFound(block.into()))?;
638
639    let expected_state_root = header.state_root();
640
641    // first line can be state root
642    let dump_state_root = parse_state_root(&mut reader)?;
643    if expected_state_root != dump_state_root {
644        error!(target: "reth::cli",
645            ?dump_state_root,
646            ?expected_state_root,
647            header=?header.num_hash(),
648            "State root from state dump does not match state root in current header."
649        );
650        return Err(InitStorageError::StateRootMismatch(GotExpected {
651            got: dump_state_root,
652            expected: expected_state_root,
653        })
654        .into())
655    }
656
657    debug!(target: "reth::cli",
658        block,
659        chain=%provider_rw.chain_spec().chain(),
660        "Initializing state at block"
661    );
662
663    // remaining lines are accounts
664    let collector = parse_accounts(&mut reader, etl_config)?;
665
666    // write state to db and collect prefix sets
667    let mut prefix_sets = TriePrefixSetsMut::default();
668    dump_state(collector, provider_rw, block, &mut prefix_sets)?;
669
670    info!(target: "reth::cli", "All accounts written to database, starting state root computation (may take some time)");
671
672    // compute and compare state root. this advances the stage checkpoints.
673    let computed_state_root = compute_state_root(provider_rw, Some(prefix_sets.freeze()))?;
674    if computed_state_root == expected_state_root {
675        info!(target: "reth::cli",
676            ?computed_state_root,
677            "Computed state root matches state root in state dump"
678        );
679    } else {
680        error!(target: "reth::cli",
681            ?computed_state_root,
682            ?expected_state_root,
683            "Computed state root does not match state root in state dump"
684        );
685
686        return Err(InitStorageError::StateRootMismatch(GotExpected {
687            got: computed_state_root,
688            expected: expected_state_root,
689        })
690        .into())
691    }
692
693    // insert sync stages for stages that require state
694    for stage in StageId::STATE_REQUIRED {
695        provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(block))?;
696    }
697
698    Ok(hash)
699}
700
701/// Parses and returns expected state root.
702fn parse_state_root(reader: &mut impl BufRead) -> eyre::Result<B256> {
703    let mut line = String::new();
704    reader.read_line(&mut line)?;
705
706    let expected_state_root = serde_json::from_str::<StateRoot>(&line)?.root;
707    trace!(target: "reth::cli",
708        root=%expected_state_root,
709        "Read state root from file"
710    );
711    Ok(expected_state_root)
712}
713
714/// Parses accounts and pushes them to a [`Collector`].
715fn parse_accounts(
716    mut reader: impl BufRead,
717    etl_config: EtlConfig,
718) -> Result<Collector<Address, GenesisAccount>, eyre::Error> {
719    let mut line = String::new();
720    let mut collector = Collector::new(etl_config.file_size, etl_config.dir);
721
722    loop {
723        let n = reader.read_line(&mut line)?;
724        if n == 0 {
725            break
726        }
727
728        let GenesisAccountWithAddress { genesis_account, address } = serde_json::from_str(&line)?;
729        collector.insert(address, genesis_account)?;
730
731        if !collector.is_empty() &&
732            collector.len().is_multiple_of(AVERAGE_COUNT_ACCOUNTS_PER_GB_STATE_DUMP)
733        {
734            info!(target: "reth::cli",
735                parsed_new_accounts=collector.len(),
736            );
737        }
738
739        line.clear();
740    }
741
742    Ok(collector)
743}
744
745/// Takes a [`Collector`] and processes all accounts.
746fn dump_state<Provider>(
747    mut collector: Collector<Address, GenesisAccount>,
748    provider_rw: &Provider,
749    block: u64,
750    prefix_sets: &mut TriePrefixSetsMut,
751) -> Result<(), eyre::Error>
752where
753    Provider: StaticFileProviderFactory
754        + DBProvider<Tx: DbTxMut>
755        + HeaderProvider
756        + HashingWriter
757        + HistoryWriter
758        + StateWriter
759        + StorageSettingsCache
760        + RocksDBProviderFactory
761        + NodePrimitivesProvider
762        + AsRef<Provider>,
763{
764    let accounts_len = collector.len();
765    let mut accounts = Vec::with_capacity(AVERAGE_COUNT_ACCOUNTS_PER_GB_STATE_DUMP);
766    let mut total_inserted_accounts = 0;
767
768    for (index, entry) in collector.iter()?.enumerate() {
769        let (address, account) = entry?;
770        let (address, _) = Address::from_compact(address.as_slice(), address.len());
771        let (account, _) = GenesisAccount::from_compact(account.as_slice(), account.len());
772
773        // Add to prefix sets
774        let hashed_address = keccak256(address);
775        prefix_sets.account_prefix_set.insert(Nibbles::unpack(hashed_address));
776
777        // Add storage keys to prefix sets if storage exists
778        if let Some(ref storage) = account.storage {
779            for key in storage.keys() {
780                let hashed_key = keccak256(key);
781                prefix_sets
782                    .storage_prefix_sets
783                    .entry(hashed_address)
784                    .or_default()
785                    .insert(Nibbles::unpack(hashed_key));
786            }
787        }
788
789        accounts.push((address, account));
790
791        if (index > 0 && index.is_multiple_of(AVERAGE_COUNT_ACCOUNTS_PER_GB_STATE_DUMP)) ||
792            index == accounts_len - 1
793        {
794            total_inserted_accounts += accounts.len();
795
796            info!(target: "reth::cli",
797                total_inserted_accounts,
798                "Writing accounts to db"
799            );
800
801            // use transaction to insert genesis header
802            insert_genesis_hashes(
803                provider_rw,
804                accounts.iter().map(|(address, account)| (address, account)),
805            )?;
806
807            insert_history(
808                provider_rw,
809                accounts.iter().map(|(address, account)| (address, account)),
810                block,
811            )?;
812
813            // block is already written to static files
814            insert_state(
815                provider_rw,
816                accounts.iter().map(|(address, account)| (address, account)),
817                block,
818            )?;
819
820            accounts.clear();
821        }
822    }
823    Ok(())
824}
825
826/// Computes the state root (from scratch) based on the accounts and storages present in the
827/// database.
828fn compute_state_root<Provider>(
829    provider: &Provider,
830    prefix_sets: Option<TriePrefixSets>,
831) -> Result<B256, InitStorageError>
832where
833    Provider: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
834{
835    reth_trie_db::with_adapter!(provider, |A| {
836        compute_state_root_inner::<_, A>(provider, prefix_sets)
837    })
838}
839
840fn compute_state_root_inner<Provider, A>(
841    provider: &Provider,
842    prefix_sets: Option<TriePrefixSets>,
843) -> Result<B256, InitStorageError>
844where
845    Provider: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
846    A: reth_trie_db::TrieTableAdapter,
847{
848    trace!(target: "reth::cli", "Computing state root");
849
850    let tx = provider.tx_ref();
851    let mut intermediate_state: Option<IntermediateStateRootState> = None;
852    let mut total_flushed_updates = 0;
853
854    loop {
855        let mut state_root =
856            DbStateRoot::<_, A>::from_tx(tx).with_intermediate_state(intermediate_state);
857
858        if let Some(sets) = prefix_sets.clone() {
859            state_root = state_root.with_prefix_sets(sets);
860        }
861
862        match state_root.root_with_progress()? {
863            StateRootProgress::Progress(state, _, updates) => {
864                let updated_len = provider.write_trie_updates(updates)?;
865                total_flushed_updates += updated_len;
866
867                trace!(target: "reth::cli",
868                    last_account_key = %state.account_root_state.last_hashed_key,
869                    updated_len,
870                    total_flushed_updates,
871                    "Flushing trie updates"
872                );
873
874                intermediate_state = Some(*state);
875
876                if total_flushed_updates.is_multiple_of(SOFT_LIMIT_COUNT_FLUSHED_UPDATES) {
877                    info!(target: "reth::cli",
878                        total_flushed_updates,
879                        "Flushing trie updates"
880                    );
881                }
882            }
883            StateRootProgress::Complete(root, _, updates) => {
884                let updated_len = provider.write_trie_updates(updates)?;
885                total_flushed_updates += updated_len;
886
887                trace!(target: "reth::cli",
888                    %root,
889                    updated_len,
890                    total_flushed_updates,
891                    "State root has been computed"
892                );
893
894                return Ok(root)
895            }
896        }
897    }
898}
899
900/// Type to deserialize state root from state dump file.
901#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
902struct StateRoot {
903    root: B256,
904}
905
906/// An account as in the state dump file. This contains a [`GenesisAccount`] and the account's
907/// address.
908#[derive(Debug, Serialize, Deserialize)]
909struct GenesisAccountWithAddress {
910    /// The account's balance, nonce, code, and storage.
911    #[serde(flatten)]
912    genesis_account: GenesisAccount,
913    /// The account's address.
914    address: Address,
915}
916
917#[cfg(test)]
918mod tests {
919    use super::*;
920    use alloy_consensus::constants::{
921        HOLESKY_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH,
922    };
923    use alloy_genesis::Genesis;
924    use reth_chainspec::{Chain, ChainSpec, HOLESKY, MAINNET, SEPOLIA};
925    use reth_db::DatabaseEnv;
926    use reth_db_api::{
927        cursor::DbCursorRO,
928        models::{storage_sharded_key::StorageShardedKey, IntegerList, ShardedKey},
929        table::{Table, TableRow},
930        transaction::DbTx,
931        Database,
932    };
933    use reth_provider::{
934        test_utils::{create_test_provider_factory_with_chain_spec, MockNodeTypesWithDB},
935        ProviderFactory, RocksDBProviderFactory,
936    };
937    use std::{collections::BTreeMap, sync::Arc};
938
939    fn collect_table_entries<DB, T>(
940        tx: &<DB as Database>::TX,
941    ) -> Result<Vec<TableRow<T>>, InitStorageError>
942    where
943        DB: Database,
944        T: Table,
945    {
946        Ok(tx.cursor_read::<T>()?.walk_range(..)?.collect::<Result<Vec<_>, _>>()?)
947    }
948
949    #[test]
950    fn success_init_genesis_mainnet() {
951        let genesis_hash =
952            init_genesis(&create_test_provider_factory_with_chain_spec(MAINNET.clone())).unwrap();
953
954        // actual, expected
955        assert_eq!(genesis_hash, MAINNET_GENESIS_HASH);
956    }
957
958    #[test]
959    fn success_init_genesis_sepolia() {
960        let genesis_hash =
961            init_genesis(&create_test_provider_factory_with_chain_spec(SEPOLIA.clone())).unwrap();
962
963        // actual, expected
964        assert_eq!(genesis_hash, SEPOLIA_GENESIS_HASH);
965    }
966
967    #[test]
968    fn success_init_genesis_holesky() {
969        let genesis_hash =
970            init_genesis(&create_test_provider_factory_with_chain_spec(HOLESKY.clone())).unwrap();
971
972        // actual, expected
973        assert_eq!(genesis_hash, HOLESKY_GENESIS_HASH);
974    }
975
976    #[test]
977    fn fail_init_inconsistent_db() {
978        let factory = create_test_provider_factory_with_chain_spec(SEPOLIA.clone());
979        let static_file_provider = factory.static_file_provider();
980        let rocksdb_provider = factory.rocksdb_provider();
981        init_genesis(&factory).unwrap();
982
983        // Try to init db with a different genesis block
984        let genesis_hash = init_genesis(
985            &ProviderFactory::<MockNodeTypesWithDB>::new(
986                factory.into_db(),
987                MAINNET.clone(),
988                static_file_provider,
989                rocksdb_provider,
990                reth_tasks::Runtime::test(),
991            )
992            .unwrap(),
993        );
994
995        assert!(matches!(
996            genesis_hash.unwrap_err(),
997            InitStorageError::GenesisHashMismatch {
998                chainspec_hash: MAINNET_GENESIS_HASH,
999                storage_hash: SEPOLIA_GENESIS_HASH
1000            }
1001        ))
1002    }
1003
1004    #[test]
1005    fn init_genesis_history() {
1006        let address_with_balance = Address::with_last_byte(1);
1007        let address_with_storage = Address::with_last_byte(2);
1008        let storage_key = B256::with_last_byte(1);
1009        let chain_spec = Arc::new(ChainSpec {
1010            chain: Chain::from_id(1),
1011            genesis: Genesis {
1012                alloc: BTreeMap::from([
1013                    (
1014                        address_with_balance,
1015                        GenesisAccount { balance: U256::from(1), ..Default::default() },
1016                    ),
1017                    (
1018                        address_with_storage,
1019                        GenesisAccount {
1020                            storage: Some(BTreeMap::from([(storage_key, B256::random())])),
1021                            ..Default::default()
1022                        },
1023                    ),
1024                ]),
1025                ..Default::default()
1026            },
1027            hardforks: Default::default(),
1028            paris_block_and_final_difficulty: None,
1029            deposit_contract: None,
1030            ..Default::default()
1031        });
1032
1033        let factory = create_test_provider_factory_with_chain_spec(chain_spec);
1034        init_genesis(&factory).unwrap();
1035
1036        let expected_accounts = vec![
1037            (ShardedKey::new(address_with_balance, u64::MAX), IntegerList::new([0]).unwrap()),
1038            (ShardedKey::new(address_with_storage, u64::MAX), IntegerList::new([0]).unwrap()),
1039        ];
1040        let expected_storages = vec![(
1041            StorageShardedKey::new(address_with_storage, storage_key, u64::MAX),
1042            IntegerList::new([0]).unwrap(),
1043        )];
1044
1045        let collect_from_mdbx = |factory: &ProviderFactory<MockNodeTypesWithDB>| {
1046            let provider = factory.provider().unwrap();
1047            let tx = provider.tx_ref();
1048            (
1049                collect_table_entries::<DatabaseEnv, tables::AccountsHistory>(tx).unwrap(),
1050                collect_table_entries::<DatabaseEnv, tables::StoragesHistory>(tx).unwrap(),
1051            )
1052        };
1053
1054        #[cfg(feature = "rocksdb")]
1055        {
1056            let settings = factory.cached_storage_settings();
1057            let rocksdb = factory.rocksdb_provider();
1058
1059            let collect_rocksdb = |rocksdb: &reth_provider::providers::RocksDBProvider| {
1060                (
1061                    rocksdb
1062                        .iter::<tables::AccountsHistory>()
1063                        .unwrap()
1064                        .collect::<Result<Vec<_>, _>>()
1065                        .unwrap(),
1066                    rocksdb
1067                        .iter::<tables::StoragesHistory>()
1068                        .unwrap()
1069                        .collect::<Result<Vec<_>, _>>()
1070                        .unwrap(),
1071                )
1072            };
1073
1074            let (accounts, storages) = if settings.storage_v2 {
1075                collect_rocksdb(&rocksdb)
1076            } else {
1077                collect_from_mdbx(&factory)
1078            };
1079            assert_eq!(accounts, expected_accounts);
1080            assert_eq!(storages, expected_storages);
1081        }
1082
1083        #[cfg(not(feature = "rocksdb"))]
1084        {
1085            let (accounts, storages) = collect_from_mdbx(&factory);
1086            assert_eq!(accounts, expected_accounts);
1087            assert_eq!(storages, expected_storages);
1088        }
1089    }
1090
1091    #[test]
1092    fn warn_storage_settings_mismatch() {
1093        let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
1094        init_genesis_with_settings(&factory, StorageSettings::v1()).unwrap();
1095
1096        // Request different settings - should warn but succeed
1097        let result = init_genesis_with_settings(&factory, StorageSettings::v2());
1098
1099        // Should succeed (warning is logged, not an error)
1100        assert!(result.is_ok());
1101    }
1102
1103    #[test]
1104    fn allow_same_storage_settings() {
1105        let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
1106        let settings = StorageSettings::v2();
1107        init_genesis_with_settings(&factory, settings).unwrap();
1108
1109        let result = init_genesis_with_settings(&factory, settings);
1110
1111        assert!(result.is_ok());
1112    }
1113}