feat(trie): expose storage proofs (#11550)

This commit is contained in:
Roman Krasiuk
2024-10-08 11:47:15 +02:00
committed by GitHub
parent 6e92ab8e43
commit 548f5b9c6d
15 changed files with 221 additions and 28 deletions

View File

@ -872,7 +872,7 @@ mod tests {
AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateRootProvider,
StorageRootProvider,
};
use reth_trie::{AccountProof, HashedStorage, MultiProof, TrieInput};
use reth_trie::{AccountProof, HashedStorage, MultiProof, StorageProof, TrieInput};
fn create_mock_state(
test_block_builder: &mut TestBlockBuilder,
@ -973,6 +973,15 @@ mod tests {
) -> ProviderResult<B256> {
Ok(B256::random())
}
fn storage_proof(
&self,
_address: Address,
slot: B256,
_hashed_storage: HashedStorage,
) -> ProviderResult<StorageProof> {
Ok(StorageProof::new(slot))
}
}
impl StateProofProvider for MockStateProvider {

View File

@ -133,11 +133,26 @@ impl StateRootProvider for MemoryOverlayStateProvider {
impl StorageRootProvider for MemoryOverlayStateProvider {
// TODO: Currently this does not reuse available in-memory trie nodes.
fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult<B256> {
let state = &self.trie_state().state;
let mut hashed_storage =
self.trie_state().state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
hashed_storage.extend(&storage);
self.historical.storage_root(address, hashed_storage)
}
// TODO: Currently this does not reuse available in-memory trie nodes.
fn storage_proof(
&self,
address: Address,
slot: B256,
storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
let state = &self.trie_state().state;
let mut hashed_storage =
state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
hashed_storage.extend(&storage);
self.historical.storage_proof(address, slot, hashed_storage)
}
}
impl StateProofProvider for MemoryOverlayStateProvider {

View File

@ -11,7 +11,8 @@ use reth_storage_api::{
};
use reth_storage_errors::provider::ProviderResult;
use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, StorageProof,
TrieInput,
};
/// Mock state for testing
@ -102,6 +103,15 @@ impl StorageRootProvider for StateProviderTest {
) -> ProviderResult<B256> {
unimplemented!("storage root is not supported")
}
fn storage_proof(
&self,
_address: Address,
_slot: B256,
_hashed_storage: HashedStorage,
) -> ProviderResult<StorageProof> {
unimplemented!("proof generation is not supported")
}
}
impl StateProofProvider for StateProviderTest {

View File

@ -58,6 +58,15 @@ impl reth_storage_api::StorageRootProvider for StateProviderTraitObjWrapper<'_>
) -> ProviderResult<B256> {
self.0.storage_root(address, hashed_storage)
}
fn storage_proof(
&self,
address: Address,
slot: B256,
hashed_storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
self.0.storage_proof(address, slot, hashed_storage)
}
}
impl reth_storage_api::StateProofProvider for StateProviderTraitObjWrapper<'_> {

View File

@ -31,6 +31,20 @@ impl<SP: StateProvider, EDP: ExecutionDataProvider> BundleStateProvider<SP, EDP>
pub const fn new(state_provider: SP, block_execution_data_provider: EDP) -> Self {
Self { state_provider, block_execution_data_provider }
}
/// Retrieve hashed storage for target address.
fn get_hashed_storage(&self, address: Address) -> HashedStorage {
let bundle_state = self.block_execution_data_provider.execution_outcome().state();
bundle_state
.account(&address)
.map(|account| {
HashedStorage::from_plain_storage(
account.status,
account.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
)
})
.unwrap_or_else(|| HashedStorage::new(false))
}
}
/* Implement StateProvider traits */
@ -109,19 +123,21 @@ impl<SP: StateProvider, EDP: ExecutionDataProvider> StorageRootProvider
address: Address,
hashed_storage: HashedStorage,
) -> ProviderResult<B256> {
let bundle_state = self.block_execution_data_provider.execution_outcome().state();
let mut storage = bundle_state
.account(&address)
.map(|account| {
HashedStorage::from_plain_storage(
account.status,
account.storage.iter().map(|(slot, value)| (slot, &value.present_value)),
)
})
.unwrap_or_else(|| HashedStorage::new(false));
let mut storage = self.get_hashed_storage(address);
storage.extend(&hashed_storage);
self.state_provider.storage_root(address, storage)
}
fn storage_proof(
&self,
address: Address,
slot: B256,
hashed_storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
let mut storage = self.get_hashed_storage(address);
storage.extend(&hashed_storage);
self.state_provider.storage_proof(address, slot, storage)
}
}
impl<SP: StateProvider, EDP: ExecutionDataProvider> StateProofProvider

View File

@ -17,12 +17,14 @@ use reth_primitives::{constants::EPOCH_SLOTS, Account, Bytecode, StaticFileSegme
use reth_storage_api::{StateProofProvider, StorageRootProvider};
use reth_storage_errors::provider::ProviderResult;
use reth_trie::{
proof::Proof, updates::TrieUpdates, witness::TrieWitness, AccountProof, HashedPostState,
HashedStorage, MultiProof, StateRoot, StorageRoot, TrieInput,
proof::{Proof, StorageProof},
updates::TrieUpdates,
witness::TrieWitness,
AccountProof, HashedPostState, HashedStorage, MultiProof, StateRoot, StorageRoot, TrieInput,
};
use reth_trie_db::{
DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot,
DatabaseStorageRoot, DatabaseTrieWitness,
DatabaseStorageProof, DatabaseStorageRoot, DatabaseTrieWitness,
};
use std::fmt::Debug;
@ -330,6 +332,18 @@ impl<TX: DbTx> StorageRootProvider for HistoricalStateProviderRef<'_, TX> {
StorageRoot::overlay_root(self.tx, address, revert_storage)
.map_err(|err| ProviderError::Database(err.into()))
}
fn storage_proof(
&self,
address: Address,
slot: B256,
hashed_storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
let mut revert_storage = self.revert_storage(address)?;
revert_storage.extend(&hashed_storage);
StorageProof::overlay_storage_proof(self.tx, address, slot, revert_storage)
.map_err(Into::<ProviderError>::into)
}
}
impl<TX: DbTx> StateProofProvider for HistoricalStateProviderRef<'_, TX> {

View File

@ -15,10 +15,15 @@ use reth_primitives::{Account, Bytecode, StaticFileSegment};
use reth_storage_api::{StateProofProvider, StorageRootProvider};
use reth_storage_errors::provider::{ProviderError, ProviderResult};
use reth_trie::{
proof::Proof, updates::TrieUpdates, witness::TrieWitness, AccountProof, HashedPostState,
HashedStorage, MultiProof, StateRoot, StorageRoot, TrieInput,
proof::{Proof, StorageProof},
updates::TrieUpdates,
witness::TrieWitness,
AccountProof, HashedPostState, HashedStorage, MultiProof, StateRoot, StorageRoot, TrieInput,
};
use reth_trie_db::{
DatabaseProof, DatabaseStateRoot, DatabaseStorageProof, DatabaseStorageRoot,
DatabaseTrieWitness,
};
use reth_trie_db::{DatabaseProof, DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieWitness};
/// State provider over latest state that takes tx reference.
#[derive(Debug)]
@ -116,6 +121,16 @@ impl<TX: DbTx> StorageRootProvider for LatestStateProviderRef<'_, TX> {
StorageRoot::overlay_root(self.tx, address, hashed_storage)
.map_err(|err| ProviderError::Database(err.into()))
}
fn storage_proof(
&self,
address: Address,
slot: B256,
hashed_storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
StorageProof::overlay_storage_proof(self.tx, address, slot, hashed_storage)
.map_err(Into::<ProviderError>::into)
}
}
impl<TX: DbTx> StateProofProvider for LatestStateProviderRef<'_, TX> {

View File

@ -49,6 +49,7 @@ macro_rules! delegate_provider_impls {
}
StorageRootProvider $(where [$($generics)*])? {
fn storage_root(&self, address: alloy_primitives::Address, storage: reth_trie::HashedStorage) -> reth_storage_errors::provider::ProviderResult<alloy_primitives::B256>;
fn storage_proof(&self, address: alloy_primitives::Address, slot: alloy_primitives::B256, storage: reth_trie::HashedStorage) -> reth_storage_errors::provider::ProviderResult<reth_trie::StorageProof>;
}
StateProofProvider $(where [$($generics)*])? {
fn proof(&self, input: reth_trie::TrieInput, address: alloy_primitives::Address, slots: &[alloy_primitives::B256]) -> reth_storage_errors::provider::ProviderResult<reth_trie::AccountProof>;

View File

@ -31,7 +31,8 @@ use reth_storage_api::{
};
use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult};
use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, StorageProof,
TrieInput,
};
use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg};
use std::{
@ -639,6 +640,15 @@ impl StorageRootProvider for MockEthProvider {
) -> ProviderResult<B256> {
Ok(EMPTY_ROOT_HASH)
}
fn storage_proof(
&self,
_address: Address,
slot: B256,
_hashed_storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
Ok(StorageProof::new(slot))
}
}
impl StateProofProvider for MockEthProvider {

View File

@ -356,6 +356,15 @@ impl StorageRootProvider for NoopProvider {
) -> ProviderResult<B256> {
Ok(B256::default())
}
fn storage_proof(
&self,
_address: Address,
slot: B256,
_hashed_storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
Ok(reth_trie::StorageProof::new(slot))
}
}
impl StateProofProvider for NoopProvider {

View File

@ -4,7 +4,8 @@ use alloy_primitives::{
};
use reth_storage_errors::provider::ProviderResult;
use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, StorageProof,
TrieInput,
};
/// A type that can compute the state root of a given post state.
@ -46,6 +47,15 @@ pub trait StorageRootProvider: Send + Sync {
/// state.
fn storage_root(&self, address: Address, hashed_storage: HashedStorage)
-> ProviderResult<B256>;
/// Returns the storage proof of the `HashedStorage` for target slot on top of the current
/// state.
fn storage_proof(
&self,
address: Address,
slot: B256,
hashed_storage: HashedStorage,
) -> ProviderResult<StorageProof>;
}
/// A type that can generate state proof on top of a given post state.

View File

@ -12,7 +12,7 @@ pub use hashed_cursor::{
DatabaseHashedAccountCursor, DatabaseHashedCursorFactory, DatabaseHashedStorageCursor,
};
pub use prefix_set::PrefixSetLoader;
pub use proof::DatabaseProof;
pub use proof::{DatabaseProof, DatabaseStorageProof};
pub use state::{DatabaseHashedPostState, DatabaseStateRoot};
pub use storage::{DatabaseHashedStorage, DatabaseStorageRoot};
pub use trie_cursor::{

View File

@ -1,13 +1,16 @@
use crate::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory};
use alloy_primitives::{
keccak256,
map::{HashMap, HashSet},
Address, B256,
};
use reth_db_api::transaction::DbTx;
use reth_execution_errors::StateProofError;
use reth_trie::{
hashed_cursor::HashedPostStateCursorFactory, proof::Proof,
trie_cursor::InMemoryTrieCursorFactory, MultiProof, TrieInput,
hashed_cursor::HashedPostStateCursorFactory,
proof::{Proof, StorageProof},
trie_cursor::InMemoryTrieCursorFactory,
HashedPostStateSorted, HashedStorage, MultiProof, TrieInput,
};
use reth_trie_common::AccountProof;
@ -81,3 +84,46 @@ impl<'a, TX: DbTx> DatabaseProof<'a, TX>
.multiproof(targets)
}
}
/// Extends [`StorageProof`] with operations specific for working with a database transaction.
pub trait DatabaseStorageProof<'a, TX> {
/// Create a new [`StorageProof`] from database transaction and account address.
fn from_tx(tx: &'a TX, address: Address) -> Self;
/// Generates the storage proof for target slot based on [`TrieInput`].
fn overlay_storage_proof(
tx: &'a TX,
address: Address,
slot: B256,
storage: HashedStorage,
) -> Result<reth_trie_common::StorageProof, StateProofError>;
}
impl<'a, TX: DbTx> DatabaseStorageProof<'a, TX>
for StorageProof<DatabaseTrieCursorFactory<'a, TX>, DatabaseHashedCursorFactory<'a, TX>>
{
fn from_tx(tx: &'a TX, address: Address) -> Self {
Self::new(DatabaseTrieCursorFactory::new(tx), DatabaseHashedCursorFactory::new(tx), address)
}
fn overlay_storage_proof(
tx: &'a TX,
address: Address,
slot: B256,
storage: HashedStorage,
) -> Result<reth_trie_common::StorageProof, StateProofError> {
let hashed_address = keccak256(address);
let prefix_set = storage.construct_prefix_set();
let state_sorted = HashedPostStateSorted::new(
Default::default(),
HashMap::from([(hashed_address, storage.into_sorted())]),
);
Self::from_tx(tx, address)
.with_hashed_cursor_factory(HashedPostStateCursorFactory::new(
DatabaseHashedCursorFactory::new(tx),
&state_sorted,
))
.with_prefix_set_mut(prefix_set)
.storage_proof(slot)
}
}

View File

@ -124,7 +124,7 @@ where
hashed_address,
)
.with_prefix_set_mut(storage_prefix_set)
.storage_proof(proof_targets)?;
.storage_multiproof(proof_targets)?;
// Encode account
account_rlp.clear();
@ -170,6 +170,26 @@ impl<T, H> StorageProof<T, H> {
}
}
/// Set the trie cursor factory.
pub fn with_trie_cursor_factory<TF>(self, trie_cursor_factory: TF) -> StorageProof<TF, H> {
StorageProof {
trie_cursor_factory,
hashed_cursor_factory: self.hashed_cursor_factory,
hashed_address: self.hashed_address,
prefix_set: self.prefix_set,
}
}
/// Set the hashed cursor factory.
pub fn with_hashed_cursor_factory<HF>(self, hashed_cursor_factory: HF) -> StorageProof<T, HF> {
StorageProof {
trie_cursor_factory: self.trie_cursor_factory,
hashed_cursor_factory,
hashed_address: self.hashed_address,
prefix_set: self.prefix_set,
}
}
/// Set the changed prefixes.
pub fn with_prefix_set_mut(mut self, prefix_set: PrefixSetMut) -> Self {
self.prefix_set = prefix_set;
@ -182,8 +202,17 @@ where
T: TrieCursorFactory,
H: HashedCursorFactory,
{
/// Generate storage proof.
/// Generate an account proof from intermediate nodes.
pub fn storage_proof(
self,
slot: B256,
) -> Result<reth_trie_common::StorageProof, StateProofError> {
let targets = HashSet::from_iter([keccak256(slot)]);
Ok(self.storage_multiproof(targets)?.storage_proof(slot)?)
}
/// Generate storage proof.
pub fn storage_multiproof(
mut self,
targets: HashSet<B256>,
) -> Result<StorageMultiProof, StateProofError> {

View File

@ -178,7 +178,7 @@ where
hashed_address,
)
.with_prefix_set_mut(storage_prefix_set)
.storage_proof(HashSet::from_iter([target_key]))?;
.storage_multiproof(HashSet::from_iter([target_key]))?;
// The subtree only contains the proof for a single target.
let node =