mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
Avoid querying unwritten values from changesets during historical lookups (#2966)
This commit is contained in:
@ -31,30 +31,83 @@ pub struct HistoricalStateProviderRef<'a, 'b, TX: DbTx<'a>> {
|
||||
_phantom: PhantomData<&'a TX>,
|
||||
}
|
||||
|
||||
pub enum HistoryInfo {
|
||||
NotWritten,
|
||||
InChangeset(u64),
|
||||
InPlainState,
|
||||
}
|
||||
|
||||
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 {} }
|
||||
}
|
||||
|
||||
/// Lookup an account in the AccountHistory table
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup a storage key in the StorageHistory table
|
||||
pub fn storage_history_lookup(
|
||||
&self,
|
||||
address: Address,
|
||||
storage_key: StorageKey,
|
||||
) -> 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>()?;
|
||||
|
||||
if let Some(chunk) = cursor
|
||||
.seek(history_key)?
|
||||
.filter(|(key, _)| key.address == address && key.sharded_key.key == storage_key)
|
||||
.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.address == address && key.sharded_key.key == storage_key
|
||||
})
|
||||
{
|
||||
return Ok(HistoryInfo::NotWritten)
|
||||
}
|
||||
if rank < chunk.len() {
|
||||
Ok(HistoryInfo::InChangeset(chunk.select(rank) as u64))
|
||||
} else {
|
||||
Ok(HistoryInfo::InPlainState)
|
||||
}
|
||||
} else {
|
||||
Ok(HistoryInfo::NotWritten)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, TX: DbTx<'a>> AccountProvider for HistoricalStateProviderRef<'a, 'b, TX> {
|
||||
/// Get basic account information.
|
||||
fn basic_account(&self, address: Address) -> Result<Option<Account>> {
|
||||
// history key to search IntegerList of block number changesets.
|
||||
let history_key = ShardedKey::new(address, self.block_number);
|
||||
|
||||
let changeset_block_number = self
|
||||
.tx
|
||||
.cursor_read::<tables::AccountHistory>()?
|
||||
.seek(history_key)?
|
||||
.filter(|(key, _)| key.key == address)
|
||||
.map(|(_, list)| {
|
||||
list.0.enable_rank().successor(self.block_number as usize).map(|i| i as u64)
|
||||
});
|
||||
|
||||
// if changeset of the block is present we are getting value from that changeset
|
||||
if let Some(Some(changeset_block_number)) = changeset_block_number {
|
||||
let account = self
|
||||
match self.account_history_lookup(address)? {
|
||||
HistoryInfo::NotWritten => Ok(None),
|
||||
HistoryInfo::InChangeset(changeset_block_number) => Ok(self
|
||||
.tx
|
||||
.cursor_dup_read::<tables::AccountChangeSet>()?
|
||||
.seek_by_key_subkey(changeset_block_number, address)?
|
||||
@ -62,15 +115,9 @@ impl<'a, 'b, TX: DbTx<'a>> AccountProvider for HistoricalStateProviderRef<'a, 'b
|
||||
.ok_or(ProviderError::AccountChangesetNotFound {
|
||||
block_number: changeset_block_number,
|
||||
address,
|
||||
})?;
|
||||
Ok(account.info)
|
||||
} else if changeset_block_number.is_none() {
|
||||
// if there is no shard, return empty account.
|
||||
Ok(None)
|
||||
} else {
|
||||
// if changeset is not present that means that there was history shard but we need to
|
||||
// use newest value from plain state. Or zero if none.
|
||||
Ok(self.tx.get::<tables::PlainAccountState>(address)?)
|
||||
})?
|
||||
.info),
|
||||
HistoryInfo::InPlainState => Ok(self.tx.get::<tables::PlainAccountState>(address)?),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,43 +151,27 @@ 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<Option<StorageValue>> {
|
||||
// history key to search IntegerList of block changesets.
|
||||
let history_key = StorageShardedKey::new(address, storage_key, self.block_number);
|
||||
|
||||
let changeset_block_number = self
|
||||
.tx
|
||||
.cursor_read::<tables::StorageHistory>()?
|
||||
.seek(history_key)?
|
||||
.filter(|(key, _)| key.address == address && key.sharded_key.key == storage_key)
|
||||
.map(|(_, list)| {
|
||||
list.0.enable_rank().successor(self.block_number as usize).map(|i| i as u64)
|
||||
});
|
||||
|
||||
// if changeset transition id is present we are getting value from changeset
|
||||
if let Some(Some(changeset_block_number)) = changeset_block_number {
|
||||
let storage_entry = self
|
||||
.tx
|
||||
.cursor_dup_read::<tables::StorageChangeSet>()?
|
||||
.seek_by_key_subkey((changeset_block_number, address).into(), storage_key)?
|
||||
.filter(|entry| entry.key == storage_key)
|
||||
.ok_or(ProviderError::StorageChangesetNotFound {
|
||||
block_number: changeset_block_number,
|
||||
address,
|
||||
storage_key,
|
||||
})?;
|
||||
Ok(Some(storage_entry.value))
|
||||
} else if changeset_block_number.is_none() {
|
||||
// if there is no shards, return empty account.
|
||||
return Ok(None)
|
||||
} else {
|
||||
// if changeset is not present that means that there was history shard but we need to
|
||||
// use newest value from plain state
|
||||
Ok(self
|
||||
match self.storage_history_lookup(address, storage_key)? {
|
||||
HistoryInfo::NotWritten => Ok(None),
|
||||
HistoryInfo::InChangeset(changeset_block_number) => Ok(Some(
|
||||
self.tx
|
||||
.cursor_dup_read::<tables::StorageChangeSet>()?
|
||||
.seek_by_key_subkey((changeset_block_number, address).into(), storage_key)?
|
||||
.filter(|entry| entry.key == storage_key)
|
||||
.ok_or(ProviderError::StorageChangesetNotFound {
|
||||
block_number: changeset_block_number,
|
||||
address,
|
||||
storage_key,
|
||||
})?
|
||||
.value,
|
||||
)),
|
||||
HistoryInfo::InPlainState => Ok(self
|
||||
.tx
|
||||
.cursor_dup_read::<tables::PlainStorageState>()?
|
||||
.seek_by_key_subkey(address, storage_key)?
|
||||
.filter(|entry| entry.key == storage_key)
|
||||
.map(|entry| entry.value))
|
||||
.map(|entry| entry.value)
|
||||
.or(Some(StorageValue::ZERO))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,6 +232,7 @@ mod tests {
|
||||
use reth_primitives::{hex_literal::hex, Account, StorageEntry, H160, H256, U256};
|
||||
|
||||
const ADDRESS: H160 = H160(hex!("0000000000000000000000000000000000000001"));
|
||||
const HIGHER_ADDRESS: H160 = H160(hex!("0000000000000000000000000000000000000005"));
|
||||
const STORAGE: H256 =
|
||||
H256(hex!("0000000000000000000000000000000000000000000000000000000000000001"));
|
||||
|
||||
@ -217,7 +249,7 @@ mod tests {
|
||||
|
||||
tx.put::<tables::AccountHistory>(
|
||||
ShardedKey { key: ADDRESS, highest_block_number: 7 },
|
||||
BlockNumberList::new([3, 7]).unwrap(),
|
||||
BlockNumberList::new([1, 3, 7]).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
tx.put::<tables::AccountHistory>(
|
||||
@ -225,6 +257,11 @@ mod tests {
|
||||
BlockNumberList::new([10, 15]).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
tx.put::<tables::AccountHistory>(
|
||||
ShardedKey { key: HIGHER_ADDRESS, highest_block_number: u64::MAX },
|
||||
BlockNumberList::new([4]).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let acc_plain = Account { nonce: 100, balance: U256::ZERO, bytecode_hash: None };
|
||||
let acc_at15 = Account { nonce: 15, balance: U256::ZERO, bytecode_hash: None };
|
||||
@ -232,12 +269,21 @@ mod tests {
|
||||
let acc_at7 = Account { nonce: 7, balance: U256::ZERO, bytecode_hash: None };
|
||||
let acc_at3 = Account { nonce: 3, balance: U256::ZERO, bytecode_hash: None };
|
||||
|
||||
let higher_acc_plain = Account { nonce: 4, balance: U256::ZERO, bytecode_hash: None };
|
||||
|
||||
// setup
|
||||
tx.put::<tables::AccountChangeSet>(1, AccountBeforeTx { address: ADDRESS, info: None })
|
||||
.unwrap();
|
||||
tx.put::<tables::AccountChangeSet>(
|
||||
3,
|
||||
AccountBeforeTx { address: ADDRESS, info: Some(acc_at3) },
|
||||
)
|
||||
.unwrap();
|
||||
tx.put::<tables::AccountChangeSet>(
|
||||
4,
|
||||
AccountBeforeTx { address: HIGHER_ADDRESS, info: None },
|
||||
)
|
||||
.unwrap();
|
||||
tx.put::<tables::AccountChangeSet>(
|
||||
7,
|
||||
AccountBeforeTx { address: ADDRESS, info: Some(acc_at7) },
|
||||
@ -256,13 +302,15 @@ mod tests {
|
||||
|
||||
// setup plain state
|
||||
tx.put::<tables::PlainAccountState>(ADDRESS, acc_plain).unwrap();
|
||||
tx.put::<tables::PlainAccountState>(HIGHER_ADDRESS, higher_acc_plain).unwrap();
|
||||
tx.commit().unwrap();
|
||||
|
||||
let tx = db.tx().unwrap();
|
||||
|
||||
// run
|
||||
assert_eq!(HistoricalStateProviderRef::new(&tx, 1).basic_account(ADDRESS), Ok(None));
|
||||
assert_eq!(
|
||||
HistoricalStateProviderRef::new(&tx, 1).basic_account(ADDRESS),
|
||||
HistoricalStateProviderRef::new(&tx, 2).basic_account(ADDRESS),
|
||||
Ok(Some(acc_at3))
|
||||
);
|
||||
assert_eq!(
|
||||
@ -293,6 +341,12 @@ mod tests {
|
||||
HistoricalStateProviderRef::new(&tx, 16).basic_account(ADDRESS),
|
||||
Ok(Some(acc_plain))
|
||||
);
|
||||
|
||||
assert_eq!(HistoricalStateProviderRef::new(&tx, 1).basic_account(HIGHER_ADDRESS), Ok(None));
|
||||
assert_eq!(
|
||||
HistoricalStateProviderRef::new(&tx, 1000).basic_account(HIGHER_ADDRESS),
|
||||
Ok(Some(higher_acc_plain))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -316,7 +370,17 @@ mod tests {
|
||||
BlockNumberList::new([10, 15]).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
tx.put::<tables::StorageHistory>(
|
||||
StorageShardedKey {
|
||||
address: HIGHER_ADDRESS,
|
||||
sharded_key: ShardedKey { key: STORAGE, highest_block_number: u64::MAX },
|
||||
},
|
||||
BlockNumberList::new([4]).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let higher_entry_plain = StorageEntry { key: STORAGE, value: U256::from(1000) };
|
||||
let higher_entry_at4 = StorageEntry { key: STORAGE, value: U256::from(0) };
|
||||
let entry_plain = StorageEntry { key: STORAGE, value: U256::from(100) };
|
||||
let entry_at15 = StorageEntry { key: STORAGE, value: U256::from(15) };
|
||||
let entry_at10 = StorageEntry { key: STORAGE, value: U256::from(10) };
|
||||
@ -325,25 +389,21 @@ mod tests {
|
||||
|
||||
// setup
|
||||
tx.put::<tables::StorageChangeSet>((3, ADDRESS).into(), entry_at3).unwrap();
|
||||
tx.put::<tables::StorageChangeSet>((4, HIGHER_ADDRESS).into(), higher_entry_at4).unwrap();
|
||||
tx.put::<tables::StorageChangeSet>((7, ADDRESS).into(), entry_at7).unwrap();
|
||||
tx.put::<tables::StorageChangeSet>((10, ADDRESS).into(), entry_at10).unwrap();
|
||||
tx.put::<tables::StorageChangeSet>((15, ADDRESS).into(), entry_at15).unwrap();
|
||||
|
||||
// setup plain state
|
||||
tx.put::<tables::PlainStorageState>(ADDRESS, entry_plain).unwrap();
|
||||
tx.put::<tables::PlainStorageState>(HIGHER_ADDRESS, higher_entry_plain).unwrap();
|
||||
tx.commit().unwrap();
|
||||
|
||||
let tx = db.tx().unwrap();
|
||||
|
||||
// run
|
||||
assert_eq!(
|
||||
HistoricalStateProviderRef::new(&tx, 0).storage(ADDRESS, STORAGE),
|
||||
Ok(Some(entry_at3.value))
|
||||
);
|
||||
assert_eq!(
|
||||
HistoricalStateProviderRef::new(&tx, 3).storage(ADDRESS, STORAGE),
|
||||
Ok(Some(entry_at3.value))
|
||||
);
|
||||
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, 4).storage(ADDRESS, STORAGE),
|
||||
Ok(Some(entry_at7.value))
|
||||
@ -368,5 +428,13 @@ mod tests {
|
||||
HistoricalStateProviderRef::new(&tx, 16).storage(ADDRESS, STORAGE),
|
||||
Ok(Some(entry_plain.value))
|
||||
);
|
||||
assert_eq!(
|
||||
HistoricalStateProviderRef::new(&tx, 1).storage(HIGHER_ADDRESS, STORAGE),
|
||||
Ok(None)
|
||||
);
|
||||
assert_eq!(
|
||||
HistoricalStateProviderRef::new(&tx, 1000).storage(HIGHER_ADDRESS, STORAGE),
|
||||
Ok(Some(higher_entry_plain.value))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user