fix(storage): fallback lookups for pruned history (#4121)

This commit is contained in:
Alexey Shekhirin
2023-08-09 22:04:59 +01:00
committed by GitHub
parent e925cbcf3a
commit 7426a01a93
2 changed files with 66 additions and 26 deletions

View File

@ -490,6 +490,7 @@ impl<DB: Database> Pruner<DB> {
{
let mut processed = 0;
let mut cursor = provider.tx_ref().cursor_write::<T>()?;
// 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<DB: Database> Pruner<DB> {
// 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<DB: Database> Pruner<DB> {
}
}
// Jump to the next address
// Jump to the next address.
cursor.seek_exact(last_key(&key))?;
}

View File

@ -16,7 +16,10 @@ use reth_primitives::{
};
use std::marker::PhantomData;
/// 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 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]
@ -40,6 +43,7 @@ pub enum HistoryInfo {
NotYetWritten,
InChangeset(u64),
InPlainState,
MaybeInPlainState,
}
impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> {
@ -71,7 +75,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::<tables::AccountHistory, _>(history_key, |key| key.key == address)
self.history_info::<tables::AccountHistory, _>(
history_key,
|key| key.key == address,
self.lowest_available_blocks.account_history_block_number,
)
}
/// Lookup a storage key in the StorageHistory table
@ -86,12 +94,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::<tables::StorageHistory, _>(history_key, |key| {
key.address == address && key.sharded_key.key == storage_key
})
self.history_info::<tables::StorageHistory, _>(
history_key,
|key| key.address == address && key.sharded_key.key == storage_key,
self.lowest_available_blocks.storage_history_block_number,
)
}
fn history_info<T, K>(&self, key: K, key_filter: impl Fn(&K) -> bool) -> Result<HistoryInfo>
fn history_info<T, K>(
&self,
key: K,
key_filter: impl Fn(&K) -> bool,
lowest_available_block_number: Option<BlockNumber>,
) -> Result<HistoryInfo>
where
T: Table<Key = K, Value = BlockNumberList>,
{
@ -106,17 +121,25 @@ 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.
if rank == 0 && !cursor.prev()?.is_some_and(|(key, _)| key_filter(&key)) {
// The key is written to, but only after our block.
return Ok(HistoryInfo::NotYetWritten)
}
if rank < chunk.len() {
if rank == 0 &&
chunk.select(rank) as u64 != self.block_number &&
!cursor.prev()?.is_some_and(|(key, _)| key_filter(&key))
{
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)
}
} 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 {
@ -124,6 +147,10 @@ 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 {
// The key has not been written to at all.
Ok(HistoryInfo::NotYetWritten)
@ -146,7 +173,9 @@ impl<'a, 'b, TX: DbTx<'a>> AccountReader for HistoricalStateProviderRef<'a, 'b,
address,
})?
.info),
HistoryInfo::InPlainState => Ok(self.tx.get::<tables::PlainAccountState>(address)?),
HistoryInfo::InPlainState | HistoryInfo::MaybeInPlainState => {
Ok(self.tx.get::<tables::PlainAccountState>(address)?)
}
}
}
}
@ -194,7 +223,7 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b,
})?
.value,
)),
HistoryInfo::InPlainState => Ok(self
HistoryInfo::InPlainState | HistoryInfo::MaybeInPlainState => Ok(self
.tx
.cursor_dup_read::<tables::PlainStorageState>()?
.seek_by_key_subkey(address, storage_key)?
@ -219,7 +248,8 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b,
}
}
/// State provider for a given transition
/// State provider for a given block number.
/// For more detailed description, see [HistoricalStateProviderRef].
pub struct HistoricalStateProvider<'a, TX: DbTx<'a>> {
/// Database transaction
tx: TX,
@ -280,9 +310,11 @@ delegate_provider_impls!(HistoricalStateProvider<'a, TX> where [TX: DbTx<'a>]);
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.
/// [Option::None] means all history is available.
pub account_history_block_number: Option<BlockNumber>,
/// Lowest block number at which the storage history is available. It may not be available if
/// [reth_primitives::PrunePart::StorageHistory] was pruned.
/// [Option::None] means all history is available.
pub storage_history_block_number: Option<BlockNumber>,
}
@ -489,7 +521,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))
@ -558,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,
@ -574,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)
);
}
}