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