mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 17:18:08 -05:00
refactor(storage): historical state lookup (better comments) (#3867)
Co-authored-by: Alexey Shekhirin <a.shekhirin@gmail.com> Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@@ -5,8 +5,10 @@ use crate::{
|
||||
use reth_db::{
|
||||
cursor::{DbCursorRO, DbDupCursorRO},
|
||||
models::{storage_sharded_key::StorageShardedKey, ShardedKey},
|
||||
table::Table,
|
||||
tables,
|
||||
transaction::DbTx,
|
||||
BlockNumberList,
|
||||
};
|
||||
use reth_interfaces::Result;
|
||||
use reth_primitives::{
|
||||
@@ -17,11 +19,11 @@ use std::marker::PhantomData;
|
||||
/// State provider for a given transition id which takes a tx reference.
|
||||
///
|
||||
/// Historical state provider reads the following tables:
|
||||
/// [tables::AccountHistory]
|
||||
/// [tables::Bytecodes]
|
||||
/// [tables::StorageHistory]
|
||||
/// [tables::AccountChangeSet]
|
||||
/// [tables::StorageChangeSet]
|
||||
/// - [tables::AccountHistory]
|
||||
/// - [tables::Bytecodes]
|
||||
/// - [tables::StorageHistory]
|
||||
/// - [tables::AccountChangeSet]
|
||||
/// - [tables::StorageChangeSet]
|
||||
pub struct HistoricalStateProviderRef<'a, 'b, TX: DbTx<'a>> {
|
||||
/// Transaction
|
||||
tx: &'b TX,
|
||||
@@ -32,7 +34,7 @@ pub struct HistoricalStateProviderRef<'a, 'b, TX: DbTx<'a>> {
|
||||
}
|
||||
|
||||
pub enum HistoryInfo {
|
||||
NotWritten,
|
||||
NotYetWritten,
|
||||
InChangeset(u64),
|
||||
InPlainState,
|
||||
}
|
||||
@@ -47,24 +49,7 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> {
|
||||
pub fn account_history_lookup(&self, address: Address) -> Result<HistoryInfo> {
|
||||
// history key to search IntegerList of block number changesets.
|
||||
let history_key = ShardedKey::new(address, self.block_number);
|
||||
let mut cursor = self.tx.cursor_read::<tables::AccountHistory>()?;
|
||||
|
||||
if let Some(chunk) =
|
||||
cursor.seek(history_key)?.filter(|(key, _)| key.key == address).map(|x| x.1 .0)
|
||||
{
|
||||
let chunk = chunk.enable_rank();
|
||||
let rank = chunk.rank(self.block_number as usize);
|
||||
if rank == 0 && !cursor.prev()?.is_some_and(|(key, _)| key.key == address) {
|
||||
return Ok(HistoryInfo::NotWritten)
|
||||
}
|
||||
if rank < chunk.len() {
|
||||
Ok(HistoryInfo::InChangeset(chunk.select(rank) as u64))
|
||||
} else {
|
||||
Ok(HistoryInfo::InPlainState)
|
||||
}
|
||||
} else {
|
||||
Ok(HistoryInfo::NotWritten)
|
||||
}
|
||||
self.history_info::<tables::AccountHistory, _>(history_key, |key| key.key == address)
|
||||
}
|
||||
|
||||
/// Lookup a storage key in the StorageHistory table
|
||||
@@ -75,29 +60,47 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> {
|
||||
) -> Result<HistoryInfo> {
|
||||
// history key to search IntegerList of block number changesets.
|
||||
let history_key = StorageShardedKey::new(address, storage_key, self.block_number);
|
||||
let mut cursor = self.tx.cursor_read::<tables::StorageHistory>()?;
|
||||
self.history_info::<tables::StorageHistory, _>(history_key, |key| {
|
||||
key.address == address && key.sharded_key.key == storage_key
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(chunk) = cursor
|
||||
.seek(history_key)?
|
||||
.filter(|(key, _)| key.address == address && key.sharded_key.key == storage_key)
|
||||
.map(|x| x.1 .0)
|
||||
{
|
||||
fn history_info<T, K>(&self, key: K, key_filter: impl Fn(&K) -> bool) -> Result<HistoryInfo>
|
||||
where
|
||||
T: Table<Key = K, Value = BlockNumberList>,
|
||||
{
|
||||
let mut cursor = self.tx.cursor_read::<T>()?;
|
||||
|
||||
// Lookup the history chunk in the history index. If they key does not appear in the
|
||||
// index, the first chunk for the next key will be returned so we filter out chunks that
|
||||
// have a different key.
|
||||
if let Some(chunk) = cursor.seek(key)?.filter(|(key, _)| key_filter(key)).map(|x| x.1 .0) {
|
||||
let chunk = chunk.enable_rank();
|
||||
|
||||
// Get the rank of the first entry after our block.
|
||||
let rank = chunk.rank(self.block_number as usize);
|
||||
if rank == 0 &&
|
||||
!cursor.prev()?.is_some_and(|(key, _)| {
|
||||
key.address == address && key.sharded_key.key == storage_key
|
||||
})
|
||||
{
|
||||
return Ok(HistoryInfo::NotWritten)
|
||||
|
||||
// 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.
|
||||
// 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() {
|
||||
// The chunk contains an entry for a write after our block, return it.
|
||||
Ok(HistoryInfo::InChangeset(chunk.select(rank) as u64))
|
||||
} else {
|
||||
// The chunk does not contain an entry for a write after our block. This can only
|
||||
// happen if this is the last chunk and so we need to look in the plain state.
|
||||
Ok(HistoryInfo::InPlainState)
|
||||
}
|
||||
} else {
|
||||
Ok(HistoryInfo::NotWritten)
|
||||
// The key has not been written to at all.
|
||||
Ok(HistoryInfo::NotYetWritten)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +109,7 @@ impl<'a, 'b, TX: DbTx<'a>> AccountReader for HistoricalStateProviderRef<'a, 'b,
|
||||
/// Get basic account information.
|
||||
fn basic_account(&self, address: Address) -> Result<Option<Account>> {
|
||||
match self.account_history_lookup(address)? {
|
||||
HistoryInfo::NotWritten => Ok(None),
|
||||
HistoryInfo::NotYetWritten => Ok(None),
|
||||
HistoryInfo::InChangeset(changeset_block_number) => Ok(self
|
||||
.tx
|
||||
.cursor_dup_read::<tables::AccountChangeSet>()?
|
||||
@@ -152,7 +155,7 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b,
|
||||
/// Get storage.
|
||||
fn storage(&self, address: Address, storage_key: StorageKey) -> Result<Option<StorageValue>> {
|
||||
match self.storage_history_lookup(address, storage_key)? {
|
||||
HistoryInfo::NotWritten => Ok(None),
|
||||
HistoryInfo::NotYetWritten => Ok(None),
|
||||
HistoryInfo::InChangeset(changeset_block_number) => Ok(Some(
|
||||
self.tx
|
||||
.cursor_dup_read::<tables::StorageChangeSet>()?
|
||||
|
||||
Reference in New Issue
Block a user