1use 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
49pub const DEFAULT_SOFT_LIMIT_BYTE_LEN_ACCOUNTS_CHUNK: usize = 1_000_000_000;
54
55pub const AVERAGE_COUNT_ACCOUNTS_PER_GB_STATE_DUMP: usize = 285_228;
62
63const SOFT_LIMIT_COUNT_FLUSHED_UPDATES: usize = 1_000_000;
65
66#[derive(Debug, thiserror::Error, Clone)]
68pub enum InitStorageError {
69 #[error(
71 "static files found, but the database is uninitialized. If attempting to re-syncing, delete both."
72 )]
73 UninitializedDatabase,
74 #[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 chainspec_hash: B256,
82 storage_hash: B256,
84 },
85 #[error(transparent)]
87 Provider(#[from] ProviderError),
88 #[error(transparent)]
90 StateRootError(#[from] StateRootError),
91 #[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
102pub 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
130pub 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 let genesis_block_number = chain.genesis_header().number();
165
166 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 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 factory.set_storage_settings_cache(genesis_storage_settings);
208
209 let alloc = &genesis.alloc;
210
211 let provider_rw = factory.database_provider_rw()?;
213
214 provider_rw.write_storage_settings(genesis_storage_settings)?;
216
217 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_genesis_header(&provider_rw, &chain)?;
242
243 insert_genesis_state(&provider_rw, alloc.iter())?;
244
245 compute_state_root(&provider_rw, None)?;
247
248 let checkpoint = StageCheckpoint::new(genesis_block_number);
250 for stage in StageId::ALL {
251 provider_rw.save_stage_checkpoint(stage, checkpoint)?;
252 }
253
254 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 provider_rw.commit()?;
278
279 Ok(hash)
280}
281
282pub 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
299pub 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 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
391pub 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 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 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
418pub 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
438pub 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
455pub 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
472pub 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
493pub 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
519pub 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
549pub 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 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 let mut writer = static_file_provider
573 .get_writer(genesis_block_number, StaticFileSegment::Headers)?;
574
575 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 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
597pub 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 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 let collector = parse_accounts(&mut reader, etl_config)?;
665
666 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 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 for stage in StageId::STATE_REQUIRED {
695 provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(block))?;
696 }
697
698 Ok(hash)
699}
700
701fn 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
714fn 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
745fn 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 let hashed_address = keccak256(address);
775 prefix_sets.account_prefix_set.insert(Nibbles::unpack(hashed_address));
776
777 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 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 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
826fn 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#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
902struct StateRoot {
903 root: B256,
904}
905
906#[derive(Debug, Serialize, Deserialize)]
909struct GenesisAccountWithAddress {
910 #[serde(flatten)]
912 genesis_account: GenesisAccount,
913 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 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 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 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 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 let result = init_genesis_with_settings(&factory, StorageSettings::v2());
1098
1099 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}