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, DbDupCursorRW},
15 models::{
16 storage_sharded_key::StorageShardedKey, AccountBeforeTx, BlockNumberAddress, IntegerList,
17 ShardedKey,
18 },
19 tables,
20 transaction::DbTxMut,
21 DatabaseError,
22};
23use reth_etl::Collector;
24use reth_execution_errors::StateRootError;
25use reth_primitives_traits::{
26 Account, Bytecode, GotExpected, NodePrimitives, SealedHeader, StorageEntry,
27};
28use reth_provider::{
29 errors::provider::ProviderResult, providers::StaticFileWriter, BlockHashReader, BlockNumReader,
30 BundleStateInit, ChainSpecProvider, DBProvider, DatabaseProviderFactory, ExecutionOutcome,
31 HashingWriter, HeaderProvider, HistoryWriter, MetadataProvider, MetadataWriter,
32 OriginalValuesKnown, ProviderError, RevertsInit, RocksDBProviderFactory, StageCheckpointReader,
33 StageCheckpointWriter, StateWriteConfig, StateWriter, StaticFileProviderFactory,
34 StorageSettings, StorageSettingsCache, TrieWriter,
35};
36use reth_stages_types::{StageCheckpoint, StageId};
37use reth_static_file_types::StaticFileSegment;
38use reth_trie::{
39 prefix_set::TriePrefixSets, IntermediateStateRootState, StateRoot as StateRootComputer,
40 StateRootProgress,
41};
42use reth_trie_db::DatabaseStateRoot;
43
44type DbStateRoot<'a, TX, A> = StateRootComputer<
45 reth_trie_db::DatabaseTrieCursorFactory<&'a TX, A>,
46 reth_trie_db::DatabaseHashedCursorFactory<&'a TX>,
47>;
48
49use serde::{Deserialize, Serialize};
50use std::io::BufRead;
51use tracing::{debug, error, info, trace, warn};
52
53pub use reth_provider::init::{
54 insert_account_history, insert_genesis_account_history, insert_genesis_history,
55 insert_genesis_storage_history, insert_history, insert_storage_history,
56};
57
58pub const DEFAULT_SOFT_LIMIT_BYTE_LEN_ACCOUNTS_CHUNK: usize = 1_000_000_000;
63
64const SOFT_LIMIT_COUNT_FLUSHED_UPDATES: usize = 1_000_000;
66
67const STORAGE_COMMIT_THRESHOLD: usize = 100_000;
71
72const STATE_ROOT_COMMIT_THRESHOLD: u64 = 25_000;
74
75#[derive(Debug, thiserror::Error, Clone)]
77pub enum InitStorageError {
78 #[error(
80 "static files found, but the database is uninitialized. If attempting to re-syncing, delete both."
81 )]
82 UninitializedDatabase,
83 #[error(
86 "genesis hash in the storage does not match the specified chainspec: chainspec is {chainspec_hash}, database is {storage_hash}"
87 )]
88 GenesisHashMismatch {
89 chainspec_hash: B256,
91 storage_hash: B256,
93 },
94 #[error(transparent)]
96 Provider(#[from] ProviderError),
97 #[error(transparent)]
99 StateRootError(#[from] StateRootError),
100 #[error("state root mismatch: {_0}")]
102 StateRootMismatch(GotExpected<B256>),
103}
104
105impl From<DatabaseError> for InitStorageError {
106 fn from(error: DatabaseError) -> Self {
107 Self::Provider(ProviderError::Database(error))
108 }
109}
110
111pub fn init_genesis<PF>(factory: &PF) -> Result<B256, InitStorageError>
113where
114 PF: DatabaseProviderFactory
115 + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
116 + ChainSpecProvider
117 + StageCheckpointReader
118 + BlockNumReader
119 + MetadataProvider
120 + StorageSettingsCache,
121 PF::ProviderRW: StaticFileProviderFactory<Primitives = PF::Primitives>
122 + StageCheckpointWriter
123 + HistoryWriter
124 + HeaderProvider
125 + HashingWriter
126 + StateWriter
127 + TrieWriter
128 + MetadataWriter
129 + ChainSpecProvider
130 + StorageSettingsCache
131 + RocksDBProviderFactory
132 + AsRef<PF::ProviderRW>,
133 PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
134{
135 init_genesis_with_settings(factory, StorageSettings::base())
136}
137
138pub fn init_genesis_with_settings<PF>(
140 factory: &PF,
141 genesis_storage_settings: StorageSettings,
142) -> Result<B256, InitStorageError>
143where
144 PF: DatabaseProviderFactory
145 + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
146 + ChainSpecProvider
147 + StageCheckpointReader
148 + BlockNumReader
149 + MetadataProvider
150 + StorageSettingsCache,
151 PF::ProviderRW: StaticFileProviderFactory<Primitives = PF::Primitives>
152 + StageCheckpointWriter
153 + HistoryWriter
154 + HeaderProvider
155 + HashingWriter
156 + StateWriter
157 + TrieWriter
158 + MetadataWriter
159 + ChainSpecProvider
160 + StorageSettingsCache
161 + RocksDBProviderFactory
162 + AsRef<PF::ProviderRW>,
163 PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
164{
165 init_genesis_with_settings_and_validate(factory, genesis_storage_settings, true)
166}
167
168pub fn init_genesis_with_settings_and_validate<PF>(
171 factory: &PF,
172 genesis_storage_settings: StorageSettings,
173 validate_genesis_hash: bool,
174) -> Result<B256, InitStorageError>
175where
176 PF: DatabaseProviderFactory
177 + StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
178 + ChainSpecProvider
179 + StageCheckpointReader
180 + BlockNumReader
181 + MetadataProvider
182 + StorageSettingsCache,
183 PF::ProviderRW: StaticFileProviderFactory<Primitives = PF::Primitives>
184 + StageCheckpointWriter
185 + HistoryWriter
186 + HeaderProvider
187 + HashingWriter
188 + StateWriter
189 + TrieWriter
190 + MetadataWriter
191 + ChainSpecProvider
192 + StorageSettingsCache
193 + RocksDBProviderFactory
194 + AsRef<PF::ProviderRW>,
195 PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
196{
197 let chain = factory.chain_spec();
198
199 let genesis = chain.genesis();
200 let hash = chain.genesis_hash();
201
202 let genesis_block_number = chain.genesis_header().number();
204
205 match factory.block_hash(genesis_block_number) {
207 Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, _)) => {}
208 Ok(Some(block_hash)) => {
209 if block_hash == hash {
210 if factory.get_stage_checkpoint(StageId::Headers)?.is_none() {
214 error!(target: "reth::storage", "Genesis header found on static files, but database is uninitialized.");
215 return Err(InitStorageError::UninitializedDatabase)
216 }
217
218 let stored = factory.storage_settings()?.unwrap_or_else(StorageSettings::v1);
219 if stored != genesis_storage_settings {
220 warn!(
221 target: "reth::storage",
222 ?stored,
223 requested = ?genesis_storage_settings,
224 "Storage settings mismatch detected. Using the stored settings from the existing database."
225 );
226 }
227
228 debug!("Genesis already written, skipping.");
229 return Ok(hash)
230 }
231
232 if !validate_genesis_hash {
233 warn!(
234 target: "reth::storage",
235 chainspec_hash = %hash,
236 storage_hash = %block_hash,
237 "Genesis hash mismatch with chainspec; trusting DB per --debug.skip-genesis-validation"
238 );
239 return Ok(block_hash)
240 }
241 return Err(InitStorageError::GenesisHashMismatch {
242 chainspec_hash: hash,
243 storage_hash: block_hash,
244 })
245 }
246 Err(e) => {
247 debug!(?e);
248 return Err(e.into());
249 }
250 }
251
252 debug!("Writing genesis block.");
253
254 factory.set_storage_settings_cache(genesis_storage_settings);
256
257 let alloc = &genesis.alloc;
258
259 let provider_rw = factory.database_provider_rw()?;
261
262 provider_rw.write_storage_settings(genesis_storage_settings)?;
264
265 let static_file_provider = provider_rw.static_file_provider();
270 if genesis_block_number > 0 {
271 if genesis_storage_settings.storage_v2 {
272 static_file_provider
273 .get_writer(genesis_block_number, StaticFileSegment::AccountChangeSets)?
274 .user_header_mut()
275 .set_expected_block_start(genesis_block_number);
276 }
277 if genesis_storage_settings.storage_v2 {
278 static_file_provider
279 .get_writer(genesis_block_number, StaticFileSegment::StorageChangeSets)?
280 .user_header_mut()
281 .set_expected_block_start(genesis_block_number);
282 }
283 }
284
285 insert_genesis_hashes(&provider_rw, alloc.iter())?;
286 insert_genesis_history(&provider_rw, alloc.iter())?;
287
288 insert_genesis_header(&provider_rw, &chain)?;
290
291 insert_genesis_state(&provider_rw, alloc.iter())?;
292
293 compute_state_root(&provider_rw, None)?;
295
296 let checkpoint = StageCheckpoint::new(genesis_block_number);
298 for stage in StageId::ALL {
299 provider_rw.save_stage_checkpoint(stage, checkpoint)?;
300 }
301
302 let static_file_provider = provider_rw.static_file_provider();
306
307 static_file_provider
308 .get_writer(genesis_block_number, StaticFileSegment::Receipts)?
309 .user_header_mut()
310 .set_block_range(genesis_block_number, genesis_block_number);
311 static_file_provider
312 .get_writer(genesis_block_number, StaticFileSegment::Transactions)?
313 .user_header_mut()
314 .set_block_range(genesis_block_number, genesis_block_number);
315
316 if genesis_storage_settings.storage_v2 {
317 static_file_provider
318 .get_writer(genesis_block_number, StaticFileSegment::TransactionSenders)?
319 .user_header_mut()
320 .set_block_range(genesis_block_number, genesis_block_number);
321 }
322
323 provider_rw.commit()?;
326
327 Ok(hash)
328}
329
330pub fn insert_genesis_state<'a, 'b, Provider>(
332 provider: &Provider,
333 alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
334) -> ProviderResult<()>
335where
336 Provider: StaticFileProviderFactory
337 + DBProvider<Tx: DbTxMut>
338 + HeaderProvider
339 + StateWriter
340 + ChainSpecProvider
341 + AsRef<Provider>,
342{
343 let genesis_block_number = provider.chain_spec().genesis_header().number();
344 insert_state(provider, alloc, genesis_block_number)
345}
346
347pub fn insert_state<'a, 'b, Provider>(
349 provider: &Provider,
350 alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)>,
351 block: u64,
352) -> ProviderResult<()>
353where
354 Provider: StaticFileProviderFactory
355 + DBProvider<Tx: DbTxMut>
356 + HeaderProvider
357 + StateWriter
358 + AsRef<Provider>,
359{
360 let capacity = alloc.size_hint().1.unwrap_or(0);
361 let mut state_init: BundleStateInit =
362 AddressMap::with_capacity_and_hasher(capacity, Default::default());
363 let mut reverts_init: AddressMap<_> =
364 AddressMap::with_capacity_and_hasher(capacity, Default::default());
365 let mut contracts: B256Map<Bytecode> =
366 B256Map::with_capacity_and_hasher(capacity, Default::default());
367
368 for (address, account) in alloc {
369 let bytecode_hash = if let Some(code) = &account.code {
370 match Bytecode::new_raw_checked(code.clone()) {
371 Ok(bytecode) => {
372 let hash = bytecode.hash_slow();
373 contracts.insert(hash, bytecode);
374 Some(hash)
375 }
376 Err(err) => {
377 error!(%address, %err, "Failed to decode genesis bytecode.");
378 return Err(DatabaseError::Other(err.to_string()).into());
379 }
380 }
381 } else {
382 None
383 };
384
385 let storage = account
387 .storage
388 .as_ref()
389 .map(|m| {
390 m.iter()
391 .map(|(key, value)| {
392 let value = U256::from_be_bytes(value.0);
393 (*key, (U256::ZERO, value))
394 })
395 .collect::<B256Map<_>>()
396 })
397 .unwrap_or_default();
398
399 reverts_init.insert(
400 *address,
401 (Some(None), storage.keys().map(|k| StorageEntry::new(*k, U256::ZERO)).collect()),
402 );
403
404 state_init.insert(
405 *address,
406 (None, Some(Account { bytecode_hash, ..Account::from(account) }), storage),
407 );
408 }
409 let all_reverts_init: RevertsInit = HashMap::from_iter([(block, reverts_init)]);
410
411 let execution_outcome = ExecutionOutcome::new_init(
412 state_init,
413 all_reverts_init,
414 contracts,
415 Vec::default(),
416 block,
417 Vec::new(),
418 );
419
420 provider.write_state(
421 &execution_outcome,
422 OriginalValuesKnown::Yes,
423 StateWriteConfig::default(),
424 )?;
425
426 trace!(target: "reth::cli", "Inserted state");
427
428 Ok(())
429}
430
431pub fn insert_genesis_hashes<'a, 'b, Provider>(
433 provider: &Provider,
434 alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)> + Clone,
435) -> ProviderResult<()>
436where
437 Provider: DBProvider<Tx: DbTxMut> + HashingWriter,
438{
439 let alloc_accounts = alloc.clone().map(|(addr, account)| (*addr, Some(Account::from(account))));
441 provider.insert_account_for_hashing(alloc_accounts)?;
442
443 trace!(target: "reth::cli", "Inserted account hashes");
444
445 let alloc_storage = alloc.filter_map(|(addr, account)| {
446 account.storage.as_ref().map(|storage| {
448 (*addr, storage.iter().map(|(&key, &value)| StorageEntry { key, value: value.into() }))
449 })
450 });
451 provider.insert_storage_for_hashing(alloc_storage)?;
452
453 trace!(target: "reth::cli", "Inserted storage hashes");
454
455 Ok(())
456}
457
458pub fn insert_genesis_header<Provider, Spec>(
460 provider: &Provider,
461 chain: &Spec,
462) -> ProviderResult<()>
463where
464 Provider: StaticFileProviderFactory<Primitives: NodePrimitives<BlockHeader: Compact>>
465 + DBProvider<Tx: DbTxMut>,
466 Spec: EthChainSpec<Header = <Provider::Primitives as NodePrimitives>::BlockHeader>,
467{
468 let (header, block_hash) = (chain.genesis_header(), chain.genesis_hash());
469 let static_file_provider = provider.static_file_provider();
470
471 let genesis_block_number = header.number();
473
474 match static_file_provider.block_hash(genesis_block_number) {
475 Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, _)) => {
476 let difficulty = header.difficulty();
477
478 let mut writer = static_file_provider
482 .get_writer(genesis_block_number, StaticFileSegment::Headers)?;
483
484 if genesis_block_number > 0 {
487 writer
488 .user_header_mut()
489 .set_block_range(genesis_block_number, genesis_block_number);
490 writer.append_header_direct(header, difficulty, &block_hash)?;
491 } else {
492 writer.append_header(header, &block_hash)?;
494 }
495 }
496 Ok(Some(_)) => {}
497 Err(e) => return Err(e),
498 }
499
500 provider.tx_ref().put::<tables::HeaderNumbers>(block_hash, genesis_block_number)?;
501 provider.tx_ref().put::<tables::BlockBodyIndices>(genesis_block_number, Default::default())?;
502
503 Ok(())
504}
505
506pub fn init_from_state_dump<PF>(
513 mut reader: impl BufRead,
514 provider_factory: &PF,
515 etl_config: EtlConfig,
516) -> eyre::Result<B256>
517where
518 PF: DatabaseProviderFactory<
519 ProviderRW: StaticFileProviderFactory
520 + DBProvider<Tx: DbTxMut>
521 + BlockNumReader
522 + ChainSpecProvider
523 + StageCheckpointWriter
524 + HistoryWriter
525 + HeaderProvider
526 + HashingWriter
527 + TrieWriter
528 + StateWriter
529 + StorageSettingsCache
530 + RocksDBProviderFactory
531 + AsRef<PF::ProviderRW>,
532 >,
533{
534 if etl_config.file_size == 0 {
535 return Err(eyre::eyre!("ETL file size cannot be zero"))
536 }
537
538 let (block, hash, expected_state_root) = {
539 let provider_rw = provider_factory.database_provider_rw()?;
540 let block = provider_rw.last_block_number()?;
541 let hash = provider_rw
542 .block_hash(block)?
543 .ok_or_else(|| eyre::eyre!("Block hash not found for block {}", block))?;
544 let header = provider_rw
545 .header_by_number(block)?
546 .map(|h| SealedHeader::new(h, hash))
547 .ok_or_else(|| ProviderError::HeaderNotFound(block.into()))?;
548 let state_root = header.state_root();
549
550 debug!(target: "reth::cli",
551 block,
552 chain=%provider_rw.chain_spec().chain(),
553 "Initializing state at block"
554 );
555
556 (block, hash, state_root)
557 };
558
559 let dump_state_root = parse_state_root(&mut reader)?;
561 if expected_state_root != dump_state_root {
562 error!(target: "reth::cli",
563 ?dump_state_root,
564 ?expected_state_root,
565 "State root from state dump does not match state root in current header."
566 );
567 return Err(InitStorageError::StateRootMismatch(GotExpected {
568 got: dump_state_root,
569 expected: expected_state_root,
570 })
571 .into())
572 }
573
574 let collector = parse_accounts(&mut reader, etl_config)?;
576
577 dump_state(collector, provider_factory, block)?;
579
580 info!(target: "reth::cli", "All accounts written to database, starting state root computation (may take some time)");
581
582 {
584 let provider_rw = provider_factory.database_provider_rw()?;
585 provider_rw.tx_ref().clear::<tables::AccountsTrie>()?;
586 provider_rw.tx_ref().clear::<tables::StoragesTrie>()?;
587 provider_rw.commit()?;
588 }
589
590 let computed_state_root = compute_state_root_chunked(provider_factory)?;
592 if computed_state_root == expected_state_root {
593 info!(target: "reth::cli",
594 ?computed_state_root,
595 "Computed state root matches state root in state dump"
596 );
597 } else {
598 error!(target: "reth::cli",
599 ?computed_state_root,
600 ?expected_state_root,
601 "Computed state root does not match state root in state dump"
602 );
603
604 return Err(InitStorageError::StateRootMismatch(GotExpected {
605 got: computed_state_root,
606 expected: expected_state_root,
607 })
608 .into())
609 }
610
611 {
613 let provider_rw = provider_factory.database_provider_rw()?;
614 for stage in StageId::STATE_REQUIRED {
615 provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(block))?;
616 }
617 provider_rw.commit()?;
618 }
619
620 Ok(hash)
621}
622
623fn parse_state_root(reader: &mut impl BufRead) -> eyre::Result<B256> {
625 let mut line = String::new();
626 reader.read_line(&mut line)?;
627
628 let expected_state_root = serde_json::from_str::<StateRoot>(&line)?.root;
629 trace!(target: "reth::cli",
630 root=%expected_state_root,
631 "Read state root from file"
632 );
633 Ok(expected_state_root)
634}
635
636fn parse_accounts(
638 reader: impl BufRead,
639 etl_config: EtlConfig,
640) -> Result<Collector<Address, GenesisAccount>, eyre::Error> {
641 let mut collector = Collector::new(etl_config.file_size, etl_config.dir);
642 let mut parsed_accounts = 0usize;
643
644 let stream =
645 serde_json::Deserializer::from_reader(reader).into_iter::<GenesisAccountWithAddress>();
646 for account in stream {
647 let GenesisAccountWithAddress { genesis_account, address } = account?;
648 collector.insert(address, genesis_account)?;
649
650 parsed_accounts += 1;
651 if parsed_accounts.is_multiple_of(100_000) {
652 info!(target: "reth::cli", parsed_accounts, "Parsed accounts");
653 }
654 }
655
656 Ok(collector)
657}
658
659fn dump_state<PF>(
674 mut collector: Collector<Address, GenesisAccount>,
675 provider_factory: &PF,
676 block: u64,
677) -> Result<(), eyre::Error>
678where
679 PF: DatabaseProviderFactory<ProviderRW: DBProvider<Tx: DbTxMut>>,
680 PF::ProviderRW: StaticFileProviderFactory + StorageSettingsCache + RocksDBProviderFactory,
681{
682 let storage_settings = provider_factory.database_provider_rw()?.cached_storage_settings();
683 if storage_settings.storage_v2 {
684 return dump_state_v2(collector, provider_factory, block)
685 }
686
687 let accounts_len = collector.len();
688 let mut total_accounts: usize = 0;
689 let mut storage_units: usize = 0;
690
691 let history_list = IntegerList::new([block])?;
693
694 let mut seen_bytecodes: B256Set = B256Set::default();
696
697 let mut provider_rw = provider_factory.database_provider_rw()?;
698
699 for entry in collector.iter()? {
700 let (address_raw, account_raw) = entry?;
701 let (address, _) = Address::from_compact(address_raw.as_slice(), address_raw.len());
702 let (account, _) = GenesisAccount::from_compact(account_raw.as_slice(), account_raw.len());
703
704 let account_storage_len = account.storage.as_ref().map_or(0, |s| s.len());
705 let account_units = 1 + account_storage_len;
706
707 if storage_units > 0 && storage_units + account_units > STORAGE_COMMIT_THRESHOLD {
709 provider_rw.commit()?;
710 provider_rw = provider_factory.database_provider_rw()?;
711 info!(target: "reth::cli",
712 total_accounts,
713 accounts_len,
714 storage_units,
715 "Committed chunk"
716 );
717 storage_units = 0;
718 seen_bytecodes = B256Set::default();
719 }
720
721 write_account_to_db(
722 provider_rw.tx_ref(),
723 &address,
724 &account,
725 block,
726 &history_list,
727 &mut seen_bytecodes,
728 )?;
729
730 total_accounts += 1;
731 storage_units += account_units;
732
733 if total_accounts.is_multiple_of(100_000) {
734 info!(target: "reth::cli", total_accounts, accounts_len, "Writing accounts...");
735 }
736 }
737
738 provider_rw.commit()?;
740
741 info!(target: "reth::cli", total_accounts, "All accounts written to database");
742
743 Ok(())
744}
745
746fn dump_state_v2<PF>(
747 mut collector: Collector<Address, GenesisAccount>,
748 provider_factory: &PF,
749 block: u64,
750) -> Result<(), eyre::Error>
751where
752 PF: DatabaseProviderFactory<
753 ProviderRW: StaticFileProviderFactory
754 + DBProvider<Tx: DbTxMut>
755 + StorageSettingsCache
756 + RocksDBProviderFactory,
757 >,
758{
759 let accounts_len = collector.len();
760 let mut total_accounts: usize = 0;
761 let mut storage_units: usize = 0;
762
763 let history_list = IntegerList::new([block])?;
765
766 let mut seen_bytecodes: B256Set = B256Set::default();
768
769 let mut provider_rw = provider_factory.database_provider_rw()?;
770 let static_file_provider = provider_rw.static_file_provider();
771 let rocksdb_provider = provider_rw.rocksdb_provider();
772 let mut history_batch = rocksdb_provider.batch_with_auto_commit();
773 if snapshot_state_tables_empty(provider_rw.tx_ref())? {
774 reset_pre_snapshot_changeset_segment(
775 &static_file_provider,
776 StaticFileSegment::AccountChangeSets,
777 block,
778 )?;
779 reset_pre_snapshot_changeset_segment(
780 &static_file_provider,
781 StaticFileSegment::StorageChangeSets,
782 block,
783 )?;
784 }
785
786 {
787 let mut account_changeset_writer =
788 static_file_provider.get_writer(block, StaticFileSegment::AccountChangeSets)?;
789 let mut storage_changeset_writer =
790 static_file_provider.get_writer(block, StaticFileSegment::StorageChangeSets)?;
791 prepare_account_changeset_writer(&mut account_changeset_writer, block)?;
792 prepare_storage_changeset_writer(&mut storage_changeset_writer, block)?;
793
794 for entry in collector.iter()? {
795 let (address_raw, account_raw) = entry?;
796 let (address, _) = Address::from_compact(address_raw.as_slice(), address_raw.len());
797 let (account, _) =
798 GenesisAccount::from_compact(account_raw.as_slice(), account_raw.len());
799
800 let account_storage_len = account.storage.as_ref().map_or(0, |s| s.len());
801 let account_units = 1 + account_storage_len;
802
803 if storage_units > 0 && storage_units + account_units > STORAGE_COMMIT_THRESHOLD {
805 history_batch.commit()?;
806 commit_mdbx_only(provider_rw)?;
807 provider_rw = provider_factory.database_provider_rw()?;
808 history_batch = rocksdb_provider.batch_with_auto_commit();
809 info!(target: "reth::cli",
810 total_accounts,
811 accounts_len,
812 storage_units,
813 "Committed chunk"
814 );
815 storage_units = 0;
816 seen_bytecodes = B256Set::default();
817 }
818
819 write_account_to_db_v2(
820 provider_rw.tx_ref(),
821 (&mut account_changeset_writer, &mut storage_changeset_writer),
822 &mut history_batch,
823 &address,
824 &account,
825 &history_list,
826 &mut seen_bytecodes,
827 )?;
828
829 total_accounts += 1;
830 storage_units += account_units;
831
832 if total_accounts.is_multiple_of(100_000) {
833 info!(target: "reth::cli", total_accounts, accounts_len, "Writing accounts...");
834 }
835 }
836 }
837
838 history_batch.commit()?;
839 commit_mdbx_only(provider_rw)?;
840 static_file_provider.finalize()?;
841
842 info!(target: "reth::cli", total_accounts, "All accounts written to database");
843
844 Ok(())
845}
846
847fn prepare_account_changeset_writer<N: NodePrimitives>(
848 writer: &mut reth_provider::providers::StaticFileProviderRWRefMut<'_, N>,
849 block: u64,
850) -> ProviderResult<()> {
851 let next_block = writer.next_block_number();
852 if next_block < block {
853 info!(
854 target: "reth::cli",
855 from_block = next_block,
856 to_block = block - 1,
857 "Padding empty account changesets before state import"
858 );
859 for empty_block in next_block..block {
860 writer.append_account_changeset(Vec::new(), empty_block)?;
861 if empty_block > next_block && empty_block.is_multiple_of(1_000_000) {
862 info!(
863 target: "reth::cli",
864 padded_to_block = empty_block,
865 "Padded empty account changesets"
866 );
867 }
868 }
869 }
870
871 writer.begin_account_changeset(block)
872}
873
874fn prepare_storage_changeset_writer<N: NodePrimitives>(
875 writer: &mut reth_provider::providers::StaticFileProviderRWRefMut<'_, N>,
876 block: u64,
877) -> ProviderResult<()> {
878 let next_block = writer.next_block_number();
879 if next_block < block {
880 info!(
881 target: "reth::cli",
882 from_block = next_block,
883 to_block = block - 1,
884 "Padding empty storage changesets before state import"
885 );
886 for empty_block in next_block..block {
887 writer.append_storage_changeset(Vec::new(), empty_block)?;
888 if empty_block > next_block && empty_block.is_multiple_of(1_000_000) {
889 info!(
890 target: "reth::cli",
891 padded_to_block = empty_block,
892 "Padded empty storage changesets"
893 );
894 }
895 }
896 }
897
898 writer.begin_storage_changeset(block)
899}
900
901fn snapshot_state_tables_empty<TX: reth_db_api::transaction::DbTx>(
902 tx: &TX,
903) -> ProviderResult<bool> {
904 Ok(tx.entries::<tables::PlainAccountState>()? == 0 &&
905 tx.entries::<tables::PlainStorageState>()? == 0 &&
906 tx.entries::<tables::HashedAccounts>()? == 0 &&
907 tx.entries::<tables::HashedStorages>()? == 0 &&
908 tx.entries::<tables::AccountChangeSets>()? == 0 &&
909 tx.entries::<tables::StorageChangeSets>()? == 0 &&
910 tx.entries::<tables::Bytecodes>()? == 0)
911}
912
913fn reset_pre_snapshot_changeset_segment<N: NodePrimitives>(
914 static_file_provider: &reth_provider::providers::StaticFileProvider<N>,
915 segment: StaticFileSegment,
916 block: u64,
917) -> ProviderResult<()> {
918 if block == 0 {
919 return Ok(())
920 }
921
922 let Some(highest_block) = static_file_provider.get_highest_static_file_block(segment) else {
923 return Ok(())
924 };
925
926 if highest_block >= block {
927 return Ok(())
928 }
929
930 let file_start = static_file_provider.find_fixed_range(segment, block).start();
931 info!(
932 target: "reth::cli",
933 ?segment,
934 highest_block,
935 import_block = block,
936 file_start,
937 "Resetting pre-snapshot changeset static files before state import"
938 );
939 static_file_provider.delete_segment(segment)?;
940
941 Ok(())
942}
943
944fn commit_mdbx_only<Provider>(provider: Provider) -> ProviderResult<()>
945where
946 Provider: DBProvider<Tx: DbTxMut>,
947{
948 reth_db_api::transaction::DbTx::commit(provider.into_tx()).map_err(ProviderError::from)
949}
950
951#[allow(clippy::clone_on_copy)]
960fn write_account_to_db<TX: DbTxMut>(
961 tx: &TX,
962 address: &Address,
963 genesis_account: &GenesisAccount,
964 block: u64,
965 history_list: &IntegerList,
966 seen_bytecodes: &mut B256Set,
967) -> Result<(), eyre::Error> {
968 let bytecode_hash = if let Some(code) = &genesis_account.code {
969 let bytecode = Bytecode::new_raw_checked(code.clone())
970 .map_err(|e| eyre::eyre!("Invalid bytecode for {address}: {e}"))?;
971 let hash = bytecode.hash_slow();
972 if seen_bytecodes.insert(hash) {
973 tx.put::<tables::Bytecodes>(hash, bytecode)?;
974 }
975 Some(hash)
976 } else {
977 None
978 };
979
980 let account = Account { bytecode_hash, ..Account::from(genesis_account) };
981
982 let hashed_address = keccak256(address);
983
984 tx.put::<tables::PlainAccountState>(*address, account.clone())?;
986
987 tx.put::<tables::HashedAccounts>(hashed_address, account)?;
989
990 let mut acct_cs_cursor = tx.cursor_dup_write::<tables::AccountChangeSets>()?;
992 acct_cs_cursor.append_dup(block, AccountBeforeTx { address: *address, info: None })?;
993
994 tx.put::<tables::AccountsHistory>(ShardedKey::new(*address, u64::MAX), history_list.clone())?;
996
997 if let Some(storage) = &genesis_account.storage {
999 let mut hashed_storage_cursor = tx.cursor_dup_write::<tables::HashedStorages>()?;
1000 let mut plain_storage_cursor = tx.cursor_dup_write::<tables::PlainStorageState>()?;
1001 let mut storage_cs_cursor = tx.cursor_dup_write::<tables::StorageChangeSets>()?;
1002
1003 for (&key, &value) in storage {
1004 let value_u256 = U256::from_be_bytes(value.0);
1005
1006 plain_storage_cursor.append_dup(*address, StorageEntry { key, value: value_u256 })?;
1008
1009 let hashed_key = keccak256(key);
1011 hashed_storage_cursor
1012 .upsert(hashed_address, &StorageEntry { key: hashed_key, value: value_u256 })?;
1013
1014 storage_cs_cursor.append_dup(
1016 BlockNumberAddress((block, *address)),
1017 StorageEntry { key, value: U256::ZERO },
1018 )?;
1019
1020 tx.put::<tables::StoragesHistory>(
1022 StorageShardedKey::new(*address, key, u64::MAX),
1023 history_list.clone(),
1024 )?;
1025 }
1026 }
1027
1028 Ok(())
1029}
1030
1031fn write_account_to_db_v2<TX, N>(
1037 tx: &TX,
1038 changeset_writers: (
1039 &mut reth_provider::providers::StaticFileProviderRWRefMut<'_, N>,
1040 &mut reth_provider::providers::StaticFileProviderRWRefMut<'_, N>,
1041 ),
1042 history_batch: &mut reth_provider::providers::RocksDBBatch<'_>,
1043 address: &Address,
1044 genesis_account: &GenesisAccount,
1045 history_list: &IntegerList,
1046 seen_bytecodes: &mut B256Set,
1047) -> Result<(), eyre::Error>
1048where
1049 TX: DbTxMut,
1050 N: NodePrimitives,
1051{
1052 let bytecode_hash = if let Some(code) = &genesis_account.code {
1053 let bytecode = Bytecode::new_raw_checked(code.clone())
1054 .map_err(|e| eyre::eyre!("Invalid bytecode for {address}: {e}"))?;
1055 let hash = bytecode.hash_slow();
1056 if seen_bytecodes.insert(hash) {
1057 tx.put::<tables::Bytecodes>(hash, bytecode)?;
1058 }
1059 Some(hash)
1060 } else {
1061 None
1062 };
1063
1064 let account = Account { bytecode_hash, ..Account::from(genesis_account) };
1065
1066 let hashed_address = keccak256(address);
1067 let (account_changeset_writer, storage_changeset_writer) = changeset_writers;
1068
1069 tx.put::<tables::HashedAccounts>(hashed_address, account)?;
1070 account_changeset_writer
1071 .append_account_changeset_entry(AccountBeforeTx { address: *address, info: None })?;
1072 history_batch
1073 .put::<tables::AccountsHistory>(ShardedKey::new(*address, u64::MAX), history_list)?;
1074
1075 if let Some(storage) = &genesis_account.storage {
1076 let mut hashed_storage_cursor = tx.cursor_dup_write::<tables::HashedStorages>()?;
1077
1078 for (&key, &value) in storage {
1079 let value_u256 = U256::from_be_bytes(value.0);
1080
1081 let hashed_key = keccak256(key);
1082 hashed_storage_cursor
1083 .upsert(hashed_address, &StorageEntry { key: hashed_key, value: value_u256 })?;
1084
1085 storage_changeset_writer.append_storage_changeset_entry(
1086 reth_db_api::models::StorageBeforeTx { address: *address, key, value: U256::ZERO },
1087 )?;
1088
1089 history_batch.put::<tables::StoragesHistory>(
1090 StorageShardedKey::new(*address, key, u64::MAX),
1091 history_list,
1092 )?;
1093 }
1094 }
1095
1096 Ok(())
1097}
1098
1099fn compute_state_root<Provider>(
1102 provider: &Provider,
1103 prefix_sets: Option<TriePrefixSets>,
1104) -> Result<B256, InitStorageError>
1105where
1106 Provider: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
1107{
1108 reth_trie_db::with_adapter!(provider, |A| {
1109 compute_state_root_inner::<_, A>(provider, prefix_sets)
1110 })
1111}
1112
1113fn compute_state_root_inner<Provider, A>(
1114 provider: &Provider,
1115 prefix_sets: Option<TriePrefixSets>,
1116) -> Result<B256, InitStorageError>
1117where
1118 Provider: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
1119 A: reth_trie_db::TrieTableAdapter,
1120{
1121 trace!(target: "reth::cli", "Computing state root");
1122
1123 let tx = provider.tx_ref();
1124 let mut intermediate_state: Option<IntermediateStateRootState> = None;
1125 let mut total_flushed_updates = 0;
1126
1127 loop {
1128 let mut state_root =
1129 DbStateRoot::<_, A>::from_tx(tx).with_intermediate_state(intermediate_state);
1130
1131 if let Some(sets) = prefix_sets.clone() {
1132 state_root = state_root.with_prefix_sets(sets);
1133 }
1134
1135 match state_root.root_with_progress()? {
1136 StateRootProgress::Progress(state, _, updates) => {
1137 let updated_len = provider.write_trie_updates(updates)?;
1138 total_flushed_updates += updated_len;
1139
1140 trace!(target: "reth::cli",
1141 last_account_key = %state.account_root_state.last_hashed_key,
1142 updated_len,
1143 total_flushed_updates,
1144 "Flushing trie updates"
1145 );
1146
1147 intermediate_state = Some(*state);
1148
1149 if total_flushed_updates.is_multiple_of(SOFT_LIMIT_COUNT_FLUSHED_UPDATES) {
1150 info!(target: "reth::cli",
1151 total_flushed_updates,
1152 "Flushing trie updates"
1153 );
1154 }
1155 }
1156 StateRootProgress::Complete(root, _, updates) => {
1157 let updated_len = provider.write_trie_updates(updates)?;
1158 total_flushed_updates += updated_len;
1159
1160 trace!(target: "reth::cli",
1161 %root,
1162 updated_len,
1163 total_flushed_updates,
1164 "State root has been computed"
1165 );
1166
1167 return Ok(root)
1168 }
1169 }
1170 }
1171}
1172
1173fn compute_state_root_chunked<PF>(provider_factory: &PF) -> Result<B256, InitStorageError>
1178where
1179 PF: DatabaseProviderFactory<
1180 ProviderRW: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
1181 >,
1182{
1183 let provider_rw = provider_factory.database_provider_rw().map_err(provider_db_err)?;
1184
1185 reth_trie_db::with_adapter!(&provider_rw, |A| {
1186 drop(provider_rw);
1187 compute_state_root_chunked_inner::<PF, A>(provider_factory)
1188 })
1189}
1190
1191fn compute_state_root_chunked_inner<PF, A>(provider_factory: &PF) -> Result<B256, InitStorageError>
1192where
1193 PF: DatabaseProviderFactory<
1194 ProviderRW: DBProvider<Tx: DbTxMut> + TrieWriter + StorageSettingsCache,
1195 >,
1196 A: reth_trie_db::TrieTableAdapter,
1197{
1198 trace!(target: "reth::cli", "Computing state root");
1199
1200 let mut intermediate_state: Option<IntermediateStateRootState> = None;
1201 let mut total_flushed_updates = 0;
1202
1203 loop {
1204 let provider_rw = provider_factory.database_provider_rw().map_err(provider_db_err)?;
1205 let tx = provider_rw.tx_ref();
1206
1207 let state_root = DbStateRoot::<_, A>::from_tx(tx)
1208 .with_intermediate_state(intermediate_state.take())
1209 .with_threshold(STATE_ROOT_COMMIT_THRESHOLD);
1210
1211 match state_root.root_with_progress()? {
1212 StateRootProgress::Progress(state, _, updates) => {
1213 let updated_len = provider_rw.write_trie_updates(updates)?;
1214 total_flushed_updates += updated_len;
1215
1216 info!(target: "reth::cli",
1217 last_account_key = %state.account_root_state.last_hashed_key,
1218 updated_len,
1219 total_flushed_updates,
1220 "Flushing trie updates (committing to free memory)"
1221 );
1222
1223 intermediate_state = Some(*state);
1224 provider_rw.commit().map_err(provider_db_err)?;
1225 }
1226 StateRootProgress::Complete(root, _, updates) => {
1227 let updated_len = provider_rw.write_trie_updates(updates)?;
1228 total_flushed_updates += updated_len;
1229
1230 info!(target: "reth::cli",
1231 %root,
1232 updated_len,
1233 total_flushed_updates,
1234 "State root computation complete"
1235 );
1236
1237 provider_rw.commit().map_err(provider_db_err)?;
1238 return Ok(root)
1239 }
1240 }
1241 }
1242}
1243
1244fn provider_db_err(e: impl std::fmt::Display) -> InitStorageError {
1246 InitStorageError::from(StateRootError::Database(DatabaseError::Other(e.to_string())))
1247}
1248
1249#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
1251struct StateRoot {
1252 root: B256,
1253}
1254
1255#[derive(Debug, Serialize, Deserialize)]
1258struct GenesisAccountWithAddress {
1259 #[serde(flatten)]
1261 genesis_account: GenesisAccount,
1262 address: Address,
1264}
1265
1266#[cfg(test)]
1267mod tests {
1268 use super::*;
1269 use alloy_consensus::constants::{
1270 HOLESKY_GENESIS_HASH, MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH,
1271 };
1272 use alloy_genesis::Genesis;
1273 use reth_chainspec::{Chain, ChainSpec, HOLESKY, MAINNET, SEPOLIA};
1274 use reth_db::DatabaseEnv;
1275 use reth_db_api::{
1276 cursor::DbCursorRO,
1277 models::{storage_sharded_key::StorageShardedKey, IntegerList, ShardedKey},
1278 table::{Table, TableRow},
1279 transaction::DbTx,
1280 Database,
1281 };
1282 use reth_provider::{
1283 test_utils::{create_test_provider_factory_with_chain_spec, MockNodeTypesWithDB},
1284 ProviderFactory, RocksDBProviderFactory,
1285 };
1286 use std::{collections::BTreeMap, sync::Arc};
1287
1288 fn collect_table_entries<DB, T>(
1289 tx: &<DB as Database>::TX,
1290 ) -> Result<Vec<TableRow<T>>, InitStorageError>
1291 where
1292 DB: Database,
1293 T: Table,
1294 {
1295 Ok(tx.cursor_read::<T>()?.walk_range(..)?.collect::<Result<Vec<_>, _>>()?)
1296 }
1297
1298 #[test]
1299 fn parse_accounts_streams_jsonl_accounts() {
1300 let input = br#"{"address":"0x0000000000000000000000000000000000000002","balance":"0x2"}
1301{"address":"0x0000000000000000000000000000000000000001","balance":"0x1"}
1302"#;
1303
1304 let mut collector =
1305 parse_accounts(&input[..], EtlConfig::new(None, 128)).expect("parse succeeds");
1306
1307 let accounts = collector
1308 .iter()
1309 .unwrap()
1310 .map(|entry| {
1311 let (address_raw, account_raw) = entry.unwrap();
1312 let (address, _) = Address::from_compact(address_raw.as_slice(), address_raw.len());
1313 let (account, _) =
1314 GenesisAccount::from_compact(account_raw.as_slice(), account_raw.len());
1315 (address, account.balance)
1316 })
1317 .collect::<Vec<_>>();
1318
1319 assert_eq!(
1320 accounts,
1321 vec![
1322 (Address::with_last_byte(1), U256::ONE),
1323 (Address::with_last_byte(2), U256::from(2))
1324 ]
1325 );
1326 }
1327
1328 #[test]
1329 fn dump_state_uses_storage_v2_destinations() {
1330 let storage_key = B256::with_last_byte(3);
1331 let input = br#"{"address":"0x0000000000000000000000000000000000000001","balance":"0x1"}
1332{"address":"0x0000000000000000000000000000000000000002","balance":"0x0","storage":{"0x0000000000000000000000000000000000000000000000000000000000000003":"0x0000000000000000000000000000000000000000000000000000000000000004"}}
1333"#;
1334
1335 let collector = parse_accounts(&input[..], EtlConfig::new(None, 128)).unwrap();
1336 let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
1337 factory.set_storage_settings_cache(StorageSettings::v2());
1338 let block = 10;
1339
1340 dump_state(collector, &factory, block).unwrap();
1341
1342 let provider = factory.provider().unwrap();
1343 let tx = provider.tx_ref();
1344 assert_eq!(tx.entries::<tables::PlainAccountState>().unwrap(), 0);
1345 assert_eq!(tx.entries::<tables::PlainStorageState>().unwrap(), 0);
1346 assert_eq!(tx.entries::<tables::AccountChangeSets>().unwrap(), 0);
1347 assert_eq!(tx.entries::<tables::StorageChangeSets>().unwrap(), 0);
1348 assert_eq!(tx.entries::<tables::HashedAccounts>().unwrap(), 2);
1349 assert_eq!(tx.entries::<tables::HashedStorages>().unwrap(), 1);
1350
1351 let address_with_balance = Address::with_last_byte(1);
1352 let address_with_storage = Address::with_last_byte(2);
1353 assert_eq!(
1354 reth_provider::ChangeSetReader::account_block_changeset(&provider, block).unwrap(),
1355 vec![
1356 AccountBeforeTx { address: address_with_balance, info: None },
1357 AccountBeforeTx { address: address_with_storage, info: None }
1358 ]
1359 );
1360 assert_eq!(
1361 reth_provider::StorageChangeSetReader::storage_changeset(&provider, block).unwrap(),
1362 vec![(
1363 BlockNumberAddress((block, address_with_storage)),
1364 StorageEntry { key: storage_key, value: U256::ZERO }
1365 )]
1366 );
1367
1368 let rocksdb = factory.rocksdb_provider();
1369 let accounts = rocksdb
1370 .iter::<tables::AccountsHistory>()
1371 .unwrap()
1372 .collect::<Result<Vec<_>, _>>()
1373 .unwrap();
1374 let storages = rocksdb
1375 .iter::<tables::StoragesHistory>()
1376 .unwrap()
1377 .collect::<Result<Vec<_>, _>>()
1378 .unwrap();
1379
1380 assert_eq!(
1381 accounts,
1382 vec![
1383 (
1384 ShardedKey::new(address_with_balance, u64::MAX),
1385 IntegerList::new([block]).unwrap()
1386 ),
1387 (
1388 ShardedKey::new(address_with_storage, u64::MAX),
1389 IntegerList::new([block]).unwrap()
1390 )
1391 ]
1392 );
1393 assert_eq!(
1394 storages,
1395 vec![(
1396 StorageShardedKey::new(address_with_storage, storage_key, u64::MAX),
1397 IntegerList::new([block]).unwrap()
1398 )]
1399 );
1400 }
1401
1402 #[test]
1403 fn dump_state_v2_resets_presnapshot_changeset_static_files() {
1404 let storage_key = B256::with_last_byte(3);
1405 let input = br#"{"address":"0x0000000000000000000000000000000000000002","balance":"0x0","storage":{"0x0000000000000000000000000000000000000000000000000000000000000003":"0x0000000000000000000000000000000000000000000000000000000000000004"}}
1406"#;
1407
1408 let collector = parse_accounts(&input[..], EtlConfig::new(None, 128)).unwrap();
1409 let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
1410 factory.set_storage_settings_cache(StorageSettings::v2());
1411 let static_files = factory.static_file_provider();
1412
1413 {
1414 let mut writer =
1415 static_files.get_writer(0, StaticFileSegment::AccountChangeSets).unwrap();
1416 writer.append_account_changeset(Vec::new(), 0).unwrap();
1417 }
1418 {
1419 let mut writer =
1420 static_files.get_writer(0, StaticFileSegment::StorageChangeSets).unwrap();
1421 writer.append_storage_changeset(Vec::new(), 0).unwrap();
1422 }
1423 static_files.commit().unwrap();
1424
1425 let block = 500_010;
1426 dump_state(collector, &factory, block).unwrap();
1427 static_files.initialize_index().unwrap();
1428
1429 let provider = factory.provider().unwrap();
1430 let address = Address::with_last_byte(2);
1431 assert!(reth_provider::ChangeSetReader::account_block_changeset(&provider, 5)
1432 .unwrap()
1433 .is_empty());
1434 assert!(reth_provider::StorageChangeSetReader::storage_changeset(&provider, 5)
1435 .unwrap()
1436 .is_empty());
1437
1438 let account_file_start =
1439 static_files.find_fixed_range(StaticFileSegment::AccountChangeSets, block).start();
1440 let storage_file_start =
1441 static_files.find_fixed_range(StaticFileSegment::StorageChangeSets, block).start();
1442 assert_eq!(account_file_start, 500_000);
1443 assert_eq!(storage_file_start, 500_000);
1444 assert!(reth_provider::ChangeSetReader::account_block_changeset(
1445 &provider,
1446 account_file_start
1447 )
1448 .unwrap()
1449 .is_empty());
1450 assert!(reth_provider::StorageChangeSetReader::storage_changeset(
1451 &provider,
1452 storage_file_start
1453 )
1454 .unwrap()
1455 .is_empty());
1456
1457 assert_eq!(
1458 reth_provider::ChangeSetReader::account_block_changeset(&provider, block).unwrap(),
1459 vec![AccountBeforeTx { address, info: None }]
1460 );
1461 assert_eq!(
1462 reth_provider::StorageChangeSetReader::storage_changeset(&provider, block).unwrap(),
1463 vec![(
1464 BlockNumberAddress((block, address)),
1465 StorageEntry { key: storage_key, value: U256::ZERO }
1466 )]
1467 );
1468
1469 let account_offsets = static_files
1470 .get_segment_provider_for_block(StaticFileSegment::AccountChangeSets, block, None)
1471 .unwrap()
1472 .read_changeset_offsets()
1473 .unwrap()
1474 .unwrap();
1475 let storage_offsets = static_files
1476 .get_segment_provider_for_block(StaticFileSegment::StorageChangeSets, block, None)
1477 .unwrap()
1478 .read_changeset_offsets()
1479 .unwrap()
1480 .unwrap();
1481 assert_eq!(account_offsets.len() as u64, block - account_file_start + 1);
1482 assert_eq!(storage_offsets.len() as u64, block - storage_file_start + 1);
1483 }
1484
1485 #[test]
1486 fn success_init_genesis_mainnet() {
1487 let genesis_hash =
1488 init_genesis(&create_test_provider_factory_with_chain_spec(MAINNET.clone())).unwrap();
1489
1490 assert_eq!(genesis_hash, MAINNET_GENESIS_HASH);
1492 }
1493
1494 #[test]
1495 fn success_init_genesis_sepolia() {
1496 let genesis_hash =
1497 init_genesis(&create_test_provider_factory_with_chain_spec(SEPOLIA.clone())).unwrap();
1498
1499 assert_eq!(genesis_hash, SEPOLIA_GENESIS_HASH);
1501 }
1502
1503 #[test]
1504 fn success_init_genesis_holesky() {
1505 let genesis_hash =
1506 init_genesis(&create_test_provider_factory_with_chain_spec(HOLESKY.clone())).unwrap();
1507
1508 assert_eq!(genesis_hash, HOLESKY_GENESIS_HASH);
1510 }
1511
1512 #[test]
1513 fn fail_init_inconsistent_db() {
1514 let factory = create_test_provider_factory_with_chain_spec(SEPOLIA.clone());
1515 let static_file_provider = factory.static_file_provider();
1516 let rocksdb_provider = factory.rocksdb_provider();
1517 init_genesis(&factory).unwrap();
1518
1519 let genesis_hash = init_genesis(
1521 &ProviderFactory::<MockNodeTypesWithDB>::new(
1522 factory.into_db(),
1523 MAINNET.clone(),
1524 static_file_provider,
1525 rocksdb_provider,
1526 reth_tasks::Runtime::test(),
1527 )
1528 .unwrap(),
1529 );
1530
1531 assert!(matches!(
1532 genesis_hash.unwrap_err(),
1533 InitStorageError::GenesisHashMismatch {
1534 chainspec_hash: MAINNET_GENESIS_HASH,
1535 storage_hash: SEPOLIA_GENESIS_HASH
1536 }
1537 ))
1538 }
1539
1540 #[test]
1541 fn skip_genesis_hash_validation_accepts_mismatched_db() {
1542 let factory = create_test_provider_factory_with_chain_spec(SEPOLIA.clone());
1543 let static_file_provider = factory.static_file_provider();
1544 let rocksdb_provider = factory.rocksdb_provider();
1545 init_genesis(&factory).unwrap();
1546
1547 let result = init_genesis_with_settings_and_validate(
1548 &ProviderFactory::<MockNodeTypesWithDB>::new(
1549 factory.into_db(),
1550 MAINNET.clone(),
1551 static_file_provider,
1552 rocksdb_provider,
1553 reth_tasks::Runtime::test(),
1554 )
1555 .unwrap(),
1556 StorageSettings::base(),
1557 false,
1558 );
1559
1560 let returned = result.expect("skip_genesis_validation should suppress mismatch error");
1561 assert_eq!(
1562 returned, SEPOLIA_GENESIS_HASH,
1563 "bypass returns the DB-resident hash, not the chainspec hash",
1564 );
1565 }
1566
1567 #[test]
1568 fn init_genesis_history() {
1569 let address_with_balance = Address::with_last_byte(1);
1570 let address_with_storage = Address::with_last_byte(2);
1571 let storage_key = B256::with_last_byte(1);
1572 let chain_spec = Arc::new(ChainSpec {
1573 chain: Chain::from_id(1),
1574 genesis: Genesis {
1575 alloc: BTreeMap::from([
1576 (
1577 address_with_balance,
1578 GenesisAccount { balance: U256::ONE, ..Default::default() },
1579 ),
1580 (
1581 address_with_storage,
1582 GenesisAccount {
1583 storage: Some(BTreeMap::from([(storage_key, B256::random())])),
1584 ..Default::default()
1585 },
1586 ),
1587 ]),
1588 ..Default::default()
1589 },
1590 hardforks: Default::default(),
1591 paris_block_and_final_difficulty: None,
1592 deposit_contract: None,
1593 ..Default::default()
1594 });
1595
1596 let factory = create_test_provider_factory_with_chain_spec(chain_spec);
1597 init_genesis(&factory).unwrap();
1598
1599 let expected_accounts = vec![
1600 (ShardedKey::new(address_with_balance, u64::MAX), IntegerList::new([0]).unwrap()),
1601 (ShardedKey::new(address_with_storage, u64::MAX), IntegerList::new([0]).unwrap()),
1602 ];
1603 let expected_storages = vec![(
1604 StorageShardedKey::new(address_with_storage, storage_key, u64::MAX),
1605 IntegerList::new([0]).unwrap(),
1606 )];
1607
1608 let collect_from_mdbx = |factory: &ProviderFactory<MockNodeTypesWithDB>| {
1609 let provider = factory.provider().unwrap();
1610 let tx = provider.tx_ref();
1611 (
1612 collect_table_entries::<DatabaseEnv, tables::AccountsHistory>(tx).unwrap(),
1613 collect_table_entries::<DatabaseEnv, tables::StoragesHistory>(tx).unwrap(),
1614 )
1615 };
1616
1617 {
1618 let settings = factory.cached_storage_settings();
1619 let rocksdb = factory.rocksdb_provider();
1620
1621 let collect_rocksdb = |rocksdb: &reth_provider::providers::RocksDBProvider| {
1622 (
1623 rocksdb
1624 .iter::<tables::AccountsHistory>()
1625 .unwrap()
1626 .collect::<Result<Vec<_>, _>>()
1627 .unwrap(),
1628 rocksdb
1629 .iter::<tables::StoragesHistory>()
1630 .unwrap()
1631 .collect::<Result<Vec<_>, _>>()
1632 .unwrap(),
1633 )
1634 };
1635
1636 let (accounts, storages) = if settings.storage_v2 {
1637 collect_rocksdb(&rocksdb)
1638 } else {
1639 collect_from_mdbx(&factory)
1640 };
1641 assert_eq!(accounts, expected_accounts);
1642 assert_eq!(storages, expected_storages);
1643 }
1644 }
1645
1646 #[test]
1647 fn warn_storage_settings_mismatch() {
1648 let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
1649 init_genesis_with_settings(&factory, StorageSettings::v1()).unwrap();
1650
1651 let result = init_genesis_with_settings(&factory, StorageSettings::v2());
1653
1654 assert!(result.is_ok());
1656 }
1657
1658 #[test]
1659 fn allow_same_storage_settings() {
1660 let factory = create_test_provider_factory_with_chain_spec(MAINNET.clone());
1661 let settings = StorageSettings::v2();
1662 init_genesis_with_settings(&factory, settings).unwrap();
1663
1664 let result = init_genesis_with_settings(&factory, settings);
1665
1666 assert!(result.is_ok());
1667 }
1668}