1use alloy_consensus::BlockHeader;
4use alloy_genesis::GenesisAccount;
5use alloy_primitives::{
6 keccak256,
7 map::{AddressMap, B256Map, B256Set, HashMap},
8 Address, B256, U256,
9};
10use reth_chainspec::EthChainSpec;
11use reth_codecs::Compact;
12use reth_config::config::EtlConfig;
13use reth_db_api::{
14 cursor::{DbCursorRW, DbDupCursorRO, DbDupCursorRW},
15 models::{
16 integer_list::IntegerListError, storage_sharded_key::StorageShardedKey, AccountBeforeTx,
17 BlockNumberAddress, IntegerList, ShardedKey,
18 },
19 table::DupSort,
20 tables,
21 transaction::{DbTx, DbTxMut},
22 DatabaseError,
23};
24use reth_etl::Collector;
25use reth_execution_errors::StateRootError;
26use reth_primitives_traits::{
27 Account, Bytecode, GotExpected, NodePrimitives, SealedHeader, StorageEntry,
28};
29use reth_provider::{
30 errors::provider::ProviderResult, providers::StaticFileWriter, BlockHashReader, BlockNumReader,
31 BundleStateInit, ChainSpecProvider, DBProvider, DatabaseProviderFactory, ExecutionOutcome,
32 HashingWriter, HeaderProvider, HistoryWriter, MetadataProvider, MetadataWriter,
33 OriginalValuesKnown, ProviderError, RevertsInit, RocksDBProviderFactory, StageCheckpointReader,
34 StageCheckpointWriter, StateWriteConfig, StateWriter, StaticFileProviderFactory,
35 StorageSettings, StorageSettingsCache, TrieWriter,
36};
37use reth_stages_types::{StageCheckpoint, StageId};
38use reth_static_file_types::StaticFileSegment;
39use reth_trie::{
40 prefix_set::TriePrefixSets, IntermediateStateRootState, StateRoot as StateRootComputer,
41 StateRootProgress,
42};
43use reth_trie_db::DatabaseStateRoot;
44
45type DbStateRoot<'a, TX, A> = StateRootComputer<
46 reth_trie_db::DatabaseTrieCursorFactory<&'a TX, A>,
47 reth_trie_db::DatabaseHashedCursorFactory<&'a TX>,
48>;
49
50use serde::{Deserialize, Serialize};
51use std::io::BufRead;
52use tracing::{debug, error, info, trace, warn};
53
54pub use reth_provider::init::{
55 insert_account_history, insert_genesis_account_history, insert_genesis_history,
56 insert_genesis_storage_history, insert_history, insert_storage_history,
57};
58
59pub const DEFAULT_SOFT_LIMIT_BYTE_LEN_ACCOUNTS_CHUNK: usize = 1_000_000_000;
64
65const SOFT_LIMIT_COUNT_FLUSHED_UPDATES: usize = 1_000_000;
67
68const STORAGE_COMMIT_THRESHOLD: usize = 100_000;
72
73const STATE_ROOT_COMMIT_THRESHOLD: u64 = 25_000;
75
76#[derive(Debug, thiserror::Error, Clone)]
78pub enum InitStorageError {
79 #[error(
81 "static files found, but the database is uninitialized. If attempting to re-syncing, delete both."
82 )]
83 UninitializedDatabase,
84 #[error(
87 "genesis hash in the storage does not match the specified chainspec: chainspec is {chainspec_hash}, database is {storage_hash}"
88 )]
89 GenesisHashMismatch {
90 chainspec_hash: B256,
92 storage_hash: B256,
94 },
95 #[error(transparent)]
97 Provider(#[from] ProviderError),
98 #[error(transparent)]
100 StateRootError(#[from] StateRootError),
101 #[error("state root mismatch: {_0}")]
103 StateRootMismatch(GotExpected<B256>),
104}
105
106impl From<DatabaseError> for InitStorageError {
107 fn from(error: DatabaseError) -> Self {
108 Self::Provider(ProviderError::Database(error))
109 }
110}
111
112pub fn init_genesis<PF>(factory: &PF) -> Result<B256, InitStorageError>
114where
115 PF: DatabaseProviderFactory
116 + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
117 + ChainSpecProvider
118 + StageCheckpointReader
119 + BlockNumReader
120 + MetadataProvider
121 + StorageSettingsCache,
122 PF::ProviderRW: StaticFileProviderFactory<Primitives = PF::Primitives>
123 + StageCheckpointWriter
124 + HistoryWriter
125 + HeaderProvider
126 + HashingWriter
127 + StateWriter
128 + TrieWriter
129 + MetadataWriter
130 + ChainSpecProvider
131 + StorageSettingsCache
132 + RocksDBProviderFactory
133 + AsRef<PF::ProviderRW>,
134 PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
135{
136 init_genesis_with_settings(factory, StorageSettings::base())
137}
138
139pub fn init_genesis_with_settings<PF>(
141 factory: &PF,
142 genesis_storage_settings: StorageSettings,
143) -> Result<B256, InitStorageError>
144where
145 PF: DatabaseProviderFactory
146 + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
147 + ChainSpecProvider
148 + StageCheckpointReader
149 + BlockNumReader
150 + MetadataProvider
151 + StorageSettingsCache,
152 PF::ProviderRW: StaticFileProviderFactory<Primitives = PF::Primitives>
153 + StageCheckpointWriter
154 + HistoryWriter
155 + HeaderProvider
156 + HashingWriter
157 + StateWriter
158 + TrieWriter
159 + MetadataWriter
160 + ChainSpecProvider
161 + StorageSettingsCache
162 + RocksDBProviderFactory
163 + AsRef<PF::ProviderRW>,
164 PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
165{
166 init_genesis_with_settings_and_validate(factory, genesis_storage_settings, true)
167}
168
169pub fn init_genesis_with_settings_and_validate<PF>(
175 factory: &PF,
176 genesis_storage_settings: StorageSettings,
177 validate_genesis_hash: bool,
178) -> Result<B256, InitStorageError>
179where
180 PF: DatabaseProviderFactory
181 + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
182 + ChainSpecProvider
183 + StageCheckpointReader
184 + BlockNumReader
185 + MetadataProvider
186 + StorageSettingsCache,
187 PF::ProviderRW: StaticFileProviderFactory<Primitives = PF::Primitives>
188 + StageCheckpointWriter
189 + HistoryWriter
190 + HeaderProvider
191 + HashingWriter
192 + StateWriter
193 + TrieWriter
194 + MetadataWriter
195 + ChainSpecProvider
196 + StorageSettingsCache
197 + RocksDBProviderFactory
198 + AsRef<PF::ProviderRW>,
199 PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
200{
201 let chain = factory.chain_spec();
202
203 let genesis = chain.genesis();
204 let hash = chain.genesis_hash();
205
206 let genesis_block_number = chain.genesis_header().number();
208
209 match factory.block_hash(genesis_block_number) {
211 Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, _)) => {}
212 Ok(Some(block_hash)) => {
213 if block_hash == hash {
214 if factory.get_stage_checkpoint(StageId::Headers)?.is_some() {
215 let stored = factory.storage_settings()?.unwrap_or_else(StorageSettings::v1);
216 if stored != genesis_storage_settings {
217 warn!(
218 target: "reth::storage",
219 ?stored,
220 requested = ?genesis_storage_settings,
221 "Storage settings mismatch detected. Using the stored settings from the existing database."
222 );
223 }
224
225 debug!("Genesis already written, skipping.");
226 return Ok(hash)
227 }
228
229 let static_files = factory.static_file_provider();
234 let has_non_genesis_static_files = StaticFileSegment::iter().any(|segment| {
235 static_files
236 .get_highest_static_file_block(segment)
237 .is_some_and(|block| block != genesis_block_number) ||
238 static_files
239 .get_lowest_range_start(segment)
240 .is_some_and(|block| block != genesis_block_number) ||
241 (segment.is_tx_based() &&
242 static_files.get_highest_static_file_tx(segment).is_some())
243 });
244 if has_non_genesis_static_files {
245 error!(target: "reth::storage", "Genesis header found on static files, but database is uninitialized.");
246 return Err(InitStorageError::UninitializedDatabase)
247 }
248
249 for segment in
252 [StaticFileSegment::AccountChangeSets, StaticFileSegment::StorageChangeSets]
253 {
254 static_files.delete_segment(segment)?;
255 }
256 warn!(target: "reth::storage", "Resuming interrupted genesis initialization.");
257 } else if !validate_genesis_hash {
258 warn!(
259 target: "reth::storage",
260 chainspec_hash = %hash,
261 storage_hash = %block_hash,
262 "Genesis hash mismatch with chainspec; trusting DB per --debug.skip-genesis-validation"
263 );
264 return Ok(block_hash)
265 } else {
266 return Err(InitStorageError::GenesisHashMismatch {
267 chainspec_hash: hash,
268 storage_hash: block_hash,
269 })
270 }
271 }
272 Err(e) => {
273 debug!(?e);
274 return Err(e.into());
275 }
276 }
277
278 debug!("Writing genesis block.");
279
280 factory.set_storage_settings_cache(genesis_storage_settings);
282
283 let alloc = &genesis.alloc;
284
285 let provider_rw = factory.database_provider_rw()?;
287
288 provider_rw.write_storage_settings(genesis_storage_settings)?;
290
291 let static_file_provider = provider_rw.static_file_provider();
296 if genesis_block_number > 0 {
297 if genesis_storage_settings.storage_v2 {
298 static_file_provider
299 .get_writer(genesis_block_number, StaticFileSegment::AccountChangeSets)?
300 .user_header_mut()
301 .set_expected_block_start(genesis_block_number);
302 }
303 if genesis_storage_settings.storage_v2 {
304 static_file_provider
305 .get_writer(genesis_block_number, StaticFileSegment::StorageChangeSets)?
306 .user_header_mut()
307 .set_expected_block_start(genesis_block_number);
308 }
309 }
310
311 insert_genesis_hashes(&provider_rw, alloc.iter())?;
312 insert_genesis_history(&provider_rw, alloc.iter())?;
313
314 insert_genesis_header(&provider_rw, &chain)?;
316
317 insert_genesis_state(&provider_rw, alloc.iter())?;
318
319 compute_state_root(&provider_rw, None)?;
321
322 let checkpoint = StageCheckpoint::new(genesis_block_number);
324 for stage in StageId::ALL {
325 provider_rw.save_stage_checkpoint(stage, checkpoint)?;
326 }
327
328 let static_file_provider = provider_rw.static_file_provider();
332
333 static_file_provider
334 .get_writer(genesis_block_number, StaticFileSegment::Receipts)?
335 .user_header_mut()
336 .set_block_range(genesis_block_number, genesis_block_number);
337 static_file_provider
338 .get_writer(genesis_block_number, StaticFileSegment::Transactions)?
339 .user_header_mut()
340 .set_block_range(genesis_block_number, genesis_block_number);
341
342 if genesis_storage_settings.storage_v2 {
343 static_file_provider
344 .get_writer(genesis_block_number, StaticFileSegment::TransactionSenders)?
345 .user_header_mut()
346 .set_block_range(genesis_block_number, genesis_block_number);
347 }
348
349 provider_rw.commit()?;
350
351 Ok(hash)
352}
353
354pub fn insert_genesis_state<'a, 'b, Provider>(
356 provider: &Provider,
357 alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
358) -> ProviderResult<()>
359where
360 Provider: StaticFileProviderFactory
361 + DBProvider<Tx: DbTxMut>
362 + HeaderProvider
363 + StateWriter
364 + ChainSpecProvider
365 + AsRef<Provider>,
366{
367 let genesis_block_number = provider.chain_spec().genesis_header().number();
368 insert_state(provider, alloc, genesis_block_number)
369}
370
371pub fn insert_state<'a, 'b, Provider>(
373 provider: &Provider,
374 alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
375 block: u64,
376) -> ProviderResult<()>
377where
378 Provider: StaticFileProviderFactory
379 + DBProvider<Tx: DbTxMut>
380 + HeaderProvider
381 + StateWriter
382 + AsRef<Provider>,
383{
384 let capacity = alloc.size_hint().1.unwrap_or(0);
385 let mut state_init: BundleStateInit =
386 AddressMap::with_capacity_and_hasher(capacity, Default::default());
387 let mut reverts_init: AddressMap<_> =
388 AddressMap::with_capacity_and_hasher(capacity, Default::default());
389 let mut contracts: B256Map<Bytecode> =
390 B256Map::with_capacity_and_hasher(capacity, Default::default());
391
392 for (address, account) in alloc {
393 let bytecode_hash = if let Some(code) = &account.code {
394 match Bytecode::new_raw_checked(code.clone()) {
395 Ok(bytecode) => {
396 let hash = bytecode.hash_slow();
397 contracts.insert(hash, bytecode);
398 Some(hash)
399 }
400 Err(err) => {
401 error!(%address, %err, "Failed to decode genesis bytecode.");
402 return Err(DatabaseError::Other(err.to_string()).into());
403 }
404 }
405 } else {
406 None
407 };
408
409 let storage = account
411 .storage
412 .as_ref()
413 .map(|m| {
414 m.iter()
415 .map(|(key, value)| {
416 let value = U256::from_be_bytes(value.0);
417 (*key, (U256::ZERO, value))
418 })
419 .collect::<B256Map<_>>()
420 })
421 .unwrap_or_default();
422
423 reverts_init.insert(
424 *address,
425 (Some(None), storage.keys().map(|k| StorageEntry::new(*k, U256::ZERO)).collect()),
426 );
427
428 state_init.insert(
429 *address,
430 (None, Some(Account { bytecode_hash, ..Account::from(account) }), storage),
431 );
432 }
433 let all_reverts_init: RevertsInit = HashMap::from_iter([(block, reverts_init)]);
434
435 let execution_outcome = ExecutionOutcome::new_init(
436 state_init,
437 all_reverts_init,
438 contracts,
439 Vec::default(),
440 block,
441 Vec::new(),
442 );
443
444 provider.write_state(
445 &execution_outcome,
446 OriginalValuesKnown::Yes,
447 StateWriteConfig::default(),
448 )?;
449
450 trace!(target: "reth::cli", "Inserted state");
451
452 Ok(())
453}
454
455pub fn insert_genesis_hashes<'a, 'b, Provider>(
457 provider: &Provider,
458 alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)> + Clone,
459) -> ProviderResult<()>
460where
461 Provider: DBProvider<Tx: DbTxMut> + HashingWriter,
462{
463 let alloc_accounts = alloc.clone().map(|(addr, account)| (*addr, Some(Account::from(account))));
465 provider.insert_account_for_hashing(alloc_accounts)?;
466
467 trace!(target: "reth::cli", "Inserted account hashes");
468
469 let alloc_storage = alloc.filter_map(|(addr, account)| {
470 account.storage.as_ref().map(|storage| {
472 (*addr, storage.iter().map(|(&key, &value)| StorageEntry { key, value: value.into() }))
473 })
474 });
475 provider.insert_storage_for_hashing(alloc_storage)?;
476
477 trace!(target: "reth::cli", "Inserted storage hashes");
478
479 Ok(())
480}
481
482pub fn insert_genesis_header<Provider, Spec>(
484 provider: &Provider,
485 chain: &Spec,
486) -> ProviderResult<()>
487where
488 Provider: StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
489 + DBProvider<Tx: DbTxMut>,
490 Spec: EthChainSpec<Header = <Provider::Primitives as NodePrimitives>::BlockHeader>,
491{
492 let (header, block_hash) = (chain.genesis_header(), chain.genesis_hash());
493 let static_file_provider = provider.static_file_provider();
494
495 let genesis_block_number = header.number();
497
498 match static_file_provider.block_hash(genesis_block_number) {
499 Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, _)) => {
500 let difficulty = header.difficulty();
501
502 let mut writer = static_file_provider
506 .get_writer(genesis_block_number, StaticFileSegment::Headers)?;
507
508 if genesis_block_number > 0 {
511 writer
512 .user_header_mut()
513 .set_block_range(genesis_block_number, genesis_block_number);
514 writer.append_header_direct(header, difficulty, &block_hash)?;
515 } else {
516 writer.append_header(header, &block_hash)?;
518 }
519 }
520 Ok(Some(_)) => {}
521 Err(e) => return Err(e),
522 }
523
524 provider.tx_ref().put::<tables::HeaderNumbers>(block_hash, genesis_block_number)?;
525 provider.tx_ref().put::<tables::BlockBodyIndices>(genesis_block_number, Default::default())?;
526
527 Ok(())
528}
529
530pub fn init_from_state_dump<PF>(
537 mut reader: impl BufRead,
538 provider_factory: &PF,
539 etl_config: EtlConfig,
540) -> eyre::Result<B256>
541where
542 PF: DatabaseProviderFactory<
543 ProviderRW: StaticFileProviderFactory
544 + DBProvider<Tx: DbTxMut>
545 + BlockNumReader
546 + ChainSpecProvider
547 + StageCheckpointWriter
548 + HistoryWriter
549 + HeaderProvider
550 + HashingWriter
551 + TrieWriter
552 + StateWriter
553 + StorageSettingsCache
554 + RocksDBProviderFactory
555 + AsRef<PF::ProviderRW>,
556 >,
557{
558 if etl_config.file_size == 0 {
559 return Err(eyre::eyre!("ETL file size cannot be zero"))
560 }
561
562 let (block, hash, expected_state_root) = {
563 let provider_rw = provider_factory.database_provider_rw()?;
564 let block = provider_rw.last_block_number()?;
565 let hash = provider_rw
566 .block_hash(block)?
567 .ok_or_else(|| eyre::eyre!("Block hash not found for block {}", block))?;
568 let header = provider_rw
569 .header_by_number(block)?
570 .map(|h| SealedHeader::new(h, hash))
571 .ok_or_else(|| ProviderError::HeaderNotFound(block.into()))?;
572 let state_root = header.state_root();
573
574 debug!(target: "reth::cli",
575 block,
576 chain=%provider_rw.chain_spec().chain(),
577 "Initializing state at block"
578 );
579
580 (block, hash, state_root)
581 };
582
583 let dump_state_root = parse_state_root(&mut reader)?;
585 if expected_state_root != dump_state_root {
586 error!(target: "reth::cli",
587 ?dump_state_root,
588 ?expected_state_root,
589 "State root from state dump does not match state root in current header."
590 );
591 return Err(InitStorageError::StateRootMismatch(GotExpected {
592 got: dump_state_root,
593 expected: expected_state_root,
594 })
595 .into())
596 }
597
598 let collector = parse_accounts(&mut reader, etl_config)?;
600
601 dump_state(collector, provider_factory, block)?;
603
604 info!(target: "reth::cli", "All accounts written to database, starting state root computation (may take some time)");
605
606 {
608 let provider_rw = provider_factory.database_provider_rw()?;
609 provider_rw.tx_ref().clear::<tables::AccountsTrie>()?;
610 provider_rw.tx_ref().clear::<tables::StoragesTrie>()?;
611 provider_rw.commit()?;
612 }
613
614 let computed_state_root = compute_state_root_chunked(provider_factory)?;
616 if computed_state_root == expected_state_root {
617 info!(target: "reth::cli",
618 ?computed_state_root,
619 "Computed state root matches state root in state dump"
620 );
621 } else {
622 error!(target: "reth::cli",
623 ?computed_state_root,
624 ?expected_state_root,
625 "Computed state root does not match state root in state dump"
626 );
627
628 return Err(InitStorageError::StateRootMismatch(GotExpected {
629 got: computed_state_root,
630 expected: expected_state_root,
631 })
632 .into())
633 }
634
635 {
637 let provider_rw = provider_factory.database_provider_rw()?;
638 for stage in StageId::STATE_REQUIRED {
639 provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(block))?;
640 }
641 provider_rw.commit()?;
642 }
643
644 Ok(hash)
645}
646
647fn parse_state_root(reader: &mut impl BufRead) -> eyre::Result<B256> {
649 let mut line = String::new();
650 reader.read_line(&mut line)?;
651
652 let expected_state_root = serde_json::from_str::<StateRoot>(&line)?.root;
653 trace!(target: "reth::cli",
654 root=%expected_state_root,
655 "Read state root from file"
656 );
657 Ok(expected_state_root)
658}
659
660fn parse_accounts(
662 reader: impl BufRead,
663 etl_config: EtlConfig,
664) -> Result<Collector<Address, GenesisAccount>, eyre::Error> {
665 let mut collector = Collector::new(etl_config.file_size, etl_config.dir);
666 let mut parsed_accounts = 0usize;
667
668 let stream =
669 serde_json::Deserializer::from_reader(reader).into_iter::<GenesisAccountWithAddress>();
670 for account in stream {
671 let GenesisAccountWithAddress { genesis_account, address } = account?;
672 collector.insert(address, genesis_account)?;
673
674 parsed_accounts += 1;
675 if parsed_accounts.is_multiple_of(100_000) {
676 info!(target: "reth::cli", parsed_accounts, "Parsed accounts");
677 }
678 }
679
680 Ok(collector)
681}
682
683fn dump_state<PF>(
704 mut collector: Collector<Address, GenesisAccount>,
705 provider_factory: &PF,
706 block: u64,
707) -> Result<(), eyre::Error>
708where
709 PF: DatabaseProviderFactory<ProviderRW: DBProvider<Tx: DbTxMut>>,
710 PF::ProviderRW: StaticFileProviderFactory + StorageSettingsCache + RocksDBProviderFactory,
711{
712 let storage_settings = provider_factory.database_provider_rw()?.cached_storage_settings();
713 if storage_settings.storage_v2 {
714 return dump_state_v2(collector, provider_factory, block)
715 }
716
717 let accounts_len = collector.len();
718 let mut total_accounts: usize = 0;
719 let mut storage_units: usize = 0;
720
721 let history_list = IntegerList::new([block])?;
723
724 let mut seen_bytecodes: B256Set = B256Set::default();
726
727 let mut provider_rw = provider_factory.database_provider_rw()?;
728
729 for entry in collector.iter()? {
730 let (address_raw, account_raw) = entry?;
731 let (address, _) = Address::from_compact(address_raw.as_slice(), address_raw.len());
732 let (account, _) = GenesisAccount::from_compact(account_raw.as_slice(), account_raw.len());
733
734 let account_storage_len = account.storage.as_ref().map_or(0, |s| s.len());
735 let account_units = 1 + account_storage_len;
736
737 if storage_units > 0 && storage_units + account_units > STORAGE_COMMIT_THRESHOLD {
739 provider_rw.commit()?;
740 provider_rw = provider_factory.database_provider_rw()?;
741 info!(target: "reth::cli",
742 total_accounts,
743 accounts_len,
744 storage_units,
745 "Committed chunk"
746 );
747 storage_units = 0;
748 seen_bytecodes = B256Set::default();
749 }
750
751 write_account_to_db(
752 provider_rw.tx_ref(),
753 &address,
754 &account,
755 block,
756 &history_list,
757 &mut seen_bytecodes,
758 )?;
759
760 total_accounts += 1;
761 storage_units += account_units;
762
763 if total_accounts.is_multiple_of(100_000) {
764 info!(target: "reth::cli", total_accounts, accounts_len, "Writing accounts...");
765 }
766 }
767
768 provider_rw.commit()?;
770
771 info!(target: "reth::cli", total_accounts, "All accounts written to database");
772
773 Ok(())
774}
775
776fn dump_state_v2<PF>(
777 mut collector: Collector<Address, GenesisAccount>,
778 provider_factory: &PF,
779 block: u64,
780) -> Result<(), eyre::Error>
781where
782 PF: DatabaseProviderFactory<
783 ProviderRW: StaticFileProviderFactory
784 + DBProvider<Tx: DbTxMut>
785 + StorageSettingsCache
786 + RocksDBProviderFactory,
787 >,
788{
789 let accounts_len = collector.len();
790 let mut total_accounts: usize = 0;
791 let mut storage_units: usize = 0;
792
793 let history_list = IntegerList::new([block])?;
795
796 let mut seen_bytecodes: B256Set = B256Set::default();
798
799 let mut provider_rw = provider_factory.database_provider_rw()?;
800 let static_file_provider = provider_rw.static_file_provider();
801 let rocksdb_provider = provider_rw.rocksdb_provider();
802 let mut history_batch = rocksdb_provider.batch_with_auto_commit();
803 if snapshot_state_tables_empty(provider_rw.tx_ref())? {
804 reset_pre_snapshot_changeset_segment(
805 &static_file_provider,
806 StaticFileSegment::AccountChangeSets,
807 block,
808 )?;
809 reset_pre_snapshot_changeset_segment(
810 &static_file_provider,
811 StaticFileSegment::StorageChangeSets,
812 block,
813 )?;
814 }
815
816 {
817 let mut account_changeset_writer =
818 static_file_provider.get_writer(block, StaticFileSegment::AccountChangeSets)?;
819 let mut storage_changeset_writer =
820 static_file_provider.get_writer(block, StaticFileSegment::StorageChangeSets)?;
821 prepare_account_changeset_writer(&mut account_changeset_writer, block)?;
822 prepare_storage_changeset_writer(&mut storage_changeset_writer, block)?;
823
824 for entry in collector.iter()? {
825 let (address_raw, account_raw) = entry?;
826 let (address, _) = Address::from_compact(address_raw.as_slice(), address_raw.len());
827 let (account, _) =
828 GenesisAccount::from_compact(account_raw.as_slice(), account_raw.len());
829
830 let account_storage_len = account.storage.as_ref().map_or(0, |s| s.len());
831 let account_units = 1 + account_storage_len;
832
833 if storage_units > 0 && storage_units + account_units > STORAGE_COMMIT_THRESHOLD {
835 history_batch.commit()?;
836 commit_mdbx_only(provider_rw)?;
837 provider_rw = provider_factory.database_provider_rw()?;
838 history_batch = rocksdb_provider.batch_with_auto_commit();
839 info!(target: "reth::cli",
840 total_accounts,
841 accounts_len,
842 storage_units,
843 "Committed chunk"
844 );
845 storage_units = 0;
846 seen_bytecodes = B256Set::default();
847 }
848
849 write_account_to_db_v2(
850 provider_rw.tx_ref(),
851 (&mut account_changeset_writer, &mut storage_changeset_writer),
852 &mut history_batch,
853 &address,
854 &account,
855 block,
856 &history_list,
857 &mut seen_bytecodes,
858 )?;
859
860 total_accounts += 1;
861 storage_units += account_units;
862
863 if total_accounts.is_multiple_of(100_000) {
864 info!(target: "reth::cli", total_accounts, accounts_len, "Writing accounts...");
865 }
866 }
867 }
868
869 history_batch.commit()?;
870 commit_mdbx_only(provider_rw)?;
871 static_file_provider.finalize()?;
872
873 info!(target: "reth::cli", total_accounts, "All accounts written to database");
874
875 Ok(())
876}
877
878fn prepare_account_changeset_writer<N: NodePrimitives>(
879 writer: &mut reth_provider::providers::StaticFileProviderRWRefMut<'_, N>,
880 block: u64,
881) -> ProviderResult<()> {
882 let next_block = writer.next_block_number();
883 if next_block < block {
884 info!(
885 target: "reth::cli",
886 from_block = next_block,
887 to_block = block - 1,
888 "Padding empty account changesets before state import"
889 );
890 for empty_block in next_block..block {
891 writer.append_account_changeset(Vec::new(), empty_block)?;
892 if empty_block > next_block && empty_block.is_multiple_of(1_000_000) {
893 info!(
894 target: "reth::cli",
895 padded_to_block = empty_block,
896 "Padded empty account changesets"
897 );
898 }
899 }
900 }
901
902 writer.begin_account_changeset(block)
903}
904
905fn prepare_storage_changeset_writer<N: NodePrimitives>(
906 writer: &mut reth_provider::providers::StaticFileProviderRWRefMut<'_, N>,
907 block: u64,
908) -> ProviderResult<()> {
909 let next_block = writer.next_block_number();
910 if next_block < block {
911 info!(
912 target: "reth::cli",
913 from_block = next_block,
914 to_block = block - 1,
915 "Padding empty storage changesets before state import"
916 );
917 for empty_block in next_block..block {
918 writer.append_storage_changeset(Vec::new(), empty_block)?;
919 if empty_block > next_block && empty_block.is_multiple_of(1_000_000) {
920 info!(
921 target: "reth::cli",
922 padded_to_block = empty_block,
923 "Padded empty storage changesets"
924 );
925 }
926 }
927 }
928
929 writer.begin_storage_changeset(block)
930}
931
932fn snapshot_state_tables_empty<TX: reth_db_api::transaction::DbTx>(
933 tx: &TX,
934) -> ProviderResult<bool> {
935 Ok(tx.entries::<tables::PlainAccountState>()? == 0 &&
936 tx.entries::<tables::PlainStorageState>()? == 0 &&
937 tx.entries::<tables::HashedAccounts>()? == 0 &&
938 tx.entries::<tables::HashedStorages>()? == 0 &&
939 tx.entries::<tables::AccountChangeSets>()? == 0 &&
940 tx.entries::<tables::StorageChangeSets>()? == 0 &&
941 tx.entries::<tables::Bytecodes>()? == 0)
942}
943
944fn reset_pre_snapshot_changeset_segment<N: NodePrimitives>(
945 static_file_provider: &reth_provider::providers::StaticFileProvider<N>,
946 segment: StaticFileSegment,
947 block: u64,
948) -> ProviderResult<()> {
949 if block == 0 {
950 return Ok(())
951 }
952
953 let Some(highest_block) = static_file_provider.get_highest_static_file_block(segment) else {
954 return Ok(())
955 };
956
957 if highest_block >= block {
958 return Ok(())
959 }
960
961 let file_start = static_file_provider.find_fixed_range(segment, block).start();
962 info!(
963 target: "reth::cli",
964 ?segment,
965 highest_block,
966 import_block = block,
967 file_start,
968 "Resetting pre-snapshot changeset static files before state import"
969 );
970 static_file_provider.delete_segment(segment)?;
971
972 Ok(())
973}
974
975fn commit_mdbx_only<Provider>(provider: Provider) -> ProviderResult<()>
976where
977 Provider: DBProvider<Tx: DbTxMut>,
978{
979 reth_db_api::transaction::DbTx::commit(provider.into_tx()).map_err(ProviderError::from)
980}
981
982fn replace_storage_slot<T, C>(
990 cursor: &mut C,
991 key: T::Key,
992 entry: StorageEntry,
993) -> Result<Option<U256>, DatabaseError>
994where
995 T: DupSort<SubKey = B256, Value = StorageEntry>,
996 T::Key: Clone,
997 C: DbDupCursorRO<T> + DbCursorRW<T>,
998{
999 let previous = cursor
1000 .seek_by_key_subkey(key.clone(), entry.key)?
1001 .filter(|existing| existing.key == entry.key)
1002 .map(|existing| existing.value);
1003 if previous.is_some() {
1004 cursor.delete_current()?;
1005 }
1006 if !entry.value.is_zero() {
1007 cursor.upsert(key, &entry)?;
1008 }
1009 Ok(previous)
1010}
1011
1012fn history_shard_with_block(
1019 shard: Option<IntegerList>,
1020 block: u64,
1021) -> Result<IntegerList, IntegerListError> {
1022 let mut shard = shard.unwrap_or_else(IntegerList::empty);
1023 if shard.max().is_none_or(|last| last < block) {
1024 shard.push(block)?;
1025 }
1026 Ok(shard)
1027}
1028
1029#[allow(clippy::clone_on_copy)]
1037fn write_account_to_db<TX: DbTx + DbTxMut>(
1038 tx: &TX,
1039 address: &Address,
1040 genesis_account: &GenesisAccount,
1041 block: u64,
1042 history_list: &IntegerList,
1043 seen_bytecodes: &mut B256Set,
1044) -> Result<(), eyre::Error> {
1045 let bytecode_hash = if let Some(code) = &genesis_account.code {
1046 let bytecode = Bytecode::new_raw_checked(code.clone())
1047 .map_err(|e| eyre::eyre!("Invalid bytecode for {address}: {e}"))?;
1048 let hash = bytecode.hash_slow();
1049 if seen_bytecodes.insert(hash) {
1050 tx.put::<tables::Bytecodes>(hash, bytecode)?;
1051 }
1052 Some(hash)
1053 } else {
1054 None
1055 };
1056
1057 let account = Account { bytecode_hash, ..Account::from(genesis_account) };
1058
1059 let hashed_address = keccak256(address);
1060 let previous_account = tx.get::<tables::PlainAccountState>(*address)?;
1061 let replaces_account = previous_account.is_some();
1062
1063 tx.put::<tables::PlainAccountState>(*address, account.clone())?;
1065
1066 tx.put::<tables::HashedAccounts>(hashed_address, account)?;
1068
1069 let mut acct_cs_cursor = tx.cursor_dup_write::<tables::AccountChangeSets>()?;
1071 acct_cs_cursor
1072 .append_dup(block, AccountBeforeTx { address: *address, info: previous_account })?;
1073
1074 let history_key = ShardedKey::new(*address, u64::MAX);
1076 let account_history = if replaces_account {
1077 history_shard_with_block(tx.get::<tables::AccountsHistory>(history_key.clone())?, block)?
1078 } else {
1079 history_list.clone()
1080 };
1081 tx.put::<tables::AccountsHistory>(history_key, account_history)?;
1082
1083 if let Some(storage) = &genesis_account.storage {
1086 let mut hashed_storage_cursor = tx.cursor_dup_write::<tables::HashedStorages>()?;
1087 let mut plain_storage_cursor = tx.cursor_dup_write::<tables::PlainStorageState>()?;
1088 let mut storage_cs_cursor = tx.cursor_dup_write::<tables::StorageChangeSets>()?;
1089
1090 for (&key, &value) in storage {
1091 let value_u256 = U256::from_be_bytes(value.0);
1092
1093 let previous_value = replace_storage_slot(
1094 &mut plain_storage_cursor,
1095 *address,
1096 StorageEntry { key, value: value_u256 },
1097 )?;
1098 replace_storage_slot(
1099 &mut hashed_storage_cursor,
1100 hashed_address,
1101 StorageEntry { key: keccak256(key), value: value_u256 },
1102 )?;
1103
1104 storage_cs_cursor.append_dup(
1106 BlockNumberAddress((block, *address)),
1107 StorageEntry { key, value: previous_value.unwrap_or_default() },
1108 )?;
1109
1110 let history_key = StorageShardedKey::new(*address, key, u64::MAX);
1112 let storage_history = if previous_value.is_some() {
1113 history_shard_with_block(
1114 tx.get::<tables::StoragesHistory>(history_key.clone())?,
1115 block,
1116 )?
1117 } else {
1118 history_list.clone()
1119 };
1120 tx.put::<tables::StoragesHistory>(history_key, storage_history)?;
1121 }
1122 }
1123
1124 Ok(())
1125}
1126
1127#[expect(clippy::too_many_arguments)]
1133fn write_account_to_db_v2<TX, N>(
1134 tx: &TX,
1135 changeset_writers: (
1136 &mut reth_provider::providers::StaticFileProviderRWRefMut<'_, N>,
1137 &mut reth_provider::providers::StaticFileProviderRWRefMut<'_, N>,
1138 ),
1139 history_batch: &mut reth_provider::providers::RocksDBBatch<'_>,
1140 address: &Address,
1141 genesis_account: &GenesisAccount,
1142 block: u64,
1143 history_list: &IntegerList,
1144 seen_bytecodes: &mut B256Set,
1145) -> Result<(), eyre::Error>
1146where
1147 TX: DbTx + DbTxMut,
1148 N: NodePrimitives,
1149{
1150 let bytecode_hash = if let Some(code) = &genesis_account.code {
1151 let bytecode = Bytecode::new_raw_checked(code.clone())
1152 .map_err(|e| eyre::eyre!("Invalid bytecode for {address}: {e}"))?;
1153 let hash = bytecode.hash_slow();
1154 if seen_bytecodes.insert(hash) {
1155 tx.put::<tables::Bytecodes>(hash, bytecode)?;
1156 }
1157 Some(hash)
1158 } else {
1159 None
1160 };
1161
1162 let account = Account { bytecode_hash, ..Account::from(genesis_account) };
1163
1164 let hashed_address = keccak256(address);
1165 let (account_changeset_writer, storage_changeset_writer) = changeset_writers;
1166
1167 let previous_account = tx.get::<tables::HashedAccounts>(hashed_address)?;
1168 let replaces_account = previous_account.is_some();
1169 tx.put::<tables::HashedAccounts>(hashed_address, account)?;
1170 account_changeset_writer.append_account_changeset_entry(AccountBeforeTx {
1171 address: *address,
1172 info: previous_account,
1173 })?;
1174 let history_key = ShardedKey::new(*address, u64::MAX);
1175 if replaces_account {
1176 let account_history = history_shard_with_block(
1177 history_batch.get::<tables::AccountsHistory>(history_key.clone())?,
1178 block,
1179 )?;
1180 history_batch.put::<tables::AccountsHistory>(history_key, &account_history)?;
1181 } else {
1182 history_batch.put::<tables::AccountsHistory>(history_key, history_list)?;
1183 }
1184
1185 if let Some(storage) = &genesis_account.storage {
1186 let mut hashed_storage_cursor = tx.cursor_dup_write::<tables::HashedStorages>()?;
1188
1189 for (&key, &value) in storage {
1190 let value_u256 = U256::from_be_bytes(value.0);
1191
1192 let previous_value = replace_storage_slot(
1193 &mut hashed_storage_cursor,
1194 hashed_address,
1195 StorageEntry { key: keccak256(key), value: value_u256 },
1196 )?;
1197
1198 storage_changeset_writer.append_storage_changeset_entry(
1199 reth_db_api::models::StorageBeforeTx {
1200 address: *address,
1201 key,
1202 value: previous_value.unwrap_or_default(),
1203 },
1204 )?;
1205
1206 let history_key = StorageShardedKey::new(*address, key, u64::MAX);
1207 if previous_value.is_some() {
1208 let storage_history = history_shard_with_block(
1209 history_batch.get::<tables::StoragesHistory>(history_key.clone())?,
1210 block,
1211 )?;
1212 history_batch.put::<tables::StoragesHistory>(history_key, &storage_history)?;
1213 } else {
1214 history_batch.put::<tables::StoragesHistory>(history_key, history_list)?;
1215 }
1216 }
1217 }
1218
1219 Ok(())
1220}
1221
1222fn compute_state_root<Provider>(
1225 provider: &Provider,
1226 prefix_sets: Option<TriePrefixSets>,
1227) -> Result<B256, InitStorageError>
1228where
1229 Provider: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
1230{
1231 reth_trie_db::with_adapter!(provider, |A| {
1232 compute_state_root_inner::<_, A>(provider, prefix_sets)
1233 })
1234}
1235
1236fn compute_state_root_inner<Provider, A>(
1237 provider: &Provider,
1238 prefix_sets: Option<TriePrefixSets>,
1239) -> Result<B256, InitStorageError>
1240where
1241 Provider: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
1242 A: reth_trie_db::TrieTableAdapter,
1243{
1244 trace!(target: "reth::cli", "Computing state root");
1245
1246 let tx = provider.tx_ref();
1247 let mut intermediate_state: Option<IntermediateStateRootState> = None;
1248 let mut total_flushed_updates = 0;
1249
1250 loop {
1251 let mut state_root =
1252 DbStateRoot::<_, A>::from_tx(tx).with_intermediate_state(intermediate_state);
1253
1254 if let Some(sets) = prefix_sets.clone() {
1255 state_root = state_root.with_prefix_sets(sets);
1256 }
1257
1258 match state_root.root_with_progress()? {
1259 StateRootProgress::Progress(state, _, updates) => {
1260 let updated_len = provider.write_trie_updates(updates)?;
1261 total_flushed_updates += updated_len;
1262
1263 trace!(target: "reth::cli",
1264 last_account_key = %state.account_root_state.last_hashed_key,
1265 updated_len,
1266 total_flushed_updates,
1267 "Flushing trie updates"
1268 );
1269
1270 intermediate_state = Some(*state);
1271
1272 if total_flushed_updates.is_multiple_of(SOFT_LIMIT_COUNT_FLUSHED_UPDATES) {
1273 info!(target: "reth::cli",
1274 total_flushed_updates,
1275 "Flushing trie updates"
1276 );
1277 }
1278 }
1279 StateRootProgress::Complete(root, _, updates) => {
1280 let updated_len = provider.write_trie_updates(updates)?;
1281 total_flushed_updates += updated_len;
1282
1283 trace!(target: "reth::cli",
1284 %root,
1285 updated_len,
1286 total_flushed_updates,
1287 "State root has been computed"
1288 );
1289
1290 return Ok(root)
1291 }
1292 }
1293 }
1294}
1295
1296fn compute_state_root_chunked<PF>(provider_factory: &PF) -> Result<B256, InitStorageError>
1301where
1302 PF: DatabaseProviderFactory<
1303 ProviderRW: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
1304 >,
1305{
1306 let provider_rw = provider_factory.database_provider_rw().map_err(provider_db_err)?;
1307
1308 reth_trie_db::with_adapter!(&provider_rw, |A| {
1309 drop(provider_rw);
1310 compute_state_root_chunked_inner::<PF, A>(provider_factory)
1311 })
1312}
1313
1314fn compute_state_root_chunked_inner<PF, A>(provider_factory: &PF) -> Result<B256, InitStorageError>
1315where
1316 PF: DatabaseProviderFactory<
1317 ProviderRW: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
1318 >,
1319 A: reth_trie_db::TrieTableAdapter,
1320{
1321 trace!(target: "reth::cli", "Computing state root");
1322
1323 let mut intermediate_state: Option<IntermediateStateRootState> = None;
1324 let mut total_flushed_updates = 0;
1325
1326 loop {
1327 let provider_rw = provider_factory.database_provider_rw().map_err(provider_db_err)?;
1328 let tx = provider_rw.tx_ref();
1329
1330 let state_root = DbStateRoot::<_, A>::from_tx(tx)
1331 .with_intermediate_state(intermediate_state.take())
1332 .with_threshold(STATE_ROOT_COMMIT_THRESHOLD);
1333
1334 match state_root.root_with_progress()? {
1335 StateRootProgress::Progress(state, _, updates) => {
1336 let updated_len = provider_rw.write_trie_updates(updates)?;
1337 total_flushed_updates += updated_len;
1338
1339 info!(target: "reth::cli",
1340 last_account_key = %state.account_root_state.last_hashed_key,
1341 updated_len,
1342 total_flushed_updates,
1343 "Flushing trie updates (committing to free memory)"
1344 );
1345
1346 intermediate_state = Some(*state);
1347 provider_rw.commit().map_err(provider_db_err)?;
1348 }
1349 StateRootProgress::Complete(root, _, updates) => {
1350 let updated_len = provider_rw.write_trie_updates(updates)?;
1351 total_flushed_updates += updated_len;
1352
1353 info!(target: "reth::cli",
1354 %root,
1355 updated_len,
1356 total_flushed_updates,
1357 "State root computation complete"
1358 );
1359
1360 provider_rw.commit().map_err(provider_db_err)?;
1361 return Ok(root)
1362 }
1363 }
1364 }
1365}
1366
1367fn provider_db_err(e: impl std::fmt::Display) -> InitStorageError {
1369 InitStorageError::from(StateRootError::Database(DatabaseError::Other(e.to_string())))
1370}
1371
1372#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1374struct StateRoot {
1375 root: B256,
1376}
1377
1378#[derive(Debug, Serialize, Deserialize)]
1381struct GenesisAccountWithAddress {
1382 #[serde(flatten)]
1384 genesis_account: GenesisAccount,
1385 address: Address,
1387}
1388
1389#[cfg(test)]
1390mod tests {
1391 use super::*;
1392 use alloy_consensus::constants::{
1393 HOLESKY_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH,
1394 };
1395 use alloy_genesis::Genesis;
1396 use reth_chainspec::{Chain, ChainSpec, HOLESKY, MAINNET, SEPOLIA};
1397 use reth_db::DatabaseEnv;
1398 use reth_db_api::{
1399 cursor::DbCursorRO,
1400 models::{storage_sharded_key::StorageShardedKey, IntegerList, ShardedKey},
1401 table::{Table, TableRow},
1402 transaction::DbTx,
1403 Database,
1404 };
1405 use reth_provider::{
1406 test_utils::{create_test_provider_factory_with_chain_spec, MockNodeTypesWithDB},
1407 ProviderFactory, RocksDBProviderFactory,
1408 };
1409 use std::{collections::BTreeMap, sync::Arc};
1410
1411 fn collect_table_entries<DB, T>(
1412 tx: &<DB as Database>::TX,
1413 ) -> Result<Vec<TableRow<T>>, InitStorageError>
1414 where
1415 DB: Database,
1416 T: Table,
1417 {
1418 Ok(tx.cursor_read::<T>()?.walk_range(..)?.collect::<Result<Vec<_>, _>>()?)
1419 }
1420
1421 #[test]
1422 fn parse_accounts_streams_jsonl_accounts() {
1423 let input = br#"{"address":"0x0000000000000000000000000000000000000002","balance":"0x2"}
1424{"address":"0x0000000000000000000000000000000000000001","balance":"0x1"}
1425"#;
1426
1427 let mut collector =
1428 parse_accounts(&input[..], EtlConfig::new(None, 128)).expect("parse succeeds");
1429
1430 let accounts = collector
1431 .iter()
1432 .unwrap()
1433 .map(|entry| {
1434 let (address_raw, account_raw) = entry.unwrap();
1435 let (address, _) = Address::from_compact(address_raw.as_slice(), address_raw.len());
1436 let (account, _) =
1437 GenesisAccount::from_compact(account_raw.as_slice(), account_raw.len());
1438 (address, account.balance)
1439 })
1440 .collect::<Vec<_>>();
1441
1442 assert_eq!(
1443 accounts,
1444 vec![
1445 (Address::with_last_byte(1), U256::ONE),
1446 (Address::with_last_byte(2), U256::from(2))
1447 ]
1448 );
1449 }
1450
1451 #[test]
1452 fn dump_state_uses_storage_v2_destinations() {
1453 let storage_key = B256::with_last_byte(3);
1454 let input = br#"{"address":"0x0000000000000000000000000000000000000001","balance":"0x1"}
1455{"address":"0x0000000000000000000000000000000000000002","balance":"0x0","storage":{"0x0000000000000000000000000000000000000000000000000000000000000003":"0x0000000000000000000000000000000000000000000000000000000000000004"}}
1456"#;
1457
1458 let collector = parse_accounts(&input[..], EtlConfig::new(None, 128)).unwrap();
1459 let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
1460 factory.set_storage_settings_cache(StorageSettings::v2());
1461 let block = 10;
1462
1463 dump_state(collector, &factory, block).unwrap();
1464
1465 let provider = factory.provider().unwrap();
1466 let tx = provider.tx_ref();
1467 assert_eq!(tx.entries::<tables::PlainAccountState>().unwrap(), 0);
1468 assert_eq!(tx.entries::<tables::PlainStorageState>().unwrap(), 0);
1469 assert_eq!(tx.entries::<tables::AccountChangeSets>().unwrap(), 0);
1470 assert_eq!(tx.entries::<tables::StorageChangeSets>().unwrap(), 0);
1471 assert_eq!(tx.entries::<tables::HashedAccounts>().unwrap(), 2);
1472 assert_eq!(tx.entries::<tables::HashedStorages>().unwrap(), 1);
1473
1474 let address_with_balance = Address::with_last_byte(1);
1475 let address_with_storage = Address::with_last_byte(2);
1476 assert_eq!(
1477 reth_provider::ChangeSetReader::account_block_changeset(&provider, block).unwrap(),
1478 vec![
1479 AccountBeforeTx { address: address_with_balance, info: None },
1480 AccountBeforeTx { address: address_with_storage, info: None }
1481 ]
1482 );
1483 assert_eq!(
1484 reth_provider::StorageChangeSetReader::storage_changeset(&provider, block).unwrap(),
1485 vec![(
1486 BlockNumberAddress((block, address_with_storage)),
1487 StorageEntry { key: storage_key, value: U256::ZERO }
1488 )]
1489 );
1490
1491 let rocksdb = factory.rocksdb_provider();
1492 let accounts = rocksdb
1493 .iter::<tables::AccountsHistory>()
1494 .unwrap()
1495 .collect::<Result<Vec<_>, _>>()
1496 .unwrap();
1497 let storages = rocksdb
1498 .iter::<tables::StoragesHistory>()
1499 .unwrap()
1500 .collect::<Result<Vec<_>, _>>()
1501 .unwrap();
1502
1503 assert_eq!(
1504 accounts,
1505 vec![
1506 (
1507 ShardedKey::new(address_with_balance, u64::MAX),
1508 IntegerList::new([block]).unwrap()
1509 ),
1510 (
1511 ShardedKey::new(address_with_storage, u64::MAX),
1512 IntegerList::new([block]).unwrap()
1513 )
1514 ]
1515 );
1516 assert_eq!(
1517 storages,
1518 vec![(
1519 StorageShardedKey::new(address_with_storage, storage_key, u64::MAX),
1520 IntegerList::new([block]).unwrap()
1521 )]
1522 );
1523 }
1524
1525 #[test]
1526 fn dump_state_v2_resets_presnapshot_changeset_static_files() {
1527 let storage_key = B256::with_last_byte(3);
1528 let input = br#"{"address":"0x0000000000000000000000000000000000000002","balance":"0x0","storage":{"0x0000000000000000000000000000000000000000000000000000000000000003":"0x0000000000000000000000000000000000000000000000000000000000000004"}}
1529"#;
1530
1531 let collector = parse_accounts(&input[..], EtlConfig::new(None, 128)).unwrap();
1532 let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
1533 factory.set_storage_settings_cache(StorageSettings::v2());
1534 let static_files = factory.static_file_provider();
1535
1536 {
1537 let mut writer =
1538 static_files.get_writer(0, StaticFileSegment::AccountChangeSets).unwrap();
1539 writer.append_account_changeset(Vec::new(), 0).unwrap();
1540 }
1541 {
1542 let mut writer =
1543 static_files.get_writer(0, StaticFileSegment::StorageChangeSets).unwrap();
1544 writer.append_storage_changeset(Vec::new(), 0).unwrap();
1545 }
1546 static_files.commit().unwrap();
1547
1548 let block = 500_010;
1549 dump_state(collector, &factory, block).unwrap();
1550 static_files.initialize_index().unwrap();
1551
1552 let provider = factory.provider().unwrap();
1553 let address = Address::with_last_byte(2);
1554 assert!(reth_provider::ChangeSetReader::account_block_changeset(&provider, 5)
1555 .unwrap()
1556 .is_empty());
1557 assert!(reth_provider::StorageChangeSetReader::storage_changeset(&provider, 5)
1558 .unwrap()
1559 .is_empty());
1560
1561 let account_file_start =
1562 static_files.find_fixed_range(StaticFileSegment::AccountChangeSets, block).start();
1563 let storage_file_start =
1564 static_files.find_fixed_range(StaticFileSegment::StorageChangeSets, block).start();
1565 assert_eq!(account_file_start, 500_000);
1566 assert_eq!(storage_file_start, 500_000);
1567 assert!(reth_provider::ChangeSetReader::account_block_changeset(
1568 &provider,
1569 account_file_start
1570 )
1571 .unwrap()
1572 .is_empty());
1573 assert!(reth_provider::StorageChangeSetReader::storage_changeset(
1574 &provider,
1575 storage_file_start
1576 )
1577 .unwrap()
1578 .is_empty());
1579
1580 assert_eq!(
1581 reth_provider::ChangeSetReader::account_block_changeset(&provider, block).unwrap(),
1582 vec![AccountBeforeTx { address, info: None }]
1583 );
1584 assert_eq!(
1585 reth_provider::StorageChangeSetReader::storage_changeset(&provider, block).unwrap(),
1586 vec![(
1587 BlockNumberAddress((block, address)),
1588 StorageEntry { key: storage_key, value: U256::ZERO }
1589 )]
1590 );
1591
1592 let account_offsets = static_files
1593 .get_segment_provider_for_block(StaticFileSegment::AccountChangeSets, block, None)
1594 .unwrap()
1595 .read_changeset_offsets()
1596 .unwrap()
1597 .unwrap();
1598 let storage_offsets = static_files
1599 .get_segment_provider_for_block(StaticFileSegment::StorageChangeSets, block, None)
1600 .unwrap()
1601 .read_changeset_offsets()
1602 .unwrap()
1603 .unwrap();
1604 assert_eq!(account_offsets.len() as u64, block - account_file_start + 1);
1605 assert_eq!(storage_offsets.len() as u64, block - storage_file_start + 1);
1606 }
1607
1608 fn assert_dump_state_overlays_genesis_state(settings: StorageSettings) {
1614 let dump_only = Address::with_last_byte(1);
1615 let overlapping = Address::with_last_byte(2);
1616 let no_dump_storage = Address::with_last_byte(3);
1617 let genesis_only = Address::with_last_byte(4);
1618 let slot = B256::with_last_byte;
1619 let val = B256::with_last_byte;
1620
1621 let genesis_alloc = BTreeMap::from([
1622 (
1623 overlapping,
1624 GenesisAccount {
1625 balance: U256::from(1),
1626 storage: Some(BTreeMap::from([
1627 (slot(1), val(0x11)),
1629 (slot(2), val(0x12)),
1631 (slot(4), val(0x14)),
1633 (slot(6), val(0x16)),
1635 ])),
1636 ..Default::default()
1637 },
1638 ),
1639 (
1640 no_dump_storage,
1641 GenesisAccount {
1642 storage: Some(BTreeMap::from([(slot(7), val(0x17))])),
1643 ..Default::default()
1644 },
1645 ),
1646 (
1647 genesis_only,
1648 GenesisAccount {
1649 balance: U256::from(9),
1650 storage: Some(BTreeMap::from([(slot(8), val(0x18))])),
1651 ..Default::default()
1652 },
1653 ),
1654 ]);
1655 let dump_alloc = BTreeMap::from([
1656 (
1657 dump_only,
1658 GenesisAccount {
1659 balance: U256::from(5),
1660 storage: Some(BTreeMap::from([(slot(5), val(0x25))])),
1661 ..Default::default()
1662 },
1663 ),
1664 (
1665 overlapping,
1666 GenesisAccount {
1667 balance: U256::from(2),
1668 storage: Some(BTreeMap::from([
1669 (slot(1), val(0x21)),
1670 (slot(3), val(0x23)),
1671 (slot(4), val(0x14)),
1672 (slot(6), B256::ZERO),
1673 ])),
1674 ..Default::default()
1675 },
1676 ),
1677 (no_dump_storage, GenesisAccount { balance: U256::from(3), ..Default::default() }),
1679 ]);
1680
1681 let mut expected_alloc = genesis_alloc.clone();
1683 for (address, account) in &dump_alloc {
1684 let mut storage =
1685 genesis_alloc.get(address).and_then(|a| a.storage.clone()).unwrap_or_default();
1686 for (key, value) in account.storage.iter().flatten() {
1687 if value.is_zero() {
1688 storage.remove(key);
1689 } else {
1690 storage.insert(*key, *value);
1691 }
1692 }
1693 expected_alloc.insert(
1694 *address,
1695 GenesisAccount {
1696 storage: (!storage.is_empty()).then_some(storage),
1697 ..account.clone()
1698 },
1699 );
1700 }
1701 let expected_state_root = reth_trie::root::state_root_ref_unhashed(&expected_alloc);
1702
1703 let chain_spec = Arc::new(ChainSpec {
1704 chain: Chain::from_id(1),
1705 genesis: Genesis { alloc: genesis_alloc, ..Default::default() },
1706 hardforks: Default::default(),
1707 paris_block_and_final_difficulty: None,
1708 deposit_contract: None,
1709 ..Default::default()
1710 });
1711 let factory = create_test_provider_factory_with_chain_spec(chain_spec);
1712 init_genesis_with_settings(&factory, settings).unwrap();
1713 factory.set_storage_settings_cache(settings);
1714
1715 let input = dump_alloc
1716 .iter()
1717 .map(|(address, account)| {
1718 serde_json::to_string(&GenesisAccountWithAddress {
1719 genesis_account: account.clone(),
1720 address: *address,
1721 })
1722 .unwrap()
1723 })
1724 .collect::<Vec<_>>()
1725 .join("\n");
1726 let collector = parse_accounts(input.as_bytes(), EtlConfig::new(None, 128)).unwrap();
1727 dump_state(collector, &factory, 10).unwrap();
1728
1729 let storage_of = |address: &Address| -> Vec<(B256, U256)> {
1730 expected_alloc[address]
1731 .storage
1732 .iter()
1733 .flatten()
1734 .map(|(key, value)| (*key, U256::from_be_bytes(value.0)))
1735 .collect()
1736 };
1737
1738 {
1739 let provider = factory.provider().unwrap();
1740 let tx = provider.tx_ref();
1741
1742 let hashed_storage =
1743 collect_table_entries::<DatabaseEnv, tables::HashedStorages>(tx).unwrap();
1744 for address in expected_alloc.keys() {
1745 let mut expected = storage_of(address)
1746 .into_iter()
1747 .map(|(key, value)| {
1748 (keccak256(address), StorageEntry { key: keccak256(key), value })
1749 })
1750 .collect::<Vec<_>>();
1751 expected.sort_by_key(|(_, entry)| entry.key);
1752 let actual = hashed_storage
1753 .iter()
1754 .filter(|(hashed_address, _)| *hashed_address == keccak256(address))
1755 .copied()
1756 .collect::<Vec<_>>();
1757 assert_eq!(actual, expected, "hashed storage of {address}");
1758 }
1759 let expected_slots = expected_alloc.keys().map(|a| storage_of(a).len()).sum::<usize>();
1760 assert_eq!(hashed_storage.len(), expected_slots);
1761
1762 let hashed_accounts =
1763 collect_table_entries::<DatabaseEnv, tables::HashedAccounts>(tx).unwrap();
1764 assert_eq!(hashed_accounts.len(), expected_alloc.len());
1765
1766 let plain_storage =
1767 collect_table_entries::<DatabaseEnv, tables::PlainStorageState>(tx).unwrap();
1768 if settings.storage_v2 {
1769 assert!(plain_storage.is_empty());
1770 } else {
1771 let expected = expected_alloc
1772 .keys()
1773 .flat_map(|address| {
1774 storage_of(address)
1775 .into_iter()
1776 .map(|(key, value)| (*address, StorageEntry { key, value }))
1777 })
1778 .collect::<Vec<_>>();
1779 assert_eq!(plain_storage, expected);
1780 }
1781 }
1782
1783 {
1786 let provider = factory.provider().unwrap();
1787 let genesis_account =
1788 |balance| Some(Account { balance: U256::from(balance), ..Default::default() });
1789 assert_eq!(
1790 reth_provider::ChangeSetReader::account_block_changeset(&provider, 10).unwrap(),
1791 vec![
1792 AccountBeforeTx { address: dump_only, info: None },
1793 AccountBeforeTx { address: overlapping, info: genesis_account(1) },
1794 AccountBeforeTx { address: no_dump_storage, info: genesis_account(0) },
1795 ]
1796 );
1797 let pre_image = |address, key, value: B256| {
1798 (
1799 BlockNumberAddress((10, address)),
1800 StorageEntry { key: slot(key), value: U256::from_be_bytes(value.0) },
1801 )
1802 };
1803 assert_eq!(
1804 reth_provider::StorageChangeSetReader::storage_changeset(&provider, 10).unwrap(),
1805 vec![
1806 pre_image(dump_only, 5, B256::ZERO),
1807 pre_image(overlapping, 1, val(0x11)),
1808 pre_image(overlapping, 3, B256::ZERO),
1809 pre_image(overlapping, 4, val(0x14)),
1810 pre_image(overlapping, 6, val(0x16)),
1811 ]
1812 );
1813
1814 let (accounts_history, storages_history) = if settings.storage_v2 {
1815 let rocksdb = factory.rocksdb_provider();
1816 (
1817 rocksdb
1818 .iter::<tables::AccountsHistory>()
1819 .unwrap()
1820 .collect::<Result<Vec<_>, _>>()
1821 .unwrap(),
1822 rocksdb
1823 .iter::<tables::StoragesHistory>()
1824 .unwrap()
1825 .collect::<Result<Vec<_>, _>>()
1826 .unwrap(),
1827 )
1828 } else {
1829 let tx = provider.tx_ref();
1830 (
1831 collect_table_entries::<DatabaseEnv, tables::AccountsHistory>(tx).unwrap(),
1832 collect_table_entries::<DatabaseEnv, tables::StoragesHistory>(tx).unwrap(),
1833 )
1834 };
1835 let blocks = |blocks: &[u64]| IntegerList::new(blocks.iter().copied()).unwrap();
1836 assert_eq!(
1837 accounts_history,
1838 vec![
1839 (ShardedKey::new(dump_only, u64::MAX), blocks(&[10])),
1840 (ShardedKey::new(overlapping, u64::MAX), blocks(&[0, 10])),
1841 (ShardedKey::new(no_dump_storage, u64::MAX), blocks(&[0, 10])),
1842 (ShardedKey::new(genesis_only, u64::MAX), blocks(&[0])),
1843 ]
1844 );
1845 let slot_history = |address, key, list: &[u64]| {
1846 (StorageShardedKey::new(address, slot(key), u64::MAX), blocks(list))
1847 };
1848 assert_eq!(
1849 storages_history,
1850 vec![
1851 slot_history(dump_only, 5, &[10]),
1852 slot_history(overlapping, 1, &[0, 10]),
1853 slot_history(overlapping, 2, &[0]),
1854 slot_history(overlapping, 3, &[10]),
1855 slot_history(overlapping, 4, &[0, 10]),
1856 slot_history(overlapping, 6, &[0, 10]),
1857 slot_history(no_dump_storage, 7, &[0]),
1858 slot_history(genesis_only, 8, &[0]),
1859 ]
1860 );
1861 }
1862
1863 {
1864 let provider_rw = factory.database_provider_rw().unwrap();
1865 provider_rw.tx_ref().clear::<tables::AccountsTrie>().unwrap();
1866 provider_rw.tx_ref().clear::<tables::StoragesTrie>().unwrap();
1867 provider_rw.commit().unwrap();
1868 }
1869 assert_eq!(compute_state_root_chunked(&factory).unwrap(), expected_state_root);
1870 }
1871
1872 #[test]
1873 fn dump_state_overlays_genesis_state_v1() {
1874 assert_dump_state_overlays_genesis_state(StorageSettings::v1());
1875 }
1876
1877 #[test]
1878 fn dump_state_overlays_genesis_state_v2() {
1879 assert_dump_state_overlays_genesis_state(StorageSettings::v2());
1880 }
1881
1882 #[test]
1883 fn success_init_genesis_mainnet() {
1884 let genesis_hash =
1885 init_genesis(&create_test_provider_factory_with_chain_spec(MAINNET.clone())).unwrap();
1886
1887 assert_eq!(genesis_hash, MAINNET_GENESIS_HASH);
1889 }
1890
1891 #[test]
1892 fn success_init_genesis_sepolia() {
1893 let genesis_hash =
1894 init_genesis(&create_test_provider_factory_with_chain_spec(SEPOLIA.clone())).unwrap();
1895
1896 assert_eq!(genesis_hash, SEPOLIA_GENESIS_HASH);
1898 }
1899
1900 #[test]
1901 fn success_init_genesis_holesky() {
1902 let genesis_hash =
1903 init_genesis(&create_test_provider_factory_with_chain_spec(HOLESKY.clone())).unwrap();
1904
1905 assert_eq!(genesis_hash, HOLESKY_GENESIS_HASH);
1907 }
1908
1909 #[test]
1910 fn fail_init_inconsistent_db() {
1911 let factory = create_test_provider_factory_with_chain_spec(SEPOLIA.clone());
1912 let static_file_provider = factory.static_file_provider();
1913 let rocksdb_provider = factory.rocksdb_provider();
1914 init_genesis(&factory).unwrap();
1915
1916 let genesis_hash = init_genesis(
1918 &ProviderFactory::<MockNodeTypesWithDB>::new(
1919 factory.into_db(),
1920 MAINNET.clone(),
1921 static_file_provider,
1922 rocksdb_provider,
1923 reth_tasks::Runtime::test(),
1924 )
1925 .unwrap(),
1926 );
1927
1928 assert!(matches!(
1929 genesis_hash.unwrap_err(),
1930 InitStorageError::GenesisHashMismatch {
1931 chainspec_hash: MAINNET_GENESIS_HASH,
1932 storage_hash: SEPOLIA_GENESIS_HASH
1933 }
1934 ))
1935 }
1936
1937 #[test]
1938 fn skip_genesis_hash_validation_accepts_mismatched_db() {
1939 let factory = create_test_provider_factory_with_chain_spec(SEPOLIA.clone());
1940 let static_file_provider = factory.static_file_provider();
1941 let rocksdb_provider = factory.rocksdb_provider();
1942 init_genesis(&factory).unwrap();
1943
1944 let result = init_genesis_with_settings_and_validate(
1945 &ProviderFactory::<MockNodeTypesWithDB>::new(
1946 factory.into_db(),
1947 MAINNET.clone(),
1948 static_file_provider,
1949 rocksdb_provider,
1950 reth_tasks::Runtime::test(),
1951 )
1952 .unwrap(),
1953 StorageSettings::base(),
1954 false,
1955 );
1956
1957 let returned = result.expect("skip_genesis_validation should suppress mismatch error");
1958 assert_eq!(
1959 returned, SEPOLIA_GENESIS_HASH,
1960 "bypass returns the DB-resident hash, not the chainspec hash",
1961 );
1962 }
1963
1964 #[test]
1965 fn init_genesis_history() {
1966 let address_with_balance = Address::with_last_byte(1);
1967 let address_with_storage = Address::with_last_byte(2);
1968 let storage_key = B256::with_last_byte(1);
1969 let chain_spec = Arc::new(ChainSpec {
1970 chain: Chain::from_id(1),
1971 genesis: Genesis {
1972 alloc: BTreeMap::from([
1973 (
1974 address_with_balance,
1975 GenesisAccount { balance: U256::ONE, ..Default::default() },
1976 ),
1977 (
1978 address_with_storage,
1979 GenesisAccount {
1980 storage: Some(BTreeMap::from([(storage_key, B256::random())])),
1981 ..Default::default()
1982 },
1983 ),
1984 ]),
1985 ..Default::default()
1986 },
1987 hardforks: Default::default(),
1988 paris_block_and_final_difficulty: None,
1989 deposit_contract: None,
1990 ..Default::default()
1991 });
1992
1993 let factory = create_test_provider_factory_with_chain_spec(chain_spec);
1994 init_genesis(&factory).unwrap();
1995
1996 let expected_accounts = vec![
1997 (ShardedKey::new(address_with_balance, u64::MAX), IntegerList::new([0]).unwrap()),
1998 (ShardedKey::new(address_with_storage, u64::MAX), IntegerList::new([0]).unwrap()),
1999 ];
2000 let expected_storages = vec![(
2001 StorageShardedKey::new(address_with_storage, storage_key, u64::MAX),
2002 IntegerList::new([0]).unwrap(),
2003 )];
2004
2005 let collect_from_mdbx = |factory: &ProviderFactory<MockNodeTypesWithDB>| {
2006 let provider = factory.provider().unwrap();
2007 let tx = provider.tx_ref();
2008 (
2009 collect_table_entries::<DatabaseEnv, tables::AccountsHistory>(tx).unwrap(),
2010 collect_table_entries::<DatabaseEnv, tables::StoragesHistory>(tx).unwrap(),
2011 )
2012 };
2013
2014 {
2015 let settings = factory.cached_storage_settings();
2016 let rocksdb = factory.rocksdb_provider();
2017
2018 let collect_rocksdb = |rocksdb: &reth_provider::providers::RocksDBProvider| {
2019 (
2020 rocksdb
2021 .iter::<tables::AccountsHistory>()
2022 .unwrap()
2023 .collect::<Result<Vec<_>, _>>()
2024 .unwrap(),
2025 rocksdb
2026 .iter::<tables::StoragesHistory>()
2027 .unwrap()
2028 .collect::<Result<Vec<_>, _>>()
2029 .unwrap(),
2030 )
2031 };
2032
2033 let (accounts, storages) = if settings.storage_v2 {
2034 collect_rocksdb(&rocksdb)
2035 } else {
2036 collect_from_mdbx(&factory)
2037 };
2038 assert_eq!(accounts, expected_accounts);
2039 assert_eq!(storages, expected_storages);
2040 }
2041 }
2042
2043 #[test]
2044 fn warn_storage_settings_mismatch() {
2045 let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
2046 init_genesis_with_settings(&factory, StorageSettings::v1()).unwrap();
2047
2048 let result = init_genesis_with_settings(&factory, StorageSettings::v2());
2050
2051 assert!(result.is_ok());
2053 }
2054
2055 #[test]
2056 fn allow_same_storage_settings() {
2057 let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
2058 let settings = StorageSettings::v2();
2059 init_genesis_with_settings(&factory, settings).unwrap();
2060
2061 let result = init_genesis_with_settings(&factory, settings);
2062
2063 assert!(result.is_ok());
2064 }
2065}