Compare commits

...

1 Commits

Author SHA1 Message Date
Arsenii Kulikov
3a18325e0e flush 2026-03-17 21:39:49 +01:00

View File

@@ -9,9 +9,7 @@ use crate::StaticFileProviderFactory;
use alloy_eips::eip2718::Encodable2718;
use alloy_primitives::BlockNumber;
use rayon::prelude::*;
use reth_chainspec::{ChainSpecProvider, EthChainSpec};
use reth_db::models::{storage_sharded_key::StorageShardedKey, ShardedKey};
use reth_db_api::tables;
use reth_db_api::{table::Table, tables};
use reth_stages_types::StageId;
use reth_static_file_types::StaticFileSegment;
use reth_storage_api::{
@@ -59,8 +57,7 @@ impl RocksDBProvider {
+ BlockBodyIndicesProvider
+ StorageChangeSetReader
+ ChangeSetReader
+ TransactionsProvider<Transaction: Encodable2718>
+ ChainSpecProvider,
+ TransactionsProvider<Transaction: Encodable2718>,
{
let mut unwind_target: Option<BlockNumber> = None;
@@ -117,14 +114,11 @@ impl RocksDBProvider {
// Fast path: clear any stale data and return.
if checkpoint == 0 {
if self.first::<tables::TransactionHashNumbers>()?.is_some() {
tracing::info!(
target: "reth::providers::rocksdb",
"TransactionHashNumbers: checkpoint is 0, clearing stale data"
);
self.clear::<tables::TransactionHashNumbers>()?;
}
tracing::info!(
target: "reth::providers::rocksdb",
"TransactionHashNumbers: checkpoint is 0, clearing stale data"
);
self.clear::<tables::TransactionHashNumbers>()?;
return Ok(None);
}
@@ -262,39 +256,21 @@ impl RocksDBProvider {
provider: &Provider,
) -> ProviderResult<Option<BlockNumber>>
where
Provider: DBProvider
+ StageCheckpointReader
+ StaticFileProviderFactory
+ StorageChangeSetReader
+ ChainSpecProvider,
Provider:
DBProvider + StageCheckpointReader + StaticFileProviderFactory + StorageChangeSetReader,
{
let checkpoint = provider
.get_stage_checkpoint(StageId::IndexStorageHistory)?
.map(|cp| cp.block_number)
.unwrap_or(0);
// Fast path: clear and re-insert genesis history.
// Fast path: clear any stale data and return.
if checkpoint == 0 {
tracing::info!(
target: "reth::providers::rocksdb",
"StoragesHistory: checkpoint is 0, clearing stale data"
);
self.clear::<tables::StoragesHistory>()?;
let chain_spec = provider.chain_spec();
let genesis = chain_spec.genesis();
let list = tables::BlockNumberList::new([0]).expect("single block always fits");
for (addr, account) in &genesis.alloc {
if let Some(storage) = &account.storage {
for key in storage.keys() {
self.put::<tables::StoragesHistory>(
StorageShardedKey::last(*addr, *key),
&list,
)?;
}
}
}
return Ok(None);
}
@@ -358,6 +334,7 @@ impl RocksDBProvider {
let batch = self.unwind_storage_history_indices(&indices)?;
self.commit_batch(batch)?;
self.flush(&[tables::StoragesHistory::NAME])?;
}
batch_start = batch_end + 1;
@@ -375,32 +352,20 @@ impl RocksDBProvider {
provider: &Provider,
) -> ProviderResult<Option<BlockNumber>>
where
Provider: DBProvider
+ StageCheckpointReader
+ StaticFileProviderFactory
+ ChangeSetReader
+ ChainSpecProvider,
Provider: DBProvider + StageCheckpointReader + StaticFileProviderFactory + ChangeSetReader,
{
let checkpoint = provider
.get_stage_checkpoint(StageId::IndexAccountHistory)?
.map(|cp| cp.block_number)
.unwrap_or(0);
// Fast path: clear and re-insert genesis history.
// Fast path: clear any stale data and return.
if checkpoint == 0 {
tracing::info!(
target: "reth::providers::rocksdb",
"AccountsHistory: checkpoint is 0, clearing stale data"
);
self.clear::<tables::AccountsHistory>()?;
let chain_spec = provider.chain_spec();
let genesis = chain_spec.genesis();
let list = tables::BlockNumberList::new([0]).expect("single block always fits");
for addr in genesis.alloc.keys() {
self.put::<tables::AccountsHistory>(ShardedKey::last(*addr), &list)?;
}
return Ok(None);
}
@@ -474,18 +439,13 @@ impl RocksDBProvider {
#[cfg(test)]
mod tests {
use std::sync::Arc;
use super::*;
use crate::{
init::insert_genesis_history,
providers::{rocksdb::RocksDBBuilder, static_file::StaticFileWriter},
test_utils::{create_test_provider_factory, create_test_provider_factory_with_chain_spec},
BlockWriter, DatabaseProviderFactory, RocksDBProviderFactory, StageCheckpointWriter,
TransactionsProvider,
test_utils::create_test_provider_factory,
BlockWriter, DatabaseProviderFactory, StageCheckpointWriter, TransactionsProvider,
};
use alloy_primitives::{Address, B256};
use reth_chainspec::MAINNET;
use reth_db::cursor::{DbCursorRO, DbCursorRW};
use reth_db_api::{
models::{storage_sharded_key::StorageShardedKey, StorageSettings},
@@ -569,14 +529,11 @@ mod tests {
let result = rocksdb.heal_storages_history(&provider).unwrap();
assert_eq!(result, None, "StoragesHistory should return early at checkpoint 0");
assert!(rocksdb.first::<tables::StoragesHistory>().unwrap().is_none());
let result = rocksdb.heal_accounts_history(&provider).unwrap();
assert_eq!(result, None, "AccountsHistory should return early at checkpoint 0");
// Genesis account history entries are re-inserted
assert_eq!(
rocksdb.iter::<tables::AccountsHistory>().unwrap().count(),
factory.chain_spec().genesis().alloc.len()
);
assert!(rocksdb.first::<tables::AccountsHistory>().unwrap().is_none());
}
#[test]
@@ -703,38 +660,35 @@ mod tests {
}
#[test]
fn test_check_consistency_storages_history_preserves_genesis_entries_at_checkpoint_zero(
) -> eyre::Result<()> {
// Modify mainnet chainspec to include a single genesis storage slot
let mut chain_spec = MAINNET.clone();
Arc::make_mut(&mut chain_spec).genesis.alloc.first_entry().unwrap().get_mut().storage =
Some(From::from([(B256::random(), B256::random())]));
fn test_check_consistency_storages_history_has_data_no_checkpoint_prunes_data() {
let temp_dir = TempDir::new().unwrap();
let rocksdb = RocksDBBuilder::new(temp_dir.path()).with_default_tables().build().unwrap();
// Insert data into RocksDB
let key = StorageShardedKey::new(Address::ZERO, B256::ZERO, 50);
let block_list = BlockNumberList::new_pre_sorted([10, 20, 30, 50]);
rocksdb.put::<tables::StoragesHistory>(key, &block_list).unwrap();
// Verify data exists
assert!(rocksdb.last::<tables::StoragesHistory>().unwrap().is_some());
// Create a test provider factory for MDBX with NO checkpoint
let factory = create_test_provider_factory_with_chain_spec(chain_spec);
let rocksdb = factory.rocksdb_provider();
let factory = create_test_provider_factory();
factory.set_storage_settings_cache(StorageSettings::v2());
// Insert genesis history into RocksDB
let provider_rw = factory.database_provider_rw().unwrap();
insert_genesis_history(&provider_rw, factory.chain_spec().genesis.alloc.iter())?;
provider_rw.commit()?;
let provider = factory.database_provider_ro().unwrap();
// This should not prune anything because only genesis entries are present
let result = rocksdb.heal_storages_history(&provider).unwrap();
assert_eq!(result, None, "Should skip healing when only genesis entries present");
// RocksDB has data but checkpoint is 0
// This means RocksDB has stale data that should be pruned (healed)
let result = rocksdb.check_consistency(&provider).unwrap();
assert_eq!(result, None, "Should heal by pruning, no unwind needed");
// Verify data was NOT deleted
// Verify data was pruned
assert!(
rocksdb.iter::<tables::StoragesHistory>().unwrap().count() > 0,
"Genesis entries should be preserved"
rocksdb.last::<tables::StoragesHistory>().unwrap().is_none(),
"RocksDB should be empty after pruning"
);
Ok(())
}
#[test]
fn test_check_consistency_mdbx_behind_checkpoint_needs_unwind() {
let temp_dir = TempDir::new().unwrap();
@@ -1114,31 +1068,36 @@ mod tests {
}
#[test]
fn test_check_consistency_accounts_history_preserves_genesis_entries_at_checkpoint_zero(
) -> eyre::Result<()> {
fn test_check_consistency_accounts_history_has_data_no_checkpoint_prunes_data() {
use reth_db_api::models::ShardedKey;
let temp_dir = TempDir::new().unwrap();
let rocksdb = RocksDBBuilder::new(temp_dir.path()).with_default_tables().build().unwrap();
// Insert data into RocksDB
let key = ShardedKey::new(Address::ZERO, 50);
let block_list = BlockNumberList::new_pre_sorted([10, 20, 30, 50]);
rocksdb.put::<tables::AccountsHistory>(key, &block_list).unwrap();
// Verify data exists
assert!(rocksdb.last::<tables::AccountsHistory>().unwrap().is_some());
// Create a test provider factory for MDBX with NO checkpoint
let factory = create_test_provider_factory();
factory.set_storage_settings_cache(StorageSettings::v2());
let rocksdb = factory.rocksdb_provider();
// Insert genesis history into RocksDB
let provider_rw = factory.database_provider_rw().unwrap();
insert_genesis_history(&provider_rw, factory.chain_spec().genesis.alloc.iter())?;
provider_rw.commit()?;
let provider = factory.database_provider_ro().unwrap();
// This should not prune anything because only genesis entries are present
// RocksDB has data but checkpoint is 0
// This means RocksDB has stale data that should be pruned (healed)
let result = rocksdb.check_consistency(&provider).unwrap();
assert_eq!(result, None, "Should heal by pruning, no unwind needed");
// Verify data was NOT deleted
// Verify data was pruned
assert!(
rocksdb.iter::<tables::AccountsHistory>().unwrap().count() > 0,
"Genesis entries should be preserved"
rocksdb.last::<tables::AccountsHistory>().unwrap().is_none(),
"RocksDB should be empty after pruning"
);
Ok(())
}
#[test]