diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 2a3bef156f6..86dec1cfa48 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -541,10 +541,16 @@ where (None, Some(_)) => "static file", (None, None) => unreachable!(), }; - assert_ne!( - unwind_block, 0, - "A {} inconsistency was found that would trigger an unwind to block 0", - inconsistency_source + + let genesis_block = self.chain_spec().genesis().number.unwrap_or(0); + + assert!( + unwind_block >= genesis_block, + "A {} inconsistency was found that would trigger an unwind to block {} (genesis is {}). \ + This would cause data loss. Check RocksDB consistency logic for custom genesis support.", + inconsistency_source, + unwind_block, + genesis_block ); let unwind_target = PipelineTarget::Unwind(unwind_block); diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index e9dd5344c4a..f4246175f3a 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -126,6 +126,24 @@ impl EngineNodeLauncher { .with_genesis()? .inspect(|this: &LaunchContextWith::ChainSpec>, _>>| { info!(target: "reth::cli", "\n{}", this.chain_spec().display_hardforks()); + + // Log storage settings after genesis initialization + match this.provider_factory().storage_settings() { + Ok(settings) => { + info!( + target: "reth::cli", + ?settings, + "Storage settings (after genesis)" + ); + }, + Err(err) => { + warn!( + target: "reth::cli", + ?err, + "Failed to get storage settings after genesis" + ); + }, + } }) .with_metrics_task() // passing FullNodeTypes as type parameter here so that we can build diff --git a/crates/node/core/src/args/static_files.rs b/crates/node/core/src/args/static_files.rs index 44116dd84b5..d2b7f52e408 100644 --- a/crates/node/core/src/args/static_files.rs +++ b/crates/node/core/src/args/static_files.rs @@ -97,9 +97,19 @@ impl StaticFilesArgs { /// Converts the static files arguments into [`StorageSettings`]. pub const fn to_settings(&self) -> StorageSettings { - StorageSettings::legacy() - .with_receipts_in_static_files(self.receipts) - .with_transaction_senders_in_static_files(self.transaction_senders) - .with_account_changesets_in_static_files(self.account_changesets) + #[cfg(feature = "edge")] + { + StorageSettings::edge() + .with_receipts_in_static_files(self.receipts) + .with_transaction_senders_in_static_files(self.transaction_senders) + .with_account_changesets_in_static_files(self.account_changesets) + } + #[cfg(not(feature = "edge"))] + { + StorageSettings::legacy() + .with_receipts_in_static_files(self.receipts) + .with_transaction_senders_in_static_files(self.transaction_senders) + .with_account_changesets_in_static_files(self.account_changesets) + } } } diff --git a/crates/storage/db-api/src/models/metadata.rs b/crates/storage/db-api/src/models/metadata.rs index 6fa9ea6443e..12bc04d1973 100644 --- a/crates/storage/db-api/src/models/metadata.rs +++ b/crates/storage/db-api/src/models/metadata.rs @@ -44,9 +44,9 @@ impl StorageSettings { receipts_in_static_files: true, transaction_senders_in_static_files: true, account_changesets_in_static_files: true, - storages_history_in_rocksdb: false, - transaction_hash_numbers_in_rocksdb: false, - account_history_in_rocksdb: false, + storages_history_in_rocksdb: true, + transaction_hash_numbers_in_rocksdb: true, + account_history_in_rocksdb: true, } } diff --git a/crates/storage/provider/src/providers/rocksdb/invariants.rs b/crates/storage/provider/src/providers/rocksdb/invariants.rs index 7a5c5f9db30..d00d5fb2c38 100644 --- a/crates/storage/provider/src/providers/rocksdb/invariants.rs +++ b/crates/storage/provider/src/providers/rocksdb/invariants.rs @@ -17,6 +17,7 @@ use reth_storage_api::{ DBProvider, StageCheckpointReader, StorageSettingsCache, TransactionsProvider, }; use reth_storage_errors::provider::ProviderResult; +use reth_chainspec::{ChainSpecProvider, EthChainSpec}; impl RocksDBProvider { /// Checks consistency of `RocksDB` tables against MDBX stage checkpoints. @@ -51,27 +52,28 @@ impl RocksDBProvider { + StageCheckpointReader + StorageSettingsCache + StaticFileProviderFactory - + TransactionsProvider, + + TransactionsProvider + + ChainSpecProvider, { let mut unwind_target: Option = None; // Check TransactionHashNumbers if stored in RocksDB - if provider.cached_storage_settings().transaction_hash_numbers_in_rocksdb && - let Some(target) = self.check_transaction_hash_numbers(provider)? + if provider.cached_storage_settings().transaction_hash_numbers_in_rocksdb + && let Some(target) = self.check_transaction_hash_numbers(provider)? { unwind_target = Some(unwind_target.map_or(target, |t| t.min(target))); } // Check StoragesHistory if stored in RocksDB - if provider.cached_storage_settings().storages_history_in_rocksdb && - let Some(target) = self.check_storages_history(provider)? + if provider.cached_storage_settings().storages_history_in_rocksdb + && let Some(target) = self.check_storages_history(provider)? { unwind_target = Some(unwind_target.map_or(target, |t| t.min(target))); } // Check AccountsHistory if stored in RocksDB - if provider.cached_storage_settings().account_history_in_rocksdb && - let Some(target) = self.check_accounts_history(provider)? + if provider.cached_storage_settings().account_history_in_rocksdb + && let Some(target) = self.check_accounts_history(provider)? { unwind_target = Some(unwind_target.map_or(target, |t| t.min(target))); } @@ -99,7 +101,8 @@ impl RocksDBProvider { Provider: DBProvider + StageCheckpointReader + StaticFileProviderFactory - + TransactionsProvider, + + TransactionsProvider + + ChainSpecProvider, { // Get the TransactionLookup stage checkpoint let checkpoint = provider @@ -167,12 +170,37 @@ impl RocksDBProvider { // Both MDBX and static files are empty. // If checkpoint says we should have data, that's an inconsistency. if checkpoint > 0 { - tracing::warn!( - target: "reth::providers::rocksdb", - checkpoint, - "Checkpoint set but no transaction data exists, unwind needed" - ); - return Ok(Some(0)); + // For nodes with custom genesis blocks, having empty data at genesis + // checkpoint is normal. Only trigger unwind if checkpoint is beyond genesis. + let genesis_block = provider.chain_spec().genesis().number.unwrap_or(0); + + if checkpoint > genesis_block { + tracing::warn!( + target: "reth::providers::rocksdb", + checkpoint, + genesis_block, + "Checkpoint beyond genesis but no transaction data exists, unwind needed" + ); + return Ok(Some(genesis_block)); + } else if checkpoint == genesis_block { + // Checkpoint is at genesis with empty data - this is normal for custom genesis. + // No unwind needed, RocksDB will start populating from genesis forward. + tracing::info!( + target: "reth::providers::rocksdb", + genesis_block, + "RocksDB checkpoint at custom genesis with empty data - normal state" + ); + return Ok(None); + } else { + // Checkpoint < genesis_block - shouldn't happen but handle gracefully + tracing::warn!( + target: "reth::providers::rocksdb", + checkpoint, + genesis_block, + "Checkpoint below genesis, unwind to genesis needed" + ); + return Ok(Some(genesis_block)); + } } } } @@ -239,7 +267,7 @@ impl RocksDBProvider { provider: &Provider, ) -> ProviderResult> where - Provider: DBProvider + StageCheckpointReader, + Provider: DBProvider + StageCheckpointReader + ChainSpecProvider, { // Get the IndexStorageHistory stage checkpoint let checkpoint = provider @@ -298,8 +326,34 @@ impl RocksDBProvider { None => { // Empty RocksDB table if checkpoint > 0 { - // Stage says we should have data but we don't - return Ok(Some(0)); + // For nodes with custom genesis blocks, having empty data at genesis + // checkpoint is normal. Only trigger unwind if checkpoint is beyond genesis. + let genesis_block = provider.chain_spec().genesis().number.unwrap_or(0); + + if checkpoint > genesis_block { + tracing::warn!( + target: "reth::providers::rocksdb", + checkpoint, + genesis_block, + "StoragesHistory checkpoint beyond genesis but no data exists, unwind needed" + ); + return Ok(Some(genesis_block)); + } else if checkpoint == genesis_block { + tracing::info!( + target: "reth::providers::rocksdb", + genesis_block, + "StoragesHistory at custom genesis with empty data - normal state" + ); + return Ok(None); + } else { + tracing::warn!( + target: "reth::providers::rocksdb", + checkpoint, + genesis_block, + "StoragesHistory checkpoint below genesis, unwind to genesis needed" + ); + return Ok(Some(genesis_block)); + } } Ok(None) } @@ -353,7 +407,7 @@ impl RocksDBProvider { provider: &Provider, ) -> ProviderResult> where - Provider: DBProvider + StageCheckpointReader, + Provider: DBProvider + StageCheckpointReader + ChainSpecProvider, { // Get the IndexAccountHistory stage checkpoint let checkpoint = provider @@ -415,8 +469,34 @@ impl RocksDBProvider { None => { // Empty RocksDB table if checkpoint > 0 { - // Stage says we should have data but we don't - return Ok(Some(0)); + // For nodes with custom genesis blocks, having empty data at genesis + // checkpoint is normal. Only trigger unwind if checkpoint is beyond genesis. + let genesis_block = provider.chain_spec().genesis().number.unwrap_or(0); + + if checkpoint > genesis_block { + tracing::warn!( + target: "reth::providers::rocksdb", + checkpoint, + genesis_block, + "AccountsHistory checkpoint beyond genesis but no data exists, unwind needed" + ); + return Ok(Some(genesis_block)); + } else if checkpoint == genesis_block { + tracing::info!( + target: "reth::providers::rocksdb", + genesis_block, + "AccountsHistory at custom genesis with empty data - normal state" + ); + return Ok(None); + } else { + tracing::warn!( + target: "reth::providers::rocksdb", + checkpoint, + genesis_block, + "AccountsHistory checkpoint below genesis, unwind to genesis needed" + ); + return Ok(Some(genesis_block)); + } } Ok(None) } diff --git a/crates/storage/provider/src/providers/rocksdb/metrics.rs b/crates/storage/provider/src/providers/rocksdb/metrics.rs index 890d9faac2f..18474a3bd29 100644 --- a/crates/storage/provider/src/providers/rocksdb/metrics.rs +++ b/crates/storage/provider/src/providers/rocksdb/metrics.rs @@ -6,7 +6,11 @@ use reth_db::Tables; use reth_metrics::Metrics; use strum::{EnumIter, IntoEnumIterator}; -const ROCKSDB_TABLES: &[&str] = &[Tables::TransactionHashNumbers.name()]; +const ROCKSDB_TABLES: &[&str] = &[ + Tables::TransactionHashNumbers.name(), + Tables::AccountsHistory.name(), + Tables::StoragesHistory.name(), +]; /// Metrics for the `RocksDB` provider. #[derive(Debug)]