feat(trie): expose multiproof via StateProofProvider (#10915)

This commit is contained in:
Roman Krasiuk
2024-09-16 11:52:15 +02:00
committed by GitHub
parent fdd64972b9
commit 06dbd3a610
15 changed files with 173 additions and 31 deletions

View File

@ -839,7 +839,8 @@ mod tests {
AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateRootProvider, AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateRootProvider,
StorageRootProvider, StorageRootProvider,
}; };
use reth_trie::{AccountProof, HashedStorage, TrieInput}; use reth_trie::{AccountProof, HashedStorage, MultiProof, TrieInput};
use std::collections::HashSet;
fn create_mock_state( fn create_mock_state(
test_block_builder: &mut TestBlockBuilder, test_block_builder: &mut TestBlockBuilder,
@ -952,6 +953,14 @@ mod tests {
Ok(AccountProof::new(Address::random())) Ok(AccountProof::new(Address::random()))
} }
fn multiproof(
&self,
_input: TrieInput,
_targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
Ok(MultiProof::default())
}
fn witness( fn witness(
&self, &self,
_input: TrieInput, _input: TrieInput,

View File

@ -7,8 +7,13 @@ use reth_storage_api::{
AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateProviderBox, AccountReader, BlockHashReader, StateProofProvider, StateProvider, StateProviderBox,
StateRootProvider, StorageRootProvider, StateRootProvider, StorageRootProvider,
}; };
use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, TrieInput}; use reth_trie::{
use std::{collections::HashMap, sync::OnceLock}; updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
};
use std::{
collections::{HashMap, HashSet},
sync::OnceLock,
};
/// A state provider that stores references to in-memory blocks along with their state as well as /// A state provider that stores references to in-memory blocks along with their state as well as
/// the historical state provider for fallback lookups. /// the historical state provider for fallback lookups.
@ -147,6 +152,16 @@ impl StateProofProvider for MemoryOverlayStateProvider {
self.historical.proof(input, address, slots) self.historical.proof(input, address, slots)
} }
fn multiproof(
&self,
mut input: TrieInput,
targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
let MemoryOverlayTrieState { nodes, state } = self.trie_state().clone();
input.prepend_cached(nodes, state);
self.historical.multiproof(input, targets)
}
fn witness( fn witness(
&self, &self,
mut input: TrieInput, mut input: TrieInput,

View File

@ -1,3 +1,5 @@
use std::collections::HashSet;
use crate::precompile::HashMap; use crate::precompile::HashMap;
use alloc::vec::Vec; use alloc::vec::Vec;
use reth_primitives::{ use reth_primitives::{
@ -8,7 +10,9 @@ use reth_storage_api::{
StorageRootProvider, StorageRootProvider,
}; };
use reth_storage_errors::provider::ProviderResult; use reth_storage_errors::provider::ProviderResult;
use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, TrieInput}; use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
};
/// Mock state for testing /// Mock state for testing
#[derive(Debug, Default, Clone, Eq, PartialEq)] #[derive(Debug, Default, Clone, Eq, PartialEq)]
@ -110,6 +114,14 @@ impl StateProofProvider for StateProviderTest {
unimplemented!("proof generation is not supported") unimplemented!("proof generation is not supported")
} }
fn multiproof(
&self,
_input: TrieInput,
_targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
unimplemented!("proof generation is not supported")
}
fn witness( fn witness(
&self, &self,
_input: TrieInput, _input: TrieInput,

View File

@ -67,6 +67,14 @@ impl<'a> reth_storage_api::StateProofProvider for StateProviderTraitObjWrapper<'
self.0.proof(input, address, slots) self.0.proof(input, address, slots)
} }
fn multiproof(
&self,
input: reth_trie::TrieInput,
targets: std::collections::HashMap<B256, std::collections::HashSet<B256>>,
) -> ProviderResult<reth_trie::MultiProof> {
self.0.multiproof(input, targets)
}
fn witness( fn witness(
&self, &self,
input: reth_trie::TrieInput, input: reth_trie::TrieInput,

View File

@ -4,8 +4,10 @@ use crate::{
use reth_primitives::{Account, Address, BlockNumber, Bytecode, Bytes, B256}; use reth_primitives::{Account, Address, BlockNumber, Bytecode, Bytes, B256};
use reth_storage_api::{StateProofProvider, StorageRootProvider}; use reth_storage_api::{StateProofProvider, StorageRootProvider};
use reth_storage_errors::provider::ProviderResult; use reth_storage_errors::provider::ProviderResult;
use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, TrieInput}; use reth_trie::{
use std::collections::HashMap; updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
};
use std::collections::{HashMap, HashSet};
/// A state provider that resolves to data from either a wrapped [`crate::ExecutionOutcome`] /// A state provider that resolves to data from either a wrapped [`crate::ExecutionOutcome`]
/// or an underlying state provider. /// or an underlying state provider.
@ -133,6 +135,16 @@ impl<SP: StateProvider, EDP: ExecutionDataProvider> StateProofProvider
self.state_provider.proof(input, address, slots) self.state_provider.proof(input, address, slots)
} }
fn multiproof(
&self,
mut input: reth_trie::TrieInput,
targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
let bundle_state = self.block_execution_data_provider.execution_outcome().state();
input.prepend(HashedPostState::from_bundle_state(&bundle_state.state));
self.state_provider.multiproof(input, targets)
}
fn witness( fn witness(
&self, &self,
mut input: TrieInput, mut input: TrieInput,

View File

@ -17,13 +17,16 @@ use reth_storage_api::{StateProofProvider, StorageRootProvider};
use reth_storage_errors::provider::ProviderResult; use reth_storage_errors::provider::ProviderResult;
use reth_trie::{ use reth_trie::{
proof::Proof, updates::TrieUpdates, witness::TrieWitness, AccountProof, HashedPostState, proof::Proof, updates::TrieUpdates, witness::TrieWitness, AccountProof, HashedPostState,
HashedStorage, StateRoot, StorageRoot, TrieInput, HashedStorage, MultiProof, StateRoot, StorageRoot, TrieInput,
}; };
use reth_trie_db::{ use reth_trie_db::{
DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot, DatabaseHashedPostState, DatabaseHashedStorage, DatabaseProof, DatabaseStateRoot,
DatabaseStorageRoot, DatabaseTrieWitness, DatabaseStorageRoot, DatabaseTrieWitness,
}; };
use std::{collections::HashMap, fmt::Debug}; use std::{
collections::{HashMap, HashSet},
fmt::Debug,
};
/// State provider for a given block number which takes a tx reference. /// State provider for a given block number which takes a tx reference.
/// ///
@ -344,6 +347,15 @@ impl<'b, TX: DbTx> StateProofProvider for HistoricalStateProviderRef<'b, TX> {
.map_err(Into::<ProviderError>::into) .map_err(Into::<ProviderError>::into)
} }
fn multiproof(
&self,
mut input: TrieInput,
targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
input.prepend(self.revert_state()?);
Proof::overlay_multiproof(self.tx, input, targets).map_err(Into::<ProviderError>::into)
}
fn witness( fn witness(
&self, &self,
mut input: TrieInput, mut input: TrieInput,

View File

@ -15,10 +15,10 @@ use reth_storage_api::{StateProofProvider, StorageRootProvider};
use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_storage_errors::provider::{ProviderError, ProviderResult};
use reth_trie::{ use reth_trie::{
proof::Proof, updates::TrieUpdates, witness::TrieWitness, AccountProof, HashedPostState, proof::Proof, updates::TrieUpdates, witness::TrieWitness, AccountProof, HashedPostState,
HashedStorage, StateRoot, StorageRoot, TrieInput, HashedStorage, MultiProof, StateRoot, StorageRoot, TrieInput,
}; };
use reth_trie_db::{DatabaseProof, DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieWitness}; use reth_trie_db::{DatabaseProof, DatabaseStateRoot, DatabaseStorageRoot, DatabaseTrieWitness};
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
/// State provider over latest state that takes tx reference. /// State provider over latest state that takes tx reference.
#[derive(Debug)] #[derive(Debug)]
@ -129,6 +129,14 @@ impl<'b, TX: DbTx> StateProofProvider for LatestStateProviderRef<'b, TX> {
.map_err(Into::<ProviderError>::into) .map_err(Into::<ProviderError>::into)
} }
fn multiproof(
&self,
input: TrieInput,
targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
Proof::overlay_multiproof(self.tx, input, targets).map_err(Into::<ProviderError>::into)
}
fn witness( fn witness(
&self, &self,
input: TrieInput, input: TrieInput,

View File

@ -52,6 +52,7 @@ macro_rules! delegate_provider_impls {
} }
StateProofProvider $(where [$($generics)*])? { StateProofProvider $(where [$($generics)*])? {
fn proof(&self, input: reth_trie::TrieInput, address: reth_primitives::Address, slots: &[reth_primitives::B256]) -> reth_storage_errors::provider::ProviderResult<reth_trie::AccountProof>; fn proof(&self, input: reth_trie::TrieInput, address: reth_primitives::Address, slots: &[reth_primitives::B256]) -> reth_storage_errors::provider::ProviderResult<reth_trie::AccountProof>;
fn multiproof(&self, input: reth_trie::TrieInput, targets: std::collections::HashMap<reth_primitives::B256, std::collections::HashSet<reth_primitives::B256>>) -> reth_storage_errors::provider::ProviderResult<reth_trie::MultiProof>;
fn witness(&self, input: reth_trie::TrieInput, target: reth_trie::HashedPostState) -> reth_storage_errors::provider::ProviderResult<std::collections::HashMap<reth_primitives::B256, reth_primitives::Bytes>>; fn witness(&self, input: reth_trie::TrieInput, target: reth_trie::HashedPostState) -> reth_storage_errors::provider::ProviderResult<std::collections::HashMap<reth_primitives::B256, reth_primitives::Bytes>>;
} }
); );

View File

@ -24,10 +24,12 @@ use reth_storage_api::{
DatabaseProviderFactory, StageCheckpointReader, StateProofProvider, StorageRootProvider, DatabaseProviderFactory, StageCheckpointReader, StateProofProvider, StorageRootProvider,
}; };
use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult}; use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult};
use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, TrieInput}; use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
};
use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg}; use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg};
use std::{ use std::{
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap, HashSet},
ops::{RangeBounds, RangeInclusive}, ops::{RangeBounds, RangeInclusive},
sync::Arc, sync::Arc,
}; };
@ -624,6 +626,14 @@ impl StateProofProvider for MockEthProvider {
Ok(AccountProof::new(address)) Ok(AccountProof::new(address))
} }
fn multiproof(
&self,
_input: TrieInput,
_targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
Ok(MultiProof::default())
}
fn witness( fn witness(
&self, &self,
_input: TrieInput, _input: TrieInput,

View File

@ -1,5 +1,5 @@
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
ops::{RangeBounds, RangeInclusive}, ops::{RangeBounds, RangeInclusive},
sync::Arc, sync::Arc,
}; };
@ -22,7 +22,9 @@ use reth_prune_types::{PruneCheckpoint, PruneSegment};
use reth_stages_types::{StageCheckpoint, StageId}; use reth_stages_types::{StageCheckpoint, StageId};
use reth_storage_api::{StateProofProvider, StorageRootProvider}; use reth_storage_api::{StateProofProvider, StorageRootProvider};
use reth_storage_errors::provider::ProviderResult; use reth_storage_errors::provider::ProviderResult;
use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, TrieInput}; use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
};
use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg}; use revm::primitives::{BlockEnv, CfgEnvWithHandlerCfg};
use tokio::sync::{broadcast, watch}; use tokio::sync::{broadcast, watch};
@ -362,6 +364,14 @@ impl StateProofProvider for NoopProvider {
Ok(AccountProof::new(address)) Ok(AccountProof::new(address))
} }
fn multiproof(
&self,
_input: TrieInput,
_targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof> {
Ok(MultiProof::default())
}
fn witness( fn witness(
&self, &self,
_input: TrieInput, _input: TrieInput,

View File

@ -1,7 +1,9 @@
use alloy_primitives::{Address, Bytes, B256}; use alloy_primitives::{Address, Bytes, B256};
use reth_storage_errors::provider::ProviderResult; use reth_storage_errors::provider::ProviderResult;
use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, TrieInput}; use reth_trie::{
use std::collections::HashMap; updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, TrieInput,
};
use std::collections::{HashMap, HashSet};
/// A type that can compute the state root of a given post state. /// A type that can compute the state root of a given post state.
#[auto_impl::auto_impl(&, Box, Arc)] #[auto_impl::auto_impl(&, Box, Arc)]
@ -56,6 +58,14 @@ pub trait StateProofProvider: Send + Sync {
slots: &[B256], slots: &[B256],
) -> ProviderResult<AccountProof>; ) -> ProviderResult<AccountProof>;
/// Generate [`MultiProof`] for target hashed account and corresponding
/// hashed storage slot keys.
fn multiproof(
&self,
input: TrieInput,
targets: HashMap<B256, HashSet<B256>>,
) -> ProviderResult<MultiProof>;
/// Get trie witness for provided state. /// Get trie witness for provided state.
fn witness( fn witness(
&self, &self,

View File

@ -13,6 +13,8 @@ use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
/// The state multiproof of target accounts and multiproofs of their storage tries. /// The state multiproof of target accounts and multiproofs of their storage tries.
/// Multiproof is effectively a state subtrie that only contains the nodes
/// in the paths of target accounts.
#[derive(Clone, Default, Debug)] #[derive(Clone, Default, Debug)]
pub struct MultiProof { pub struct MultiProof {
/// State trie multiproof for requested accounts. /// State trie multiproof for requested accounts.

View File

@ -4,9 +4,10 @@ use reth_execution_errors::StateProofError;
use reth_primitives::{Address, B256}; use reth_primitives::{Address, B256};
use reth_trie::{ use reth_trie::{
hashed_cursor::HashedPostStateCursorFactory, proof::Proof, hashed_cursor::HashedPostStateCursorFactory, proof::Proof,
trie_cursor::InMemoryTrieCursorFactory, TrieInput, trie_cursor::InMemoryTrieCursorFactory, MultiProof, TrieInput,
}; };
use reth_trie_common::AccountProof; use reth_trie_common::AccountProof;
use std::collections::{HashMap, HashSet};
/// Extends [`Proof`] with operations specific for working with a database transaction. /// Extends [`Proof`] with operations specific for working with a database transaction.
pub trait DatabaseProof<'a, TX> { pub trait DatabaseProof<'a, TX> {
@ -20,6 +21,13 @@ pub trait DatabaseProof<'a, TX> {
address: Address, address: Address,
slots: &[B256], slots: &[B256],
) -> Result<AccountProof, StateProofError>; ) -> Result<AccountProof, StateProofError>;
/// Generates the state [`MultiProof`] for target hashed account and storage keys.
fn overlay_multiproof(
tx: &'a TX,
input: TrieInput,
targets: HashMap<B256, HashSet<B256>>,
) -> Result<MultiProof, StateProofError>;
} }
impl<'a, TX: DbTx> DatabaseProof<'a, TX> impl<'a, TX: DbTx> DatabaseProof<'a, TX>
@ -50,4 +58,25 @@ impl<'a, TX: DbTx> DatabaseProof<'a, TX>
.with_prefix_sets_mut(input.prefix_sets) .with_prefix_sets_mut(input.prefix_sets)
.account_proof(address, slots) .account_proof(address, slots)
} }
fn overlay_multiproof(
tx: &'a TX,
input: TrieInput,
targets: HashMap<B256, HashSet<B256>>,
) -> Result<MultiProof, StateProofError> {
let nodes_sorted = input.nodes.into_sorted();
let state_sorted = input.state.into_sorted();
Self::from_tx(tx)
.with_trie_cursor_factory(InMemoryTrieCursorFactory::new(
DatabaseTrieCursorFactory::new(tx),
&nodes_sorted,
))
.with_hashed_cursor_factory(HashedPostStateCursorFactory::new(
DatabaseHashedCursorFactory::new(tx),
&state_sorted,
))
.with_prefix_sets_mut(input.prefix_sets)
.with_targets(targets)
.multiproof()
}
} }

View File

@ -12,7 +12,7 @@ use reth_execution_errors::trie::StateProofError;
use reth_trie_common::{ use reth_trie_common::{
proof::ProofRetainer, AccountProof, MultiProof, StorageMultiProof, TrieAccount, proof::ProofRetainer, AccountProof, MultiProof, StorageMultiProof, TrieAccount,
}; };
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
/// A struct for generating merkle proofs. /// A struct for generating merkle proofs.
/// ///
@ -28,7 +28,7 @@ pub struct Proof<T, H> {
/// A set of prefix sets that have changes. /// A set of prefix sets that have changes.
prefix_sets: TriePrefixSetsMut, prefix_sets: TriePrefixSetsMut,
/// Proof targets. /// Proof targets.
targets: HashMap<B256, Vec<B256>>, targets: HashMap<B256, HashSet<B256>>,
} }
impl<T, H> Proof<T, H> { impl<T, H> Proof<T, H> {
@ -68,8 +68,13 @@ impl<T, H> Proof<T, H> {
self self
} }
/// Set the target account and slots.
pub fn with_target(self, target: (B256, HashSet<B256>)) -> Self {
self.with_targets(HashMap::from([target]))
}
/// Set the target accounts and slots. /// Set the target accounts and slots.
pub fn with_targets(mut self, targets: HashMap<B256, Vec<B256>>) -> Self { pub fn with_targets(mut self, targets: HashMap<B256, HashSet<B256>>) -> Self {
self.targets = targets; self.targets = targets;
self self
} }
@ -87,10 +92,7 @@ where
slots: &[B256], slots: &[B256],
) -> Result<AccountProof, StateProofError> { ) -> Result<AccountProof, StateProofError> {
Ok(self Ok(self
.with_targets(HashMap::from([( .with_target((keccak256(address), slots.iter().map(keccak256).collect()))
keccak256(address),
slots.iter().map(keccak256).collect(),
)]))
.multiproof()? .multiproof()?
.account_proof(address, slots)?) .account_proof(address, slots)?)
} }

View File

@ -79,11 +79,13 @@ where
state: HashedPostState, state: HashedPostState,
) -> Result<HashMap<B256, Bytes>, TrieWitnessError> { ) -> Result<HashMap<B256, Bytes>, TrieWitnessError> {
let proof_targets = HashMap::from_iter( let proof_targets = HashMap::from_iter(
state.accounts.keys().map(|hashed_address| (*hashed_address, Vec::new())).chain( state
state.storages.iter().map(|(hashed_address, storage)| { .accounts
.keys()
.map(|hashed_address| (*hashed_address, HashSet::default()))
.chain(state.storages.iter().map(|(hashed_address, storage)| {
(*hashed_address, storage.storage.keys().copied().collect()) (*hashed_address, storage.storage.keys().copied().collect())
}), })),
),
); );
let mut account_multiproof = let mut account_multiproof =
Proof::new(self.trie_cursor_factory.clone(), self.hashed_cursor_factory.clone()) Proof::new(self.trie_cursor_factory.clone(), self.hashed_cursor_factory.clone())
@ -137,13 +139,13 @@ where
// Right pad the target with 0s. // Right pad the target with 0s.
let mut padded_key = key.pack(); let mut padded_key = key.pack();
padded_key.resize(32, 0); padded_key.resize(32, 0);
let target = (hashed_address, Vec::from([B256::from_slice(&padded_key)])); let target_key = B256::from_slice(&padded_key);
let mut proof = Proof::new( let mut proof = Proof::new(
self.trie_cursor_factory.clone(), self.trie_cursor_factory.clone(),
self.hashed_cursor_factory.clone(), self.hashed_cursor_factory.clone(),
) )
.with_prefix_sets_mut(self.prefix_sets.clone()) .with_prefix_sets_mut(self.prefix_sets.clone())
.with_targets(HashMap::from([target])) .with_target((hashed_address, HashSet::from([target_key])))
.storage_multiproof(hashed_address)?; .storage_multiproof(hashed_address)?;
// The subtree only contains the proof for a single target. // The subtree only contains the proof for a single target.
@ -162,7 +164,7 @@ where
let mut proof = let mut proof =
Proof::new(self.trie_cursor_factory.clone(), self.hashed_cursor_factory.clone()) Proof::new(self.trie_cursor_factory.clone(), self.hashed_cursor_factory.clone())
.with_prefix_sets_mut(self.prefix_sets.clone()) .with_prefix_sets_mut(self.prefix_sets.clone())
.with_targets(HashMap::from([(B256::from_slice(&padded_key), Vec::new())])) .with_target((B256::from_slice(&padded_key), HashSet::default()))
.multiproof()?; .multiproof()?;
// The subtree only contains the proof for a single target. // The subtree only contains the proof for a single target.