From ef02024ba1f7c75f690215811078956f09a24b0f Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 7 Aug 2023 12:05:59 +0100 Subject: [PATCH 01/16] feat(storage): account for pruned account/storage history --- .../provider/src/providers/database/mod.rs | 46 +++++++----- .../src/providers/state/historical.rs | 70 +++++++++++++++++-- 2 files changed, 95 insertions(+), 21 deletions(-) diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 5fbb0fceeaed..ce08cbcd1ef1 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -87,7 +87,7 @@ impl ProviderFactory { } /// Storage provider for state at that given block - pub fn history_by_block_number( + fn state_provider_by_block_number( &self, mut block_number: BlockNumber, ) -> Result> { @@ -102,30 +102,42 @@ impl ProviderFactory { // +1 as the changeset that we want is the one that was applied after this block. block_number += 1; + let mut state_provider = HistoricalStateProvider::new(provider.into_tx(), block_number); + + // If we pruned account or storage history, we can't return state on every historical block. + // Instead, we should cap it at the latest prune checkpoint for corresponding prune part. + if let Some(prune_checkpoint) = provider.get_prune_checkpoint(PrunePart::AccountHistory)? { + state_provider = state_provider + .with_latest_account_history_block_number(prune_checkpoint.block_number); + } + if let Some(prune_checkpoint) = provider.get_prune_checkpoint(PrunePart::StorageHistory)? { + state_provider = state_provider + .with_latest_storage_history_block_number(prune_checkpoint.block_number); + } + + Ok(Box::new(state_provider)) + } + + /// Storage provider for state at that given block + pub fn history_by_block_number( + &self, + block_number: BlockNumber, + ) -> Result> { + let state_provider = self.state_provider_by_block_number(block_number)?; trace!(target: "providers::db", ?block_number, "Returning historical state provider for block number"); - Ok(Box::new(HistoricalStateProvider::new(provider.into_tx(), block_number))) + Ok(state_provider) } /// Storage provider for state at that given block hash pub fn history_by_block_hash(&self, block_hash: BlockHash) -> Result> { - let provider = self.provider()?; - - let mut block_number = provider + let block_number = self + .provider()? .block_number(block_hash)? .ok_or(ProviderError::BlockHashNotFound(block_hash))?; - if block_number == provider.best_block_number().unwrap_or_default() && - block_number == provider.last_block_number().unwrap_or_default() - { - return Ok(Box::new(LatestStateProvider::new(provider.into_tx()))) - } - - // +1 as the changeset that we want is the one that was applied after this block. - // as the changeset contains old values. - block_number += 1; - - trace!(target: "providers::db", ?block_hash, "Returning historical state provider for block hash"); - Ok(Box::new(HistoricalStateProvider::new(provider.into_tx(), block_number))) + let state_provider = self.state_provider_by_block_number(block_number)?; + trace!(target: "providers::db", ?block_number, "Returning historical state provider for block hash"); + Ok(state_provider) } } diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 458a4c6421ea..1603f963b738 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -1,6 +1,6 @@ use crate::{ providers::state::macros::delegate_provider_impls, AccountReader, BlockHashReader, PostState, - ProviderError, StateProvider, StateRootProvider, + ProviderError, ProviderFactory, StateProvider, StateRootProvider, }; use reth_db::{ cursor::{DbCursorRO, DbDupCursorRO}, @@ -29,6 +29,8 @@ pub struct HistoricalStateProviderRef<'a, 'b, TX: DbTx<'a>> { tx: &'b TX, /// Block number is main index for the history state of accounts and storages. block_number: BlockNumber, + latest_account_history_block_number: Option, + latest_storage_history_block_number: Option, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, } @@ -42,11 +44,40 @@ pub enum HistoryInfo { impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { /// Create new StateProvider from history transaction number pub fn new(tx: &'b TX, block_number: BlockNumber) -> Self { - Self { tx, block_number, _phantom: PhantomData {} } + Self { + tx, + block_number, + latest_account_history_block_number: None, + latest_storage_history_block_number: None, + _phantom: PhantomData {}, + } + } + + pub fn new_with_latest_history_block_numbers( + tx: &'b TX, + latest_account_history_block_number: Option, + latest_storage_history_block_number: Option, + block_number: BlockNumber, + ) -> Self { + Self { + tx, + block_number, + latest_account_history_block_number, + latest_storage_history_block_number, + _phantom: PhantomData {}, + } } /// Lookup an account in the AccountHistory table pub fn account_history_lookup(&self, address: Address) -> Result { + if self + .latest_account_history_block_number + .map(|block_number| block_number >= self.block_number) + .unwrap_or(false) + { + return Ok(HistoryInfo::NotYetWritten) + } + // history key to search IntegerList of block number changesets. let history_key = ShardedKey::new(address, self.block_number); self.history_info::(history_key, |key| key.key == address) @@ -58,6 +89,14 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { address: Address, storage_key: StorageKey, ) -> Result { + if self + .latest_storage_history_block_number + .map(|block_number| block_number >= self.block_number) + .unwrap_or(false) + { + return Ok(HistoryInfo::NotYetWritten) + } + // history key to search IntegerList of block number changesets. let history_key = StorageShardedKey::new(address, storage_key, self.block_number); self.history_info::(history_key, |key| { @@ -199,6 +238,8 @@ pub struct HistoricalStateProvider<'a, TX: DbTx<'a>> { tx: TX, /// State at the block number is the main indexer of the state. block_number: BlockNumber, + latest_account_history_block_number: Option, + latest_storage_history_block_number: Option, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, } @@ -206,13 +247,34 @@ pub struct HistoricalStateProvider<'a, TX: DbTx<'a>> { impl<'a, TX: DbTx<'a>> HistoricalStateProvider<'a, TX> { /// Create new StateProvider from history transaction number pub fn new(tx: TX, block_number: BlockNumber) -> Self { - Self { tx, block_number, _phantom: PhantomData {} } + Self { + tx, + block_number, + latest_account_history_block_number: None, + latest_storage_history_block_number: None, + _phantom: PhantomData {}, + } + } + + pub fn with_latest_account_history_block_number(mut self, block_number: BlockNumber) -> Self { + self.latest_account_history_block_number = Some(block_number); + self + } + + pub fn with_latest_storage_history_block_number(mut self, block_number: BlockNumber) -> Self { + self.latest_storage_history_block_number = Some(block_number); + self } /// Returns a new provider that takes the `TX` as reference #[inline(always)] fn as_ref<'b>(&'b self) -> HistoricalStateProviderRef<'a, 'b, TX> { - HistoricalStateProviderRef::new(&self.tx, self.block_number) + HistoricalStateProviderRef::new_with_latest_history_block_numbers( + &self.tx, + self.latest_account_history_block_number, + self.latest_storage_history_block_number, + self.block_number, + ) } } From 01018bb0a0496c276874f1f3076c57246807cfe5 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 7 Aug 2023 12:29:07 +0100 Subject: [PATCH 02/16] fix borrow --- crates/storage/provider/src/providers/database/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index ce08cbcd1ef1..6f0aa6d13cfe 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -102,15 +102,20 @@ impl ProviderFactory { // +1 as the changeset that we want is the one that was applied after this block. block_number += 1; + let account_history_prune_checkpoint = + provider.get_prune_checkpoint(PrunePart::AccountHistory)?; + let storage_history_prune_checkpoint = + provider.get_prune_checkpoint(PrunePart::StorageHistory)?; + let mut state_provider = HistoricalStateProvider::new(provider.into_tx(), block_number); // If we pruned account or storage history, we can't return state on every historical block. // Instead, we should cap it at the latest prune checkpoint for corresponding prune part. - if let Some(prune_checkpoint) = provider.get_prune_checkpoint(PrunePart::AccountHistory)? { + if let Some(prune_checkpoint) = account_history_prune_checkpoint { state_provider = state_provider .with_latest_account_history_block_number(prune_checkpoint.block_number); } - if let Some(prune_checkpoint) = provider.get_prune_checkpoint(PrunePart::StorageHistory)? { + if let Some(prune_checkpoint) = storage_history_prune_checkpoint { state_provider = state_provider .with_latest_storage_history_block_number(prune_checkpoint.block_number); } From 0473aeec2c262e019cb4eaed636daa86efa0be04 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 7 Aug 2023 14:09:52 +0100 Subject: [PATCH 03/16] return error on pruned block --- crates/interfaces/src/provider.rs | 2 + .../provider/src/providers/database/mod.rs | 4 +- .../src/providers/state/historical.rs | 58 ++++++++++--------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/crates/interfaces/src/provider.rs b/crates/interfaces/src/provider.rs index f9ed2a8dc4a1..5359aa6d7218 100644 --- a/crates/interfaces/src/provider.rs +++ b/crates/interfaces/src/provider.rs @@ -94,4 +94,6 @@ pub enum ProviderError { /// Block hash block_hash: BlockHash, }, + #[error("State at block #{0} is pruned")] + StateAtBlockPruned(BlockNumber), } diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 6f0aa6d13cfe..5004da8366b3 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -113,11 +113,11 @@ impl ProviderFactory { // Instead, we should cap it at the latest prune checkpoint for corresponding prune part. if let Some(prune_checkpoint) = account_history_prune_checkpoint { state_provider = state_provider - .with_latest_account_history_block_number(prune_checkpoint.block_number); + .with_lowest_account_history_block_number(prune_checkpoint.block_number + 1); } if let Some(prune_checkpoint) = storage_history_prune_checkpoint { state_provider = state_provider - .with_latest_storage_history_block_number(prune_checkpoint.block_number); + .with_lowest_storage_history_block_number(prune_checkpoint.block_number + 1); } Ok(Box::new(state_provider)) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 1603f963b738..6a72312569ca 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -1,6 +1,6 @@ use crate::{ providers::state::macros::delegate_provider_impls, AccountReader, BlockHashReader, PostState, - ProviderError, ProviderFactory, StateProvider, StateRootProvider, + ProviderError, StateProvider, StateRootProvider, }; use reth_db::{ cursor::{DbCursorRO, DbDupCursorRO}, @@ -29,8 +29,8 @@ pub struct HistoricalStateProviderRef<'a, 'b, TX: DbTx<'a>> { tx: &'b TX, /// Block number is main index for the history state of accounts and storages. block_number: BlockNumber, - latest_account_history_block_number: Option, - latest_storage_history_block_number: Option, + lowest_account_history_block_number: Option, + lowest_storage_history_block_number: Option, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, } @@ -47,35 +47,37 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { Self { tx, block_number, - latest_account_history_block_number: None, - latest_storage_history_block_number: None, + lowest_account_history_block_number: None, + lowest_storage_history_block_number: None, _phantom: PhantomData {}, } } - pub fn new_with_latest_history_block_numbers( + pub fn new_with_lowest_history_block_numbers( tx: &'b TX, - latest_account_history_block_number: Option, - latest_storage_history_block_number: Option, + lowest_account_history_block_number: Option, + lowest_storage_history_block_number: Option, block_number: BlockNumber, ) -> Self { Self { tx, block_number, - latest_account_history_block_number, - latest_storage_history_block_number, + lowest_account_history_block_number, + lowest_storage_history_block_number, _phantom: PhantomData {}, } } /// Lookup an account in the AccountHistory table pub fn account_history_lookup(&self, address: Address) -> Result { + // Check if lowest available block number for storage history is more than the requested + // block number for this historical provider instance. if self - .latest_account_history_block_number - .map(|block_number| block_number >= self.block_number) + .lowest_account_history_block_number + .map(|block_number| block_number > self.block_number) .unwrap_or(false) { - return Ok(HistoryInfo::NotYetWritten) + return Err(ProviderError::StateAtBlockPruned(self.block_number).into()) } // history key to search IntegerList of block number changesets. @@ -89,12 +91,14 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { address: Address, storage_key: StorageKey, ) -> Result { + // Check if lowest available block number for account history is more than the requested + // block number for this historical provider instance. if self - .latest_storage_history_block_number - .map(|block_number| block_number >= self.block_number) + .lowest_storage_history_block_number + .map(|block_number| block_number > self.block_number) .unwrap_or(false) { - return Ok(HistoryInfo::NotYetWritten) + return Err(ProviderError::StateAtBlockPruned(self.block_number).into()) } // history key to search IntegerList of block number changesets. @@ -238,8 +242,8 @@ pub struct HistoricalStateProvider<'a, TX: DbTx<'a>> { tx: TX, /// State at the block number is the main indexer of the state. block_number: BlockNumber, - latest_account_history_block_number: Option, - latest_storage_history_block_number: Option, + lowest_account_history_block_number: Option, + lowest_storage_history_block_number: Option, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, } @@ -250,29 +254,29 @@ impl<'a, TX: DbTx<'a>> HistoricalStateProvider<'a, TX> { Self { tx, block_number, - latest_account_history_block_number: None, - latest_storage_history_block_number: None, + lowest_account_history_block_number: None, + lowest_storage_history_block_number: None, _phantom: PhantomData {}, } } - pub fn with_latest_account_history_block_number(mut self, block_number: BlockNumber) -> Self { - self.latest_account_history_block_number = Some(block_number); + pub fn with_lowest_account_history_block_number(mut self, block_number: BlockNumber) -> Self { + self.lowest_account_history_block_number = Some(block_number); self } - pub fn with_latest_storage_history_block_number(mut self, block_number: BlockNumber) -> Self { - self.latest_storage_history_block_number = Some(block_number); + pub fn with_lowest_storage_history_block_number(mut self, block_number: BlockNumber) -> Self { + self.lowest_storage_history_block_number = Some(block_number); self } /// Returns a new provider that takes the `TX` as reference #[inline(always)] fn as_ref<'b>(&'b self) -> HistoricalStateProviderRef<'a, 'b, TX> { - HistoricalStateProviderRef::new_with_latest_history_block_numbers( + HistoricalStateProviderRef::new_with_lowest_history_block_numbers( &self.tx, - self.latest_account_history_block_number, - self.latest_storage_history_block_number, + self.lowest_account_history_block_number, + self.lowest_storage_history_block_number, self.block_number, ) } From 40f5a690f6f4d2ab1347d5ad8dc4c204cd284c28 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 7 Aug 2023 14:37:11 +0100 Subject: [PATCH 04/16] fix lint --- .../provider/src/providers/state/historical.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 6a72312569ca..c172048f5653 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -29,7 +29,9 @@ pub struct HistoricalStateProviderRef<'a, 'b, TX: DbTx<'a>> { tx: &'b TX, /// Block number is main index for the history state of accounts and storages. block_number: BlockNumber, + /// Lowest block number at which the account history is available. lowest_account_history_block_number: Option, + /// Lowest block number at which the storage history is available. lowest_storage_history_block_number: Option, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, @@ -42,7 +44,7 @@ pub enum HistoryInfo { } impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { - /// Create new StateProvider from history transaction number + /// Create new StateProvider for historical block number pub fn new(tx: &'b TX, block_number: BlockNumber) -> Self { Self { tx, @@ -53,6 +55,8 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { } } + /// Create new StateProvider for historical block number and lowest block numbers at which + /// account & storage histories are available. pub fn new_with_lowest_history_block_numbers( tx: &'b TX, lowest_account_history_block_number: Option, @@ -242,14 +246,16 @@ pub struct HistoricalStateProvider<'a, TX: DbTx<'a>> { tx: TX, /// State at the block number is the main indexer of the state. block_number: BlockNumber, + /// Lowest block number at which the account history is available. lowest_account_history_block_number: Option, + /// Lowest block number at which the storage history is available. lowest_storage_history_block_number: Option, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, } impl<'a, TX: DbTx<'a>> HistoricalStateProvider<'a, TX> { - /// Create new StateProvider from history transaction number + /// Create new StateProvider for historical block number pub fn new(tx: TX, block_number: BlockNumber) -> Self { Self { tx, @@ -260,11 +266,13 @@ impl<'a, TX: DbTx<'a>> HistoricalStateProvider<'a, TX> { } } + /// Set the lowest block number at which the account history is available. pub fn with_lowest_account_history_block_number(mut self, block_number: BlockNumber) -> Self { self.lowest_account_history_block_number = Some(block_number); self } + /// Set the lowest block number at which the storage history is available. pub fn with_lowest_storage_history_block_number(mut self, block_number: BlockNumber) -> Self { self.lowest_storage_history_block_number = Some(block_number); self From a250639b21a0dfdfbc344daec2bd280f6087174d Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 8 Aug 2023 14:01:01 +0100 Subject: [PATCH 05/16] fix(storage): lookup in plain state for pruned node --- crates/prune/src/pruner.rs | 13 ++- .../src/providers/state/historical.rs | 79 ++++++++++++------- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/crates/prune/src/pruner.rs b/crates/prune/src/pruner.rs index 913aede6780f..cf137d668652 100644 --- a/crates/prune/src/pruner.rs +++ b/crates/prune/src/pruner.rs @@ -490,6 +490,7 @@ impl Pruner { { let mut processed = 0; let mut cursor = provider.tx_ref().cursor_write::()?; + // Prune history table: // 1. If the shard has `highest_block_number` less than or equal to the target block number // for pruning, delete the shard completely. @@ -525,20 +526,24 @@ impl Pruner { // If there are no more blocks in this shard, we need to remove it, as empty // shards are not allowed. if key.as_ref().highest_block_number == u64::MAX { - // If current shard is the last shard for this sharded key, replace it - // with the previous shard. if let Some(prev_value) = cursor .prev()? .filter(|(prev_key, _)| key_matches(prev_key, &key)) .map(|(_, prev_value)| prev_value) { + // If current shard is the last shard for the sharded key that has + // previous shards, replace it with the previous shard. cursor.delete_current()?; // Upsert will replace the last shard for this sharded key with the - // previous value + // previous value. cursor.upsert(key.clone(), prev_value)?; } else { // If there's no previous shard for this sharded key, // just delete last shard completely. + + // Jump back to the original last shard. + cursor.next()?; + // Delete shard. cursor.delete_current()?; } } else { @@ -551,7 +556,7 @@ impl Pruner { } } - // Jump to the next address + // Jump to the next address. cursor.seek_exact(last_key(&key))?; } diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index c172048f5653..3c78290b17de 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -30,8 +30,10 @@ pub struct HistoricalStateProviderRef<'a, 'b, TX: DbTx<'a>> { /// Block number is main index for the history state of accounts and storages. block_number: BlockNumber, /// Lowest block number at which the account history is available. + /// [Option::None] means all history is available. lowest_account_history_block_number: Option, /// Lowest block number at which the storage history is available. + /// [Option::None] means all history is available. lowest_storage_history_block_number: Option, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, @@ -41,6 +43,7 @@ pub enum HistoryInfo { NotYetWritten, InChangeset(u64), InPlainState, + MaybeInPlainState, } impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { @@ -86,7 +89,11 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { // history key to search IntegerList of block number changesets. let history_key = ShardedKey::new(address, self.block_number); - self.history_info::(history_key, |key| key.key == address) + self.history_info::( + history_key, + |key| key.key == address, + self.lowest_account_history_block_number, + ) } /// Lookup a storage key in the StorageHistory table @@ -107,12 +114,19 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { // history key to search IntegerList of block number changesets. let history_key = StorageShardedKey::new(address, storage_key, self.block_number); - self.history_info::(history_key, |key| { - key.address == address && key.sharded_key.key == storage_key - }) + self.history_info::( + history_key, + |key| key.address == address && key.sharded_key.key == storage_key, + self.lowest_storage_history_block_number, + ) } - fn history_info(&self, key: K, key_filter: impl Fn(&K) -> bool) -> Result + fn history_info( + &self, + key: K, + key_filter: impl Fn(&K) -> bool, + lowest_available_block_number: Option, + ) -> Result where T: Table, { @@ -146,8 +160,14 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { Ok(HistoryInfo::InPlainState) } } else { - // The key has not been written to at all. - Ok(HistoryInfo::NotYetWritten) + if lowest_available_block_number.is_some() { + // The key may have been written, but due to pruning we may not have changesets and + // history, so we need to make a plain state lookup. + Ok(HistoryInfo::MaybeInPlainState) + } else { + // The key has not been written to at all. + Ok(HistoryInfo::NotYetWritten) + } } } } @@ -155,20 +175,23 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { impl<'a, 'b, TX: DbTx<'a>> AccountReader for HistoricalStateProviderRef<'a, 'b, TX> { /// Get basic account information. fn basic_account(&self, address: Address) -> Result> { - match self.account_history_lookup(address)? { - HistoryInfo::NotYetWritten => Ok(None), - HistoryInfo::InChangeset(changeset_block_number) => Ok(self - .tx - .cursor_dup_read::()? - .seek_by_key_subkey(changeset_block_number, address)? - .filter(|acc| acc.address == address) - .ok_or(ProviderError::AccountChangesetNotFound { - block_number: changeset_block_number, - address, - })? - .info), - HistoryInfo::InPlainState => Ok(self.tx.get::(address)?), - } + Ok(match self.account_history_lookup(address)? { + HistoryInfo::NotYetWritten => None, + HistoryInfo::InChangeset(changeset_block_number) => { + self.tx + .cursor_dup_read::()? + .seek_by_key_subkey(changeset_block_number, address)? + .filter(|acc| acc.address == address) + .ok_or(ProviderError::AccountChangesetNotFound { + block_number: changeset_block_number, + address, + })? + .info + } + HistoryInfo::InPlainState | HistoryInfo::MaybeInPlainState => { + self.tx.get::(address)? + } + }) } } @@ -201,9 +224,9 @@ impl<'a, 'b, TX: DbTx<'a>> StateRootProvider for HistoricalStateProviderRef<'a, impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b, TX> { /// Get storage. fn storage(&self, address: Address, storage_key: StorageKey) -> Result> { - match self.storage_history_lookup(address, storage_key)? { - HistoryInfo::NotYetWritten => Ok(None), - HistoryInfo::InChangeset(changeset_block_number) => Ok(Some( + Ok(match self.storage_history_lookup(address, storage_key)? { + HistoryInfo::NotYetWritten => None, + HistoryInfo::InChangeset(changeset_block_number) => Some( self.tx .cursor_dup_read::()? .seek_by_key_subkey((changeset_block_number, address).into(), storage_key)? @@ -214,15 +237,15 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b, storage_key, })? .value, - )), - HistoryInfo::InPlainState => Ok(self + ), + HistoryInfo::InPlainState | HistoryInfo::MaybeInPlainState => self .tx .cursor_dup_read::()? .seek_by_key_subkey(address, storage_key)? .filter(|entry| entry.key == storage_key) .map(|entry| entry.value) - .or(Some(StorageValue::ZERO))), - } + .or(Some(StorageValue::ZERO)), + }) } /// Get account code by its hash From 0a15082b21adfd8a28f0a9921dd1ef589c3e8e7b Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 8 Aug 2023 15:06:45 +0100 Subject: [PATCH 06/16] another case --- crates/storage/provider/src/providers/state/historical.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 3c78290b17de..e406f1491a3b 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -15,6 +15,7 @@ use reth_primitives::{ Account, Address, BlockNumber, Bytecode, Bytes, StorageKey, StorageValue, H256, }; use std::marker::PhantomData; +use tracing::log::error; /// State provider for a given transition id which takes a tx reference. /// @@ -147,8 +148,12 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { // This check is worth it, the `cursor.prev()` check is rarely triggered (the if will // short-circuit) and when it passes we save a full seek into the changeset/plain state // table. - if rank == 0 && !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key)) { + if rank == 0 && + chunk.select(rank) as u64 != self.block_number && + !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key)) + { // The key is written to, but only after our block. + error!("rank == 0 && !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key))"); return Ok(HistoryInfo::NotYetWritten) } if rank < chunk.len() { @@ -166,6 +171,7 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { Ok(HistoryInfo::MaybeInPlainState) } else { // The key has not been written to at all. + error!("lowest_available_block_number.is_none()"); Ok(HistoryInfo::NotYetWritten) } } From 65296d0094bad12a322b76757bfd10c39392855e Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 8 Aug 2023 15:19:23 +0100 Subject: [PATCH 07/16] remove error logs --- crates/storage/provider/src/providers/state/historical.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index e406f1491a3b..b84394c870a7 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -15,9 +15,8 @@ use reth_primitives::{ Account, Address, BlockNumber, Bytecode, Bytes, StorageKey, StorageValue, H256, }; use std::marker::PhantomData; -use tracing::log::error; -/// State provider for a given transition id which takes a tx reference. +/// State provider for a given block number which takes a tx reference. /// /// Historical state provider reads the following tables: /// - [tables::AccountHistory] @@ -153,7 +152,6 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key)) { // The key is written to, but only after our block. - error!("rank == 0 && !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key))"); return Ok(HistoryInfo::NotYetWritten) } if rank < chunk.len() { @@ -171,7 +169,6 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { Ok(HistoryInfo::MaybeInPlainState) } else { // The key has not been written to at all. - error!("lowest_available_block_number.is_none()"); Ok(HistoryInfo::NotYetWritten) } } From 8c0de0b54264acfb0b1298d83bdc8fa02f449890 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 8 Aug 2023 16:12:02 +0100 Subject: [PATCH 08/16] fix test --- crates/storage/provider/src/providers/state/historical.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index b84394c870a7..8f85b3a136a7 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -266,7 +266,7 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b, } } -/// State provider for a given transition +/// State provider for a given block number pub struct HistoricalStateProvider<'a, TX: DbTx<'a>> { /// Database transaction tx: TX, @@ -506,7 +506,10 @@ mod tests { // run assert_eq!(HistoricalStateProviderRef::new(&tx, 0).storage(ADDRESS, STORAGE), Ok(None)); - assert_eq!(HistoricalStateProviderRef::new(&tx, 3).storage(ADDRESS, STORAGE), Ok(None)); + assert_eq!( + HistoricalStateProviderRef::new(&tx, 3).storage(ADDRESS, STORAGE), + Ok(Some(U256::ZERO)) + ); assert_eq!( HistoricalStateProviderRef::new(&tx, 4).storage(ADDRESS, STORAGE), Ok(Some(entry_at7.value)) From 4f20ae4654af9a2ed924497ab29ba396e8fbda30 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 8 Aug 2023 17:36:20 +0100 Subject: [PATCH 09/16] fix another case --- .../provider/src/providers/state/historical.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 8f85b3a136a7..b505c1629e6e 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -141,9 +141,9 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { // Get the rank of the first entry after our block. let rank = chunk.rank(self.block_number as usize); - // If our block is before the first entry in the index chunk, it might be before - // the first write ever. To check, we look at the previous entry and check if the - // key is the same. + // If our block is before the first entry in the index chunk and this first entry + // doesn't equal to our block, it might be before the first write ever. To check, we + // look at the previous entry and check if the key is the same. // This check is worth it, the `cursor.prev()` check is rarely triggered (the if will // short-circuit) and when it passes we save a full seek into the changeset/plain state // table. @@ -151,8 +151,14 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { chunk.select(rank) as u64 != self.block_number && !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key)) { - // The key is written to, but only after our block. - return Ok(HistoryInfo::NotYetWritten) + return if lowest_available_block_number.is_some() { + // The key may have been written, but due to pruning we may not have changesets + // and history, so we need to make a changeset lookup. + Ok(HistoryInfo::InChangeset(chunk.select(rank) as u64)) + } else { + // The key is written to, but only after our block. + Ok(HistoryInfo::NotYetWritten) + } } if rank < chunk.len() { // The chunk contains an entry for a write after our block, return it. From aa9036ee69875d33bc89463abe086b286b4bda41 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 8 Aug 2023 17:51:48 +0100 Subject: [PATCH 10/16] fix lint --- .../src/providers/state/historical.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index b505c1629e6e..6d645f7bd4b6 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -151,7 +151,7 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { chunk.select(rank) as u64 != self.block_number && !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key)) { - return if lowest_available_block_number.is_some() { + if lowest_available_block_number.is_some() { // The key may have been written, but due to pruning we may not have changesets // and history, so we need to make a changeset lookup. Ok(HistoryInfo::InChangeset(chunk.select(rank) as u64)) @@ -159,8 +159,7 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { // The key is written to, but only after our block. Ok(HistoryInfo::NotYetWritten) } - } - if rank < chunk.len() { + } else if rank < chunk.len() { // The chunk contains an entry for a write after our block, return it. Ok(HistoryInfo::InChangeset(chunk.select(rank) as u64)) } else { @@ -168,15 +167,13 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { // happen if this is the last chunk and so we need to look in the plain state. Ok(HistoryInfo::InPlainState) } + } else if lowest_available_block_number.is_some() { + // The key may have been written, but due to pruning we may not have changesets and + // history, so we need to make a plain state lookup. + Ok(HistoryInfo::MaybeInPlainState) } else { - if lowest_available_block_number.is_some() { - // The key may have been written, but due to pruning we may not have changesets and - // history, so we need to make a plain state lookup. - Ok(HistoryInfo::MaybeInPlainState) - } else { - // The key has not been written to at all. - Ok(HistoryInfo::NotYetWritten) - } + // The key has not been written to at all. + Ok(HistoryInfo::NotYetWritten) } } } From 47f00e5c5018eda786cdf2df3e1aed4296b7164c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 8 Aug 2023 19:58:30 +0100 Subject: [PATCH 11/16] sorry no ok(match {}) --- .../src/providers/state/historical.rs | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 6d645f7bd4b6..dffe3cf8d90f 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -181,23 +181,22 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { impl<'a, 'b, TX: DbTx<'a>> AccountReader for HistoricalStateProviderRef<'a, 'b, TX> { /// Get basic account information. fn basic_account(&self, address: Address) -> Result> { - Ok(match self.account_history_lookup(address)? { - HistoryInfo::NotYetWritten => None, - HistoryInfo::InChangeset(changeset_block_number) => { - self.tx - .cursor_dup_read::()? - .seek_by_key_subkey(changeset_block_number, address)? - .filter(|acc| acc.address == address) - .ok_or(ProviderError::AccountChangesetNotFound { - block_number: changeset_block_number, - address, - })? - .info - } + match self.account_history_lookup(address)? { + HistoryInfo::NotYetWritten => Ok(None), + HistoryInfo::InChangeset(changeset_block_number) => Ok(self + .tx + .cursor_dup_read::()? + .seek_by_key_subkey(changeset_block_number, address)? + .filter(|acc| acc.address == address) + .ok_or(ProviderError::AccountChangesetNotFound { + block_number: changeset_block_number, + address, + })? + .info), HistoryInfo::InPlainState | HistoryInfo::MaybeInPlainState => { - self.tx.get::(address)? + Ok(self.tx.get::(address)?) } - }) + } } } @@ -230,9 +229,9 @@ impl<'a, 'b, TX: DbTx<'a>> StateRootProvider for HistoricalStateProviderRef<'a, impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b, TX> { /// Get storage. fn storage(&self, address: Address, storage_key: StorageKey) -> Result> { - Ok(match self.storage_history_lookup(address, storage_key)? { - HistoryInfo::NotYetWritten => None, - HistoryInfo::InChangeset(changeset_block_number) => Some( + match self.storage_history_lookup(address, storage_key)? { + HistoryInfo::NotYetWritten => Ok(None), + HistoryInfo::InChangeset(changeset_block_number) => Ok(Some( self.tx .cursor_dup_read::()? .seek_by_key_subkey((changeset_block_number, address).into(), storage_key)? @@ -243,15 +242,15 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b, storage_key, })? .value, - ), - HistoryInfo::InPlainState | HistoryInfo::MaybeInPlainState => self + )), + HistoryInfo::InPlainState | HistoryInfo::MaybeInPlainState => Ok(self .tx .cursor_dup_read::()? .seek_by_key_subkey(address, storage_key)? .filter(|entry| entry.key == storage_key) .map(|entry| entry.value) - .or(Some(StorageValue::ZERO)), - }) + .or(Some(StorageValue::ZERO))), + } } /// Get account code by its hash From d9a23fc1968ba6451b8e55df967ad0efb51df64e Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 9 Aug 2023 11:47:38 +0100 Subject: [PATCH 12/16] explain historical state provider behaviour --- crates/storage/provider/src/providers/state/historical.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index dffe3cf8d90f..318ff323ef6d 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -18,6 +18,9 @@ use std::marker::PhantomData; /// State provider for a given block number which takes a tx reference. /// +/// Historical state provider accesses the state at the start of the provided block number. +/// It means that all changes made in the provided block number are not included. +/// /// Historical state provider reads the following tables: /// - [tables::AccountHistory] /// - [tables::Bytecodes] @@ -268,7 +271,8 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b, } } -/// State provider for a given block number +/// State provider for a given block number. +/// For more detailed description, see [HistoricalStateProviderRef]. pub struct HistoricalStateProvider<'a, TX: DbTx<'a>> { /// Database transaction tx: TX, From 30cc002fc2188c2ac632176e8709a5f51377214c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 9 Aug 2023 18:01:56 +0100 Subject: [PATCH 13/16] reorder fields, add helper struct, add tests --- .../provider/src/providers/database/mod.rs | 10 +- .../src/providers/state/historical.rs | 146 ++++++++++++------ 2 files changed, 109 insertions(+), 47 deletions(-) diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 5004da8366b3..506cc59ebab3 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -112,12 +112,14 @@ impl ProviderFactory { // If we pruned account or storage history, we can't return state on every historical block. // Instead, we should cap it at the latest prune checkpoint for corresponding prune part. if let Some(prune_checkpoint) = account_history_prune_checkpoint { - state_provider = state_provider - .with_lowest_account_history_block_number(prune_checkpoint.block_number + 1); + state_provider = state_provider.with_lowest_available_account_history_block_number( + prune_checkpoint.block_number + 1, + ); } if let Some(prune_checkpoint) = storage_history_prune_checkpoint { - state_provider = state_provider - .with_lowest_storage_history_block_number(prune_checkpoint.block_number + 1); + state_provider = state_provider.with_lowest_available_storage_history_block_number( + prune_checkpoint.block_number + 1, + ); } Ok(Box::new(state_provider)) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index c172048f5653..c0e47d1418d5 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -29,14 +29,13 @@ pub struct HistoricalStateProviderRef<'a, 'b, TX: DbTx<'a>> { tx: &'b TX, /// Block number is main index for the history state of accounts and storages. block_number: BlockNumber, - /// Lowest block number at which the account history is available. - lowest_account_history_block_number: Option, - /// Lowest block number at which the storage history is available. - lowest_storage_history_block_number: Option, + /// Lowest blocks at which different parts of the state are available. + lowest_available_blocks: LowestAvailableBlocks, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, } +#[derive(Debug, Eq, PartialEq)] pub enum HistoryInfo { NotYetWritten, InChangeset(u64), @@ -49,38 +48,24 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { Self { tx, block_number, - lowest_account_history_block_number: None, - lowest_storage_history_block_number: None, + lowest_available_blocks: Default::default(), _phantom: PhantomData {}, } } /// Create new StateProvider for historical block number and lowest block numbers at which /// account & storage histories are available. - pub fn new_with_lowest_history_block_numbers( + pub fn new_with_lowest_available_blocks( tx: &'b TX, - lowest_account_history_block_number: Option, - lowest_storage_history_block_number: Option, block_number: BlockNumber, + lowest_available_blocks: LowestAvailableBlocks, ) -> Self { - Self { - tx, - block_number, - lowest_account_history_block_number, - lowest_storage_history_block_number, - _phantom: PhantomData {}, - } + Self { tx, block_number, lowest_available_blocks, _phantom: PhantomData {} } } /// Lookup an account in the AccountHistory table pub fn account_history_lookup(&self, address: Address) -> Result { - // Check if lowest available block number for storage history is more than the requested - // block number for this historical provider instance. - if self - .lowest_account_history_block_number - .map(|block_number| block_number > self.block_number) - .unwrap_or(false) - { + if !self.lowest_available_blocks.is_account_history_available(self.block_number) { return Err(ProviderError::StateAtBlockPruned(self.block_number).into()) } @@ -95,13 +80,7 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { address: Address, storage_key: StorageKey, ) -> Result { - // Check if lowest available block number for account history is more than the requested - // block number for this historical provider instance. - if self - .lowest_storage_history_block_number - .map(|block_number| block_number > self.block_number) - .unwrap_or(false) - { + if !self.lowest_available_blocks.is_storage_history_available(self.block_number) { return Err(ProviderError::StateAtBlockPruned(self.block_number).into()) } @@ -246,10 +225,8 @@ pub struct HistoricalStateProvider<'a, TX: DbTx<'a>> { tx: TX, /// State at the block number is the main indexer of the state. block_number: BlockNumber, - /// Lowest block number at which the account history is available. - lowest_account_history_block_number: Option, - /// Lowest block number at which the storage history is available. - lowest_storage_history_block_number: Option, + /// Lowest blocks at which different parts of the state are available. + lowest_available_blocks: LowestAvailableBlocks, /// Phantom lifetime `'a` _phantom: PhantomData<&'a TX>, } @@ -260,32 +237,36 @@ impl<'a, TX: DbTx<'a>> HistoricalStateProvider<'a, TX> { Self { tx, block_number, - lowest_account_history_block_number: None, - lowest_storage_history_block_number: None, + lowest_available_blocks: Default::default(), _phantom: PhantomData {}, } } /// Set the lowest block number at which the account history is available. - pub fn with_lowest_account_history_block_number(mut self, block_number: BlockNumber) -> Self { - self.lowest_account_history_block_number = Some(block_number); + pub fn with_lowest_available_account_history_block_number( + mut self, + block_number: BlockNumber, + ) -> Self { + self.lowest_available_blocks.account_history_block_number = Some(block_number); self } /// Set the lowest block number at which the storage history is available. - pub fn with_lowest_storage_history_block_number(mut self, block_number: BlockNumber) -> Self { - self.lowest_storage_history_block_number = Some(block_number); + pub fn with_lowest_available_storage_history_block_number( + mut self, + block_number: BlockNumber, + ) -> Self { + self.lowest_available_blocks.storage_history_block_number = Some(block_number); self } /// Returns a new provider that takes the `TX` as reference #[inline(always)] fn as_ref<'b>(&'b self) -> HistoricalStateProviderRef<'a, 'b, TX> { - HistoricalStateProviderRef::new_with_lowest_history_block_numbers( + HistoricalStateProviderRef::new_with_lowest_available_blocks( &self.tx, - self.lowest_account_history_block_number, - self.lowest_storage_history_block_number, self.block_number, + self.lowest_available_blocks, ) } } @@ -293,9 +274,36 @@ impl<'a, TX: DbTx<'a>> HistoricalStateProvider<'a, TX> { // Delegates all provider impls to [HistoricalStateProviderRef] delegate_provider_impls!(HistoricalStateProvider<'a, TX> where [TX: DbTx<'a>]); +/// Lowest blocks at which different parts of the state are available. +/// They may be [Some] if pruning is enabled. +#[derive(Default, Copy, Clone)] +pub struct LowestAvailableBlocks { + /// Lowest block number at which the account history is available. It may not be available if + /// [reth_primitives::PrunePart::AccountHistory] was pruned. + pub account_history_block_number: Option, + /// Lowest block number at which the storage history is available. It may not be available if + /// [reth_primitives::PrunePart::StorageHistory] was pruned. + pub storage_history_block_number: Option, +} + +impl LowestAvailableBlocks { + /// Check if account history is available at the provided block number, i.e. lowest available + /// block number for account history is less than or equal to the provided block number. + pub fn is_account_history_available(&self, at: BlockNumber) -> bool { + self.account_history_block_number.map(|block_number| block_number <= at).unwrap_or(true) + } + + /// Check if storage history is available at the provided block number, i.e. lowest available + /// block number for storage history is less than or equal to the provided block number. + pub fn is_storage_history_available(&self, at: BlockNumber) -> bool { + self.storage_history_block_number.map(|block_number| block_number <= at).unwrap_or(true) + } +} + #[cfg(test)] mod tests { use crate::{ + providers::state::historical::{HistoryInfo, LowestAvailableBlocks}, AccountReader, HistoricalStateProvider, HistoricalStateProviderRef, StateProvider, }; use reth_db::{ @@ -306,6 +314,7 @@ mod tests { transaction::{DbTx, DbTxMut}, BlockNumberList, }; + use reth_interfaces::provider::ProviderError; use reth_primitives::{hex_literal::hex, Account, StorageEntry, H160, H256, U256}; const ADDRESS: H160 = H160(hex!("0000000000000000000000000000000000000001")); @@ -514,4 +523,55 @@ mod tests { Ok(Some(higher_entry_plain.value)) ); } + + #[test] + fn history_provider_unavailable() { + let db = create_test_rw_db(); + let tx = db.tx().unwrap(); + + let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( + &tx, + 2, + LowestAvailableBlocks { + account_history_block_number: Some(3), + storage_history_block_number: Some(3), + }, + ); + assert_eq!( + provider.account_history_lookup(ADDRESS), + Err(ProviderError::StateAtBlockPruned(provider.block_number).into()) + ); + assert_eq!( + provider.storage_history_lookup(ADDRESS, STORAGE), + Err(ProviderError::StateAtBlockPruned(provider.block_number).into()) + ); + + let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( + &tx, + 2, + LowestAvailableBlocks { + account_history_block_number: Some(2), + storage_history_block_number: Some(2), + }, + ); + assert_eq!(provider.account_history_lookup(ADDRESS), Ok(HistoryInfo::NotYetWritten)); + assert_eq!( + provider.storage_history_lookup(ADDRESS, STORAGE), + Ok(HistoryInfo::NotYetWritten) + ); + + let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( + &tx, + 2, + LowestAvailableBlocks { + account_history_block_number: Some(1), + storage_history_block_number: Some(1), + }, + ); + assert_eq!(provider.account_history_lookup(ADDRESS), Ok(HistoryInfo::NotYetWritten)); + assert_eq!( + provider.storage_history_lookup(ADDRESS, STORAGE), + Ok(HistoryInfo::NotYetWritten) + ); + } } From c307a949cd53e4593a359fc851903f1485e3d3a3 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 9 Aug 2023 18:04:24 +0100 Subject: [PATCH 14/16] add explanations to test --- crates/storage/provider/src/providers/state/historical.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index c0e47d1418d5..b87227da4384 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -529,6 +529,8 @@ mod tests { let db = create_test_rw_db(); let tx = db.tx().unwrap(); + // provider block_number < lowest available block number, + // i.e. state at provider block is pruned let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( &tx, 2, @@ -546,6 +548,8 @@ mod tests { Err(ProviderError::StateAtBlockPruned(provider.block_number).into()) ); + // provider block_number == lowest available block number, + // i.e. state at provider block is available let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( &tx, 2, @@ -560,6 +564,8 @@ mod tests { Ok(HistoryInfo::NotYetWritten) ); + // provider block_number == lowest available block number, + // i.e. state at provider block is available let provider = HistoricalStateProviderRef::new_with_lowest_available_blocks( &tx, 2, From b85d9bce3addadd3b77e82483d311631b871e91c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 9 Aug 2023 20:55:27 +0100 Subject: [PATCH 15/16] fixes after merge --- crates/storage/provider/src/providers/state/historical.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index f6ea06821e85..a61cb0ab7033 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -78,7 +78,7 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { self.history_info::( history_key, |key| key.key == address, - self.lowest_account_history_block_number, + self.lowest_available_blocks.account_history_block_number, ) } @@ -97,7 +97,7 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { self.history_info::( history_key, |key| key.address == address && key.sharded_key.key == storage_key, - self.lowest_storage_history_block_number, + self.lowest_available_blocks.storage_history_block_number, ) } From 4ceff4908c6ec17f40fa349ca2fd9b76ea3f6893 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 9 Aug 2023 21:56:22 +0100 Subject: [PATCH 16/16] fix test --- crates/storage/provider/src/providers/state/historical.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index a61cb0ab7033..01c9ea8b61fb 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -593,10 +593,10 @@ mod tests { storage_history_block_number: Some(2), }, ); - assert_eq!(provider.account_history_lookup(ADDRESS), Ok(HistoryInfo::NotYetWritten)); + assert_eq!(provider.account_history_lookup(ADDRESS), Ok(HistoryInfo::MaybeInPlainState)); assert_eq!( provider.storage_history_lookup(ADDRESS, STORAGE), - Ok(HistoryInfo::NotYetWritten) + Ok(HistoryInfo::MaybeInPlainState) ); // provider block_number == lowest available block number, @@ -609,10 +609,10 @@ mod tests { storage_history_block_number: Some(1), }, ); - assert_eq!(provider.account_history_lookup(ADDRESS), Ok(HistoryInfo::NotYetWritten)); + assert_eq!(provider.account_history_lookup(ADDRESS), Ok(HistoryInfo::MaybeInPlainState)); assert_eq!( provider.storage_history_lookup(ADDRESS, STORAGE), - Ok(HistoryInfo::NotYetWritten) + Ok(HistoryInfo::MaybeInPlainState) ); } }