reth_db_common/
init.rs

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