mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-28 16:48:13 -05:00
feat(rpc): add partial eth_getProof implementation (#1515)
Co-authored-by: lambdaclass-user <github@lambdaclass.com>
This commit is contained in:
@@ -599,6 +599,14 @@ mod tests {
|
||||
fn bytecode_by_hash(&self, code_hash: H256) -> reth_interfaces::Result<Option<Bytecode>> {
|
||||
Ok(self.contracts.get(&code_hash).cloned())
|
||||
}
|
||||
|
||||
fn proof(
|
||||
&self,
|
||||
_address: Address,
|
||||
_keys: &[H256],
|
||||
) -> reth_interfaces::Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -68,6 +68,9 @@ pub enum ProviderError {
|
||||
/// Reached the end of the transaction sender table.
|
||||
#[error("Got to the end of the transaction sender table")]
|
||||
EndOfTransactionSenderTable,
|
||||
/// Some error occurred while interacting with the state tree.
|
||||
#[error("Unknown error occurred while interacting with the state tree.")]
|
||||
StateTree,
|
||||
/// Thrown when required header related data was not found but was required.
|
||||
#[error("requested data not found")]
|
||||
HeaderNotFound,
|
||||
|
||||
@@ -34,6 +34,15 @@ impl Account {
|
||||
|
||||
self.nonce == 0 && self.balance == U256::ZERO && is_bytecode_empty
|
||||
}
|
||||
|
||||
/// Returns an account bytecode's hash.
|
||||
/// In case of no bytecode, returns [`KECCAK_EMPTY`].
|
||||
pub fn get_bytecode_hash(&self) -> H256 {
|
||||
match self.bytecode_hash {
|
||||
Some(hash) => hash,
|
||||
None => KECCAK_EMPTY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bytecode for an account.
|
||||
|
||||
@@ -73,6 +73,7 @@ where
|
||||
EthApiClient::block_by_number(client, block_number, false).await.unwrap();
|
||||
EthApiClient::block_transaction_count_by_number(client, block_number).await.unwrap();
|
||||
EthApiClient::block_transaction_count_by_hash(client, hash).await.unwrap();
|
||||
EthApiClient::get_proof(client, address, vec![], None).await.unwrap();
|
||||
EthApiClient::block_uncles_count_by_hash(client, hash).await.unwrap();
|
||||
EthApiClient::block_uncles_count_by_number(client, block_number).await.unwrap();
|
||||
EthApiClient::uncle_by_block_hash_and_index(client, hash, index).await.unwrap();
|
||||
@@ -123,9 +124,6 @@ where
|
||||
.err()
|
||||
.unwrap()
|
||||
));
|
||||
assert!(is_unimplemented(
|
||||
EthApiClient::get_proof(client, address, vec![], None).await.err().unwrap()
|
||||
));
|
||||
}
|
||||
|
||||
async fn test_basic_debug_calls<C>(client: &C)
|
||||
|
||||
@@ -25,6 +25,7 @@ use revm::{
|
||||
},
|
||||
Database,
|
||||
};
|
||||
use std::ops::Deref;
|
||||
|
||||
// Gas per transaction not creating a contract.
|
||||
const MIN_TRANSACTION_GAS: u64 = 21_000u64;
|
||||
@@ -66,7 +67,7 @@ where
|
||||
) -> EthResult<(ResultAndState, Env)> {
|
||||
let (cfg, block_env, at) = self.evm_env_at(at).await?;
|
||||
let state = self.state_at_block_id(at)?.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
|
||||
self.call_with(cfg, block_env, request, state, state_overrides)
|
||||
self.call_with(cfg, block_env, request, &*state, state_overrides)
|
||||
}
|
||||
|
||||
/// Executes the call request using the given environment against the state provider
|
||||
@@ -106,7 +107,7 @@ where
|
||||
) -> EthResult<U256> {
|
||||
let (cfg, block_env, at) = self.evm_env_at(at).await?;
|
||||
let state = self.state_at_block_id(at)?.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
|
||||
self.estimate_gas_with(cfg, block_env, request, state)
|
||||
self.estimate_gas_with(cfg, block_env, request, &*state)
|
||||
}
|
||||
|
||||
/// Estimates the gas usage of the `request` with the state.
|
||||
@@ -295,7 +296,7 @@ where
|
||||
cfg.disable_block_gas_limit = true;
|
||||
|
||||
let env = build_call_evm_env(cfg, block, request.clone())?;
|
||||
let mut db = SubState::new(State::new(state));
|
||||
let mut db = SubState::new(State::new(state.deref()));
|
||||
|
||||
let from = request.from.unwrap_or_default();
|
||||
let to = if let Some(to) = request.to {
|
||||
|
||||
@@ -10,8 +10,10 @@ use reth_network_api::NetworkInfo;
|
||||
use reth_primitives::{
|
||||
Address, BlockId, BlockNumberOrTag, ChainInfo, TransactionSigned, H256, U64,
|
||||
};
|
||||
use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderFactory};
|
||||
use std::num::NonZeroUsize;
|
||||
use reth_provider::{
|
||||
BlockProvider, EvmEnvProvider, StateProvider as StateProviderTrait, StateProviderFactory,
|
||||
};
|
||||
use std::{num::NonZeroUsize, ops::Deref};
|
||||
|
||||
use crate::eth::{cache::EthStateCache, error::EthResult};
|
||||
use reth_provider::providers::ChainState;
|
||||
@@ -97,6 +99,37 @@ impl<Client, Pool, Network> EthApi<Client, Pool, Network> {
|
||||
}
|
||||
}
|
||||
|
||||
// Transparent wrapper to enable state access helpers
|
||||
// returning latest state provider when appropiate
|
||||
pub(crate) enum StateProvider<'a, H, L> {
|
||||
History(H),
|
||||
Latest(L),
|
||||
_Unreachable(&'a ()), // like a PhantomData for 'a
|
||||
}
|
||||
|
||||
type HistoryOrLatest<'a, Client> = StateProvider<
|
||||
'a,
|
||||
<Client as StateProviderFactory>::HistorySP<'a>,
|
||||
<Client as StateProviderFactory>::LatestSP<'a>,
|
||||
>;
|
||||
|
||||
impl<'a, H, L> Deref for StateProvider<'a, H, L>
|
||||
where
|
||||
Self: 'a,
|
||||
H: StateProviderTrait + 'a,
|
||||
L: StateProviderTrait + 'a,
|
||||
{
|
||||
type Target = dyn StateProviderTrait + 'a;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
StateProvider::History(h) => h,
|
||||
StateProvider::Latest(l) => l,
|
||||
StateProvider::_Unreachable(()) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === State access helpers ===
|
||||
|
||||
impl<Client, Pool, Network> EthApi<Client, Pool, Network>
|
||||
@@ -119,11 +152,11 @@ where
|
||||
pub(crate) fn state_at_block_id_or_latest(
|
||||
&self,
|
||||
block_id: Option<BlockId>,
|
||||
) -> Result<Option<<Client as StateProviderFactory>::HistorySP<'_>>> {
|
||||
) -> Result<Option<HistoryOrLatest<'_, Client>>> {
|
||||
if let Some(block_id) = block_id {
|
||||
self.state_at_block_id(block_id)
|
||||
} else {
|
||||
self.latest_state()
|
||||
self.latest_state().map(|v| Some(StateProvider::Latest(v)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,9 +164,11 @@ where
|
||||
pub(crate) fn state_at_block_id(
|
||||
&self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<<Client as StateProviderFactory>::HistorySP<'_>>> {
|
||||
) -> Result<Option<HistoryOrLatest<'_, Client>>> {
|
||||
match block_id {
|
||||
BlockId::Hash(hash) => self.state_at_hash(hash.into()).map(Some),
|
||||
BlockId::Hash(hash) => {
|
||||
self.state_at_hash(hash.into()).map(|s| Some(StateProvider::History(s)))
|
||||
}
|
||||
BlockId::Number(num) => self.state_at_block_number(num),
|
||||
}
|
||||
}
|
||||
@@ -144,7 +179,7 @@ where
|
||||
pub(crate) fn state_at_block_number(
|
||||
&self,
|
||||
num: BlockNumberOrTag,
|
||||
) -> Result<Option<<Client as StateProviderFactory>::HistorySP<'_>>> {
|
||||
) -> Result<Option<HistoryOrLatest<'_, Client>>> {
|
||||
if let Some(number) = self.convert_block_number(num)? {
|
||||
self.state_at_number(number).map(Some)
|
||||
} else {
|
||||
@@ -161,18 +196,16 @@ where
|
||||
}
|
||||
|
||||
/// Returns the state at the given block number
|
||||
pub(crate) fn state_at_number(
|
||||
&self,
|
||||
block_number: u64,
|
||||
) -> Result<<Client as StateProviderFactory>::HistorySP<'_>> {
|
||||
self.client().history_by_block_number(block_number)
|
||||
pub(crate) fn state_at_number(&self, block_number: u64) -> Result<HistoryOrLatest<'_, Client>> {
|
||||
match self.convert_block_number(BlockNumberOrTag::Latest)? {
|
||||
Some(num) if num == block_number => self.latest_state().map(StateProvider::Latest),
|
||||
_ => self.client().history_by_block_number(block_number).map(StateProvider::History),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the _latest_ state
|
||||
pub(crate) fn latest_state(
|
||||
&self,
|
||||
) -> Result<Option<<Client as StateProviderFactory>::HistorySP<'_>>> {
|
||||
self.state_at_block_number(BlockNumberOrTag::Latest)
|
||||
pub(crate) fn latest_state(&self) -> Result<<Client as StateProviderFactory>::LatestSP<'_>> {
|
||||
self.client().latest()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -374,17 +374,24 @@ where
|
||||
/// Handler for: `eth_getProof`
|
||||
async fn get_proof(
|
||||
&self,
|
||||
_address: Address,
|
||||
_keys: Vec<H256>,
|
||||
_block_number: Option<BlockId>,
|
||||
address: Address,
|
||||
keys: Vec<H256>,
|
||||
block_number: Option<BlockId>,
|
||||
) -> Result<EIP1186AccountProofResponse> {
|
||||
Err(internal_rpc_err("unimplemented"))
|
||||
let res = EthApi::get_proof(self, address, keys, block_number);
|
||||
|
||||
Ok(res.map_err(|e| match e {
|
||||
EthApiError::InvalidBlockRange => {
|
||||
internal_rpc_err("eth_getProof is unimplemented for historical blocks")
|
||||
}
|
||||
_ => e.into(),
|
||||
})?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::eth::cache::EthStateCache;
|
||||
use crate::{eth::cache::EthStateCache, EthApi};
|
||||
use jsonrpsee::{
|
||||
core::{error::Error as RpcError, RpcResult},
|
||||
types::error::{CallError, INVALID_PARAMS_CODE},
|
||||
@@ -396,8 +403,6 @@ mod tests {
|
||||
use reth_rpc_api::EthApiServer;
|
||||
use reth_transaction_pool::test_utils::testing_pool;
|
||||
|
||||
use crate::EthApi;
|
||||
|
||||
#[tokio::test]
|
||||
/// Handler for: `eth_test_fee_history`
|
||||
async fn test_fee_history() {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
//! Contains RPC handler implementations specific to state.
|
||||
|
||||
use crate::{
|
||||
eth::error::{EthApiError, EthResult},
|
||||
eth::{
|
||||
api::StateProvider,
|
||||
error::{EthApiError, EthResult},
|
||||
},
|
||||
EthApi,
|
||||
};
|
||||
use reth_primitives::{Address, BlockId, Bytes, H256, U256};
|
||||
use reth_provider::{BlockProvider, EvmEnvProvider, StateProvider, StateProviderFactory};
|
||||
use reth_primitives::{Address, BlockId, Bytes, H256, KECCAK_EMPTY, U256};
|
||||
use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderFactory};
|
||||
use reth_rpc_types::{EIP1186AccountProofResponse, StorageProof};
|
||||
|
||||
impl<Client, Pool, Network> EthApi<Client, Pool, Network>
|
||||
where
|
||||
@@ -48,4 +52,50 @@ where
|
||||
let value = state.storage(address, storage_key)?.unwrap_or_default();
|
||||
Ok(H256(value.to_be_bytes()))
|
||||
}
|
||||
|
||||
pub(crate) fn get_proof(
|
||||
&self,
|
||||
address: Address,
|
||||
keys: Vec<H256>,
|
||||
block_id: Option<BlockId>,
|
||||
) -> EthResult<EIP1186AccountProofResponse> {
|
||||
let state =
|
||||
self.state_at_block_id_or_latest(block_id)?.ok_or(EthApiError::UnknownBlockNumber)?;
|
||||
|
||||
// TODO: remove when HistoricalStateProviderRef::proof is implemented
|
||||
if matches!(state, StateProvider::History(_)) {
|
||||
return Err(EthApiError::InvalidBlockRange)
|
||||
}
|
||||
|
||||
let (account_proof, storage_hash, stg_proofs) = state.proof(address, &keys)?;
|
||||
|
||||
let storage_proof = keys
|
||||
.into_iter()
|
||||
.zip(stg_proofs)
|
||||
.map(|(key, proof)| {
|
||||
state.storage(address, key).map(|op| StorageProof {
|
||||
key: U256::from_be_bytes(*key.as_fixed_bytes()),
|
||||
value: op.unwrap_or_default(),
|
||||
proof,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let mut proof = EIP1186AccountProofResponse {
|
||||
address,
|
||||
code_hash: KECCAK_EMPTY,
|
||||
account_proof,
|
||||
storage_hash,
|
||||
storage_proof,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(account) = state.basic_account(address)? {
|
||||
proof.balance = account.balance;
|
||||
proof.nonce = account.nonce.into();
|
||||
proof.code_hash = account.get_bytecode_hash();
|
||||
}
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ description = "Staged syncing primitives used in reth."
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = [
|
||||
# Used for diagrams in docs
|
||||
"aquamarine"
|
||||
"aquamarine",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
@@ -46,7 +46,7 @@ proptest = { version = "1.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# reth
|
||||
reth-primitives = { path = "../primitives", features = ["arbitrary"]}
|
||||
reth-primitives = { path = "../primitives", features = ["arbitrary"] }
|
||||
reth-db = { path = "../storage/db", features = ["test-utils", "mdbx"] }
|
||||
reth-interfaces = { path = "../interfaces", features = ["test-utils"] }
|
||||
reth-downloaders = { path = "../net/downloaders" }
|
||||
@@ -60,7 +60,11 @@ rand = "0.8.5"
|
||||
paste = "1.0"
|
||||
|
||||
# Stage benchmarks
|
||||
pprof = { version = "0.11", features = ["flamegraph", "frame-pointer", "criterion"] }
|
||||
pprof = { version = "0.11", features = [
|
||||
"flamegraph",
|
||||
"frame-pointer",
|
||||
"criterion",
|
||||
] }
|
||||
criterion = { version = "0.4.0", features = ["async_futures"] }
|
||||
proptest = { version = "1.0" }
|
||||
arbitrary = { version = "1.1.7", features = ["derive"] }
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, StageId, UnwindInput, Unwi
|
||||
use reth_db::{database::Database, tables, transaction::DbTx};
|
||||
use reth_interfaces::consensus;
|
||||
use reth_provider::{trie::DBTrieLoader, Transaction};
|
||||
use std::fmt::Debug;
|
||||
use std::{fmt::Debug, ops::DerefMut};
|
||||
use tracing::*;
|
||||
|
||||
/// The [`StageId`] of the merkle hashing execution stage.
|
||||
@@ -108,14 +108,14 @@ impl<DB: Database> Stage<DB> for MerkleStage {
|
||||
} else if to_transition - from_transition > threshold || stage_progress == 0 {
|
||||
debug!(target: "sync::stages::merkle::exec", current = ?stage_progress, target = ?previous_stage_progress, "Rebuilding trie");
|
||||
// if there are more blocks than threshold it is faster to rebuild the trie
|
||||
DBTrieLoader::<DB>::new(tx)
|
||||
DBTrieLoader::new(tx.deref_mut())
|
||||
.calculate_root()
|
||||
.map_err(|e| StageError::Fatal(Box::new(e)))?
|
||||
} else {
|
||||
debug!(target: "sync::stages::merkle::exec", current = ?stage_progress, target = ?previous_stage_progress, "Updating trie");
|
||||
// Iterate over changeset (similar to Hashing stages) and take new values
|
||||
let current_root = tx.get_header(stage_progress)?.state_root;
|
||||
DBTrieLoader::<DB>::new(tx)
|
||||
DBTrieLoader::new(tx.deref_mut())
|
||||
.update_root(current_root, from_transition..to_transition)
|
||||
.map_err(|e| StageError::Fatal(Box::new(e)))?
|
||||
};
|
||||
@@ -160,7 +160,7 @@ impl<DB: Database> Stage<DB> for MerkleStage {
|
||||
let from_transition = tx.get_block_transition(input.unwind_to)?;
|
||||
let to_transition = tx.get_block_transition(input.stage_progress)?;
|
||||
|
||||
let block_root = DBTrieLoader::<DB>::new(tx)
|
||||
let block_root = DBTrieLoader::new(tx.deref_mut())
|
||||
.update_root(current_root, from_transition..to_transition)
|
||||
.map_err(|e| StageError::Fatal(Box::new(e)))?;
|
||||
|
||||
@@ -191,6 +191,7 @@ mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
use reth_db::{
|
||||
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW},
|
||||
database::DatabaseGAT,
|
||||
mdbx::{Env, WriteMap},
|
||||
tables,
|
||||
transaction::{DbTx, DbTxMut},
|
||||
@@ -199,7 +200,7 @@ mod tests {
|
||||
random_block, random_block_range, random_contract_account_range, random_transition_range,
|
||||
};
|
||||
use reth_primitives::{keccak256, Account, Address, SealedBlock, StorageEntry, H256, U256};
|
||||
use std::collections::BTreeMap;
|
||||
use std::{collections::BTreeMap, ops::Deref};
|
||||
|
||||
stage_test_suite_ext!(MerkleTestRunner, merkle);
|
||||
|
||||
@@ -262,8 +263,8 @@ mod tests {
|
||||
|
||||
fn create_trie_loader<'tx, 'db>(
|
||||
tx: &'tx Transaction<'db, Env<WriteMap>>,
|
||||
) -> DBTrieLoader<'tx, 'db, Env<WriteMap>> {
|
||||
DBTrieLoader::<Env<WriteMap>>::new(tx)
|
||||
) -> DBTrieLoader<'tx, <Env<WriteMap> as DatabaseGAT<'db>>::TXMut> {
|
||||
DBTrieLoader::new(tx.deref())
|
||||
}
|
||||
|
||||
struct MerkleTestRunner {
|
||||
|
||||
@@ -13,8 +13,8 @@ reth-primitives = { path = "../../primitives" }
|
||||
reth-interfaces = { path = "../../interfaces" }
|
||||
reth-revm-primitives = { path = "../../revm/revm-primitives" }
|
||||
reth-db = { path = "../db" }
|
||||
reth-tracing = {path = "../../tracing"}
|
||||
reth-rlp = {path = "../../rlp"}
|
||||
reth-tracing = { path = "../../tracing" }
|
||||
reth-rlp = { path = "../../rlp" }
|
||||
|
||||
revm-primitives = "1.0.0"
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ pub use providers::{
|
||||
LatestStateProviderRef, ShareableDatabase,
|
||||
};
|
||||
|
||||
/// Merkle trie
|
||||
/// Helper type for loading Merkle Patricia Trees from the database
|
||||
pub mod trie;
|
||||
|
||||
/// Execution result
|
||||
|
||||
@@ -10,7 +10,7 @@ use reth_db::{
|
||||
};
|
||||
use reth_interfaces::Result;
|
||||
use reth_primitives::{
|
||||
Account, Address, Bytecode, StorageKey, StorageValue, TransitionId, H256, U256,
|
||||
Account, Address, Bytecode, Bytes, StorageKey, StorageValue, TransitionId, H256, U256,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
@@ -119,6 +119,15 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b,
|
||||
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>> {
|
||||
self.tx.get::<tables::Bytecodes>(code_hash).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Get account and storage proofs.
|
||||
fn proof(
|
||||
&self,
|
||||
_address: Address,
|
||||
_keys: &[H256],
|
||||
) -> Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
|
||||
todo!("this should retrieve past state info and generate proof")
|
||||
}
|
||||
}
|
||||
|
||||
/// State provider for a given transition
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
use crate::{
|
||||
providers::state::macros::delegate_provider_impls, AccountProvider, BlockHashProvider,
|
||||
StateProvider,
|
||||
providers::state::macros::delegate_provider_impls, trie::DBTrieLoader, AccountProvider,
|
||||
BlockHashProvider, StateProvider,
|
||||
};
|
||||
use reth_db::{
|
||||
cursor::{DbCursorRO, DbDupCursorRO},
|
||||
tables,
|
||||
transaction::DbTx,
|
||||
};
|
||||
use reth_interfaces::{provider::ProviderError, Result};
|
||||
use reth_primitives::{
|
||||
keccak256, Account, Address, Bytecode, Bytes, StorageKey, StorageValue, H256, KECCAK_EMPTY,
|
||||
U256,
|
||||
};
|
||||
use reth_db::{cursor::DbDupCursorRO, tables, transaction::DbTx};
|
||||
use reth_interfaces::Result;
|
||||
use reth_primitives::{Account, Address, Bytecode, StorageKey, StorageValue, H256, U256};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// State provider over latest state that takes tx reference.
|
||||
@@ -52,6 +59,42 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for LatestStateProviderRef<'a, 'b, TX>
|
||||
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>> {
|
||||
self.db.get::<tables::Bytecodes>(code_hash).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn proof(
|
||||
&self,
|
||||
address: Address,
|
||||
keys: &[H256],
|
||||
) -> Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
|
||||
let hashed_address = keccak256(address);
|
||||
let loader = DBTrieLoader::new(self.db);
|
||||
let root = self
|
||||
.db
|
||||
.cursor_read::<tables::Headers>()?
|
||||
.last()?
|
||||
.ok_or(ProviderError::Header { number: 0 })?
|
||||
.1
|
||||
.state_root;
|
||||
|
||||
let (account_proof, storage_root) = loader
|
||||
.generate_acount_proof(self.db, root, hashed_address)
|
||||
.map_err(|_| ProviderError::StateTree)?;
|
||||
let account_proof = account_proof.into_iter().map(Bytes::from).collect();
|
||||
|
||||
let storage_proof = if storage_root == KECCAK_EMPTY {
|
||||
// if there isn't storage, we return empty storage proofs
|
||||
(0..keys.len()).map(|_| Vec::new()).collect()
|
||||
} else {
|
||||
let hashed_keys: Vec<H256> = keys.iter().map(keccak256).collect();
|
||||
loader
|
||||
.generate_storage_proofs(self.db, storage_root, hashed_address, &hashed_keys)
|
||||
.map_err(|_| ProviderError::StateTree)?
|
||||
.into_iter()
|
||||
.map(|v| v.into_iter().map(Bytes::from).collect())
|
||||
.collect()
|
||||
};
|
||||
|
||||
Ok((account_proof, storage_root, storage_proof))
|
||||
}
|
||||
}
|
||||
|
||||
/// State provider for the latest state.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
///
|
||||
/// Used to implement provider traits.
|
||||
macro_rules! delegate_impls_to_as_ref {
|
||||
(for $target:ty => $($trait:ident $(where [$($generics:tt)*])? { $(fn $func:ident(&self, $($arg:ident: $argty:path),*) -> $ret:path;)* })* ) => {
|
||||
(for $target:ty => $($trait:ident $(where [$($generics:tt)*])? { $(fn $func:ident(&self, $($arg:ident: $argty:ty),*) -> $ret:path;)* })* ) => {
|
||||
|
||||
$(
|
||||
impl<'a, $($($generics)*)?> $trait for $target {
|
||||
@@ -38,6 +38,7 @@ macro_rules! delegate_provider_impls {
|
||||
}
|
||||
StateProvider $(where [$($generics)*])?{
|
||||
fn storage(&self, account: reth_primitives::Address, storage_key: reth_primitives::StorageKey) -> reth_interfaces::Result<Option<reth_primitives::StorageValue>>;
|
||||
fn proof(&self, address: reth_primitives::Address, keys: &[reth_primitives::H256]) -> reth_interfaces::Result<(Vec<reth_primitives::Bytes>, reth_primitives::H256, Vec<Vec<reth_primitives::Bytes>>)>;
|
||||
fn bytecode_by_hash(&self, code_hash: reth_primitives::H256) -> reth_interfaces::Result<Option<reth_primitives::Bytecode>>;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -252,6 +252,14 @@ impl StateProvider for MockEthProvider {
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn proof(
|
||||
&self,
|
||||
_address: Address,
|
||||
_keys: &[H256],
|
||||
) -> Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl EvmEnvProvider for MockEthProvider {
|
||||
|
||||
@@ -4,8 +4,9 @@ use crate::{
|
||||
};
|
||||
use reth_interfaces::Result;
|
||||
use reth_primitives::{
|
||||
Account, Address, Block, BlockHash, BlockId, BlockNumber, Bytecode, ChainInfo, Header, Receipt,
|
||||
StorageKey, StorageValue, TransactionSigned, TxHash, TxNumber, H256, U256,
|
||||
Account, Address, Block, BlockHash, BlockId, BlockNumber, Bytecode, Bytes, ChainInfo, Header,
|
||||
Receipt, StorageKey, StorageValue, TransactionSigned, TxHash, TxNumber, H256, KECCAK_EMPTY,
|
||||
U256,
|
||||
};
|
||||
use revm_primitives::{BlockEnv, CfgEnv};
|
||||
use std::ops::RangeBounds;
|
||||
@@ -113,6 +114,14 @@ impl StateProvider for NoopProvider {
|
||||
fn bytecode_by_hash(&self, _code_hash: H256) -> Result<Option<Bytecode>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn proof(
|
||||
&self,
|
||||
_address: Address,
|
||||
_keys: &[H256],
|
||||
) -> Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
|
||||
Ok((vec![], KECCAK_EMPTY, vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
impl EvmEnvProvider for NoopProvider {
|
||||
|
||||
@@ -3,18 +3,23 @@ use crate::BlockHashProvider;
|
||||
use auto_impl::auto_impl;
|
||||
use reth_interfaces::Result;
|
||||
use reth_primitives::{
|
||||
Address, BlockHash, BlockNumber, Bytecode, StorageKey, StorageValue, H256, KECCAK_EMPTY, U256,
|
||||
Address, BlockHash, BlockNumber, Bytecode, Bytes, StorageKey, StorageValue, H256, KECCAK_EMPTY,
|
||||
U256,
|
||||
};
|
||||
|
||||
/// An abstraction for a type that provides state data.
|
||||
#[auto_impl(&, Box)]
|
||||
pub trait StateProvider: BlockHashProvider + AccountProvider + Send + Sync {
|
||||
/// Get storage.
|
||||
/// Get storage of given account.
|
||||
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>>;
|
||||
|
||||
/// Get account code by its hash
|
||||
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>>;
|
||||
|
||||
/// Get account and storage proofs.
|
||||
fn proof(&self, address: Address, keys: &[H256])
|
||||
-> Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)>;
|
||||
|
||||
/// Get account code by its address.
|
||||
///
|
||||
/// Returns `None` if the account doesn't exist or account is not a contract
|
||||
|
||||
@@ -308,7 +308,7 @@ where
|
||||
// merkle tree
|
||||
{
|
||||
let current_root = self.get_header(parent_block_number)?.state_root;
|
||||
let loader = DBTrieLoader::<DB>::new(self);
|
||||
let loader = DBTrieLoader::new(self.deref_mut());
|
||||
let root = loader.update_root(current_root, from..to)?;
|
||||
if root != block.state_root {
|
||||
return Err(TransactionError::StateTrieRootMismatch {
|
||||
|
||||
@@ -2,7 +2,6 @@ use cita_trie::{PatriciaTrie, Trie};
|
||||
use hasher::HasherKeccak;
|
||||
use reth_db::{
|
||||
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO},
|
||||
database::{Database, DatabaseGAT},
|
||||
models::{AccountBeforeTx, TransitionIdAddress},
|
||||
tables,
|
||||
transaction::{DbTx, DbTxMut},
|
||||
@@ -18,6 +17,7 @@ use reth_rlp::{
|
||||
use reth_tracing::tracing::*;
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
marker::PhantomData,
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -26,24 +26,28 @@ use std::{
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum TrieError {
|
||||
/// Error returned by the underlying implementation.
|
||||
#[error("Some error occurred: {0}")]
|
||||
InternalError(#[from] cita_trie::TrieError),
|
||||
/// The database doesn't contain the root of the trie.
|
||||
#[error("The root node wasn't found in the DB")]
|
||||
MissingRoot(H256),
|
||||
/// Error returned by the database.
|
||||
#[error("{0:?}")]
|
||||
DatabaseError(#[from] reth_db::Error),
|
||||
/// Error when encoding/decoding a value.
|
||||
#[error("{0:?}")]
|
||||
DecodeError(#[from] DecodeError),
|
||||
}
|
||||
|
||||
/// Database wrapper implementing HashDB trait.
|
||||
pub struct HashDatabase<'tx, 'db, DB: Database> {
|
||||
tx: &'tx <DB as DatabaseGAT<'db>>::TXMut,
|
||||
/// Database wrapper implementing HashDB trait, with a read-write transaction.
|
||||
pub struct HashDatabaseMut<'tx, TX> {
|
||||
tx: &'tx TX,
|
||||
}
|
||||
|
||||
impl<'tx, 'db, DB> cita_trie::DB for HashDatabase<'tx, 'db, DB>
|
||||
impl<'tx, 'db, TX> cita_trie::DB for HashDatabaseMut<'tx, TX>
|
||||
where
|
||||
DB: Database,
|
||||
TX: DbTxMut<'db> + DbTx<'db> + Send + Sync,
|
||||
{
|
||||
type Error = TrieError;
|
||||
|
||||
@@ -87,9 +91,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tx, 'db, DB: Database> HashDatabase<'tx, 'db, DB> {
|
||||
impl<'tx, 'db, TX> HashDatabaseMut<'tx, TX>
|
||||
where
|
||||
TX: DbTxMut<'db> + DbTx<'db> + Send + Sync,
|
||||
{
|
||||
/// Instantiates a new Database for the accounts trie, with an empty root
|
||||
pub fn new(tx: &'tx <DB as DatabaseGAT<'db>>::TXMut) -> Result<Self, TrieError> {
|
||||
pub fn new(tx: &'tx TX) -> Result<Self, TrieError> {
|
||||
let root = EMPTY_ROOT;
|
||||
if tx.get::<tables::AccountsTrie>(root)?.is_none() {
|
||||
tx.put::<tables::AccountsTrie>(root, [EMPTY_STRING_CODE].to_vec())?;
|
||||
@@ -98,10 +105,7 @@ impl<'tx, 'db, DB: Database> HashDatabase<'tx, 'db, DB> {
|
||||
}
|
||||
|
||||
/// Instantiates a new Database for the accounts trie, with an existing root
|
||||
pub fn from_root(
|
||||
tx: &'tx <DB as DatabaseGAT<'db>>::TXMut,
|
||||
root: H256,
|
||||
) -> Result<Self, TrieError> {
|
||||
pub fn from_root(tx: &'tx TX, root: H256) -> Result<Self, TrieError> {
|
||||
if root == EMPTY_ROOT {
|
||||
return Self::new(tx)
|
||||
}
|
||||
@@ -110,15 +114,15 @@ impl<'tx, 'db, DB: Database> HashDatabase<'tx, 'db, DB> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Database wrapper implementing HashDB trait.
|
||||
pub struct DupHashDatabase<'tx, 'db, DB: Database> {
|
||||
tx: &'tx <DB as DatabaseGAT<'db>>::TXMut,
|
||||
/// Database wrapper implementing HashDB trait, with a read-write transaction.
|
||||
pub struct DupHashDatabaseMut<'tx, TX> {
|
||||
tx: &'tx TX,
|
||||
key: H256,
|
||||
}
|
||||
|
||||
impl<'tx, 'db, DB> cita_trie::DB for DupHashDatabase<'tx, 'db, DB>
|
||||
impl<'tx, 'db, TX> cita_trie::DB for DupHashDatabaseMut<'tx, TX>
|
||||
where
|
||||
DB: Database,
|
||||
TX: DbTxMut<'db> + DbTx<'db> + Send + Sync,
|
||||
{
|
||||
type Error = TrieError;
|
||||
|
||||
@@ -172,9 +176,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tx, 'db, DB: Database> DupHashDatabase<'tx, 'db, DB> {
|
||||
impl<'tx, 'db, TX> DupHashDatabaseMut<'tx, TX>
|
||||
where
|
||||
TX: DbTxMut<'db> + DbTx<'db> + Send + Sync,
|
||||
{
|
||||
/// Instantiates a new Database for the storage trie, with an empty root
|
||||
pub fn new(tx: &'tx <DB as DatabaseGAT<'db>>::TXMut, key: H256) -> Result<Self, TrieError> {
|
||||
pub fn new(tx: &'tx TX, key: H256) -> Result<Self, TrieError> {
|
||||
let root = EMPTY_ROOT;
|
||||
let mut cursor = tx.cursor_dup_write::<tables::StoragesTrie>()?;
|
||||
if cursor.seek_by_key_subkey(key, root)?.filter(|entry| entry.hash == root).is_none() {
|
||||
@@ -187,11 +194,7 @@ impl<'tx, 'db, DB: Database> DupHashDatabase<'tx, 'db, DB> {
|
||||
}
|
||||
|
||||
/// Instantiates a new Database for the storage trie, with an existing root
|
||||
pub fn from_root(
|
||||
tx: &'tx <DB as DatabaseGAT<'db>>::TXMut,
|
||||
key: H256,
|
||||
root: H256,
|
||||
) -> Result<Self, TrieError> {
|
||||
pub fn from_root(tx: &'tx TX, key: H256, root: H256) -> Result<Self, TrieError> {
|
||||
if root == EMPTY_ROOT {
|
||||
return Self::new(tx, key)
|
||||
}
|
||||
@@ -203,6 +206,95 @@ impl<'tx, 'db, DB: Database> DupHashDatabase<'tx, 'db, DB> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Database wrapper implementing HashDB trait, with a read-only transaction.
|
||||
struct HashDatabase<'tx, 'itx, TX: DbTx<'itx>> {
|
||||
tx: &'tx TX,
|
||||
_p: PhantomData<&'itx ()>, // to suppress "unused" lifetime 'itx
|
||||
}
|
||||
|
||||
impl<'tx, 'itx, TX> cita_trie::DB for HashDatabase<'tx, 'itx, TX>
|
||||
where
|
||||
TX: DbTx<'itx>,
|
||||
{
|
||||
type Error = TrieError;
|
||||
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||
Ok(self.tx.get::<tables::AccountsTrie>(H256::from_slice(key))?)
|
||||
}
|
||||
|
||||
fn contains(&self, key: &[u8]) -> Result<bool, Self::Error> {
|
||||
Ok(<Self as cita_trie::DB>::get(self, key)?.is_some())
|
||||
}
|
||||
|
||||
fn insert(&self, _key: Vec<u8>, _value: Vec<u8>) -> Result<(), Self::Error> {
|
||||
// this could be avoided if cita_trie::DB was split into two traits
|
||||
// with read and write operations respectively
|
||||
unimplemented!("insert isn't valid for read-only transaction");
|
||||
}
|
||||
|
||||
fn remove(&self, _key: &[u8]) -> Result<(), Self::Error> {
|
||||
unimplemented!("remove isn't valid for read-only transaction");
|
||||
}
|
||||
|
||||
fn flush(&self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tx, 'itx, TX: DbTx<'itx>> HashDatabase<'tx, 'itx, TX> {
|
||||
/// Instantiates a new Database for the accounts trie, with an existing root
|
||||
fn from_root(tx: &'tx TX, root: H256) -> Result<Self, TrieError> {
|
||||
tx.get::<tables::AccountsTrie>(root)?.ok_or(TrieError::MissingRoot(root))?;
|
||||
Ok(Self { tx, _p: Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Database wrapper implementing HashDB trait, with a read-only transaction.
|
||||
struct DupHashDatabase<'tx, 'itx, TX: DbTx<'itx>> {
|
||||
tx: &'tx TX,
|
||||
key: H256,
|
||||
_p: PhantomData<&'itx ()>, // to suppress "unused" lifetime 'itx
|
||||
}
|
||||
|
||||
impl<'tx, 'itx, TX> cita_trie::DB for DupHashDatabase<'tx, 'itx, TX>
|
||||
where
|
||||
TX: DbTx<'itx>,
|
||||
{
|
||||
type Error = TrieError;
|
||||
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
|
||||
let mut cursor = self.tx.cursor_dup_read::<tables::StoragesTrie>()?;
|
||||
Ok(cursor.seek_by_key_subkey(self.key, H256::from_slice(key))?.map(|entry| entry.node))
|
||||
}
|
||||
|
||||
fn contains(&self, key: &[u8]) -> Result<bool, Self::Error> {
|
||||
Ok(<Self as cita_trie::DB>::get(self, key)?.is_some())
|
||||
}
|
||||
|
||||
fn insert(&self, _key: Vec<u8>, _value: Vec<u8>) -> Result<(), Self::Error> {
|
||||
// Caching and bulk inserting shouldn't be needed, as the data is ordered
|
||||
unimplemented!("insert isn't valid for read-only transaction");
|
||||
}
|
||||
|
||||
fn remove(&self, _key: &[u8]) -> Result<(), Self::Error> {
|
||||
unimplemented!("remove isn't valid for read-only transaction");
|
||||
}
|
||||
|
||||
fn flush(&self) -> Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tx, 'itx, TX: DbTx<'itx>> DupHashDatabase<'tx, 'itx, TX> {
|
||||
/// Instantiates a new Database for the storage trie, with an existing root
|
||||
fn from_root(tx: &'tx TX, key: H256, root: H256) -> Result<Self, TrieError> {
|
||||
tx.cursor_dup_read::<tables::StoragesTrie>()?
|
||||
.seek_by_key_subkey(key, root)?
|
||||
.ok_or(TrieError::MissingRoot(root))?;
|
||||
Ok(Self { tx, key, _p: Default::default() })
|
||||
}
|
||||
}
|
||||
|
||||
/// An Ethereum account, for RLP encoding traits deriving.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
|
||||
pub struct EthAccount {
|
||||
@@ -240,18 +332,28 @@ impl EthAccount {
|
||||
}
|
||||
}
|
||||
|
||||
/// A merkle proof of existence (or nonexistence) of a leaf value. Consists
|
||||
/// of a the encoded nodes in the path from the root of the tree to the leaf.
|
||||
pub type MerkleProof = Vec<Vec<u8>>;
|
||||
|
||||
/// Struct for calculating the root of a merkle patricia tree,
|
||||
/// while populating the database with intermediate hashes.
|
||||
pub struct DBTrieLoader<'tx, 'db, DB: Database> {
|
||||
tx: &'tx <DB as DatabaseGAT<'db>>::TXMut,
|
||||
pub struct DBTrieLoader<'tx, TX> {
|
||||
tx: &'tx TX,
|
||||
}
|
||||
|
||||
impl<'tx, 'db, DB: Database> DBTrieLoader<'tx, 'db, DB> {
|
||||
impl<'tx, TX> DBTrieLoader<'tx, TX> {
|
||||
/// Create new instance of trie loader.
|
||||
pub fn new(tx: &'tx <DB as DatabaseGAT<'db>>::TXMut) -> Self {
|
||||
pub fn new(tx: &'tx TX) -> Self {
|
||||
Self { tx }
|
||||
}
|
||||
}
|
||||
|
||||
// Read-write impls
|
||||
impl<'tx, 'db, TX> DBTrieLoader<'tx, TX>
|
||||
where
|
||||
TX: DbTxMut<'db> + DbTx<'db> + Send + Sync,
|
||||
{
|
||||
/// Calculates the root of the state trie, saving intermediate hashes in the database.
|
||||
pub fn calculate_root(&self) -> Result<H256, TrieError> {
|
||||
self.tx.clear::<tables::AccountsTrie>()?;
|
||||
@@ -260,7 +362,7 @@ impl<'tx, 'db, DB: Database> DBTrieLoader<'tx, 'db, DB> {
|
||||
let mut accounts_cursor = self.tx.cursor_read::<tables::HashedAccount>()?;
|
||||
let mut walker = accounts_cursor.walk(None)?;
|
||||
|
||||
let db = Arc::new(HashDatabase::<DB>::new(self.tx)?);
|
||||
let db = Arc::new(HashDatabaseMut::new(self.tx)?);
|
||||
|
||||
let hasher = Arc::new(HasherKeccak::new());
|
||||
|
||||
@@ -281,7 +383,7 @@ impl<'tx, 'db, DB: Database> DBTrieLoader<'tx, 'db, DB> {
|
||||
|
||||
/// Calculate the accounts storage root.
|
||||
pub fn calculate_storage_root(&self, address: H256) -> Result<H256, TrieError> {
|
||||
let db = Arc::new(DupHashDatabase::<DB>::new(self.tx, address)?);
|
||||
let db = Arc::new(DupHashDatabaseMut::new(self.tx, address)?);
|
||||
|
||||
let hasher = Arc::new(HasherKeccak::new());
|
||||
|
||||
@@ -319,7 +421,7 @@ impl<'tx, 'db, DB: Database> DBTrieLoader<'tx, 'db, DB> {
|
||||
|
||||
let changed_accounts = self.gather_changes(tid_range)?;
|
||||
|
||||
let db = Arc::new(HashDatabase::<DB>::from_root(self.tx, root)?);
|
||||
let db = Arc::new(HashDatabaseMut::from_root(self.tx, root)?);
|
||||
|
||||
let hasher = Arc::new(HasherKeccak::new());
|
||||
|
||||
@@ -362,7 +464,7 @@ impl<'tx, 'db, DB: Database> DBTrieLoader<'tx, 'db, DB> {
|
||||
address: H256,
|
||||
changed_storages: BTreeSet<H256>,
|
||||
) -> Result<H256, TrieError> {
|
||||
let db = Arc::new(DupHashDatabase::<DB>::from_root(self.tx, address, root)?);
|
||||
let db = Arc::new(DupHashDatabaseMut::from_root(self.tx, address, root)?);
|
||||
|
||||
let hasher = Arc::new(HasherKeccak::new());
|
||||
|
||||
@@ -437,6 +539,52 @@ impl<'tx, 'db, DB: Database> DBTrieLoader<'tx, 'db, DB> {
|
||||
}
|
||||
}
|
||||
|
||||
// Read-only impls
|
||||
impl<'tx, 'db, TX> DBTrieLoader<'tx, TX>
|
||||
where
|
||||
TX: DbTx<'db> + Send + Sync,
|
||||
{
|
||||
/// Returns a Merkle proof of the given account, plus its storage root hash.
|
||||
pub fn generate_acount_proof<'itx>(
|
||||
&self,
|
||||
tx: &'tx impl DbTx<'itx>,
|
||||
root: H256,
|
||||
address: H256,
|
||||
) -> Result<(MerkleProof, H256), TrieError> {
|
||||
let db = Arc::new(HashDatabase::from_root(tx, root)?);
|
||||
let hasher = Arc::new(HasherKeccak::new());
|
||||
|
||||
let trie = PatriciaTrie::from(Arc::clone(&db), Arc::clone(&hasher), root.as_bytes())?;
|
||||
let proof = trie.get_proof(address.as_bytes())?;
|
||||
|
||||
let Some(account) = trie.get(address.as_slice())? else { return Ok((proof, KECCAK_EMPTY)) };
|
||||
|
||||
let storage_root = EthAccount::decode(&mut account.as_slice())?.storage_root;
|
||||
|
||||
Ok((proof, storage_root))
|
||||
}
|
||||
|
||||
/// Returns a Merkle proof of the given storage keys, starting at the given root hash.
|
||||
pub fn generate_storage_proofs<'itx>(
|
||||
&self,
|
||||
tx: &'tx impl DbTx<'itx>,
|
||||
storage_root: H256,
|
||||
address: H256,
|
||||
keys: &[H256],
|
||||
) -> Result<Vec<MerkleProof>, TrieError> {
|
||||
let db = Arc::new(DupHashDatabase::from_root(tx, address, storage_root)?);
|
||||
let hasher = Arc::new(HasherKeccak::new());
|
||||
|
||||
let trie =
|
||||
PatriciaTrie::from(Arc::clone(&db), Arc::clone(&hasher), storage_root.as_bytes())?;
|
||||
|
||||
let proof =
|
||||
keys.iter().map(|key| trie.get_proof(key.as_bytes())).collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Transaction;
|
||||
@@ -445,6 +593,7 @@ mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
use proptest::{prelude::ProptestConfig, proptest};
|
||||
use reth_db::{
|
||||
database::{Database, DatabaseGAT},
|
||||
mdbx::{test_utils::create_test_rw_db, Env, WriteMap},
|
||||
tables,
|
||||
transaction::DbTxMut,
|
||||
@@ -453,14 +602,43 @@ mod tests {
|
||||
hex_literal::hex,
|
||||
keccak256,
|
||||
proofs::{genesis_state_root, KeccakHasher, EMPTY_ROOT},
|
||||
Address, ChainSpec, MAINNET,
|
||||
Address, Bytes, ChainSpec, Genesis, MAINNET,
|
||||
};
|
||||
use std::{collections::HashMap, ops::Deref, str::FromStr};
|
||||
use triehash::sec_trie_root;
|
||||
|
||||
fn load_mainnet_genesis_root<DB: Database>(tx: &mut Transaction<'_, DB>) -> Genesis {
|
||||
let ChainSpec { genesis, .. } = MAINNET.clone();
|
||||
|
||||
// Insert account state
|
||||
for (address, account) in &genesis.alloc {
|
||||
tx.put::<tables::PlainAccountState>(
|
||||
*address,
|
||||
Account {
|
||||
nonce: account.nonce.unwrap_or_default(),
|
||||
balance: account.balance,
|
||||
bytecode_hash: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
tx.put::<tables::HashedAccount>(
|
||||
keccak256(address),
|
||||
Account {
|
||||
nonce: account.nonce.unwrap_or_default(),
|
||||
balance: account.balance,
|
||||
bytecode_hash: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
tx.commit().unwrap();
|
||||
|
||||
genesis
|
||||
}
|
||||
|
||||
fn create_test_loader<'tx, 'db>(
|
||||
tx: &'tx Transaction<'db, Env<WriteMap>>,
|
||||
) -> DBTrieLoader<'tx, 'db, Arc<Env<WriteMap>>> {
|
||||
) -> DBTrieLoader<'tx, <Arc<Env<WriteMap>> as DatabaseGAT<'db>>::TXMut> {
|
||||
DBTrieLoader::new(tx.deref())
|
||||
}
|
||||
|
||||
@@ -596,21 +774,8 @@ mod tests {
|
||||
fn verify_genesis() {
|
||||
let db = create_test_rw_db();
|
||||
let mut tx = Transaction::new(db.as_ref()).unwrap();
|
||||
let ChainSpec { genesis, .. } = MAINNET.clone();
|
||||
|
||||
// Insert account state
|
||||
for (address, account) in &genesis.alloc {
|
||||
tx.put::<tables::HashedAccount>(
|
||||
keccak256(address),
|
||||
Account {
|
||||
nonce: account.nonce.unwrap_or_default(),
|
||||
balance: account.balance,
|
||||
bytecode_hash: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
tx.commit().unwrap();
|
||||
let genesis = load_mainnet_genesis_root(&mut tx);
|
||||
|
||||
let state_root = genesis_state_root(&genesis.alloc);
|
||||
|
||||
@@ -708,4 +873,125 @@ mod tests {
|
||||
test_with_accounts(accounts);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_proof() {
|
||||
let db = create_test_rw_db();
|
||||
let mut tx = Transaction::new(db.as_ref()).unwrap();
|
||||
|
||||
load_mainnet_genesis_root(&mut tx);
|
||||
|
||||
let root = {
|
||||
let trie = create_test_loader(&tx);
|
||||
trie.calculate_root().expect("should be able to load trie")
|
||||
};
|
||||
|
||||
tx.commit().unwrap();
|
||||
|
||||
let address = Address::from(hex!("000d836201318ec6899a67540690382780743280"));
|
||||
|
||||
let trie = create_test_loader(&tx);
|
||||
let (proof, storage_root) = trie
|
||||
.generate_acount_proof(&tx.inner().tx().unwrap(), root, keccak256(address))
|
||||
.expect("failed to generate proof");
|
||||
|
||||
// values extracted from geth via rpc:
|
||||
// {
|
||||
// "method": "eth_getProof",
|
||||
// "params": ["0x000d836201318ec6899a67540690382780743280", [], "0x0"]
|
||||
// }
|
||||
let expected = [
|
||||
hex!("f90211a090dcaf88c40c7bbc95a912cbdde67c175767b31173df9ee4b0d733bfdd511c43a0babe369f6b12092f49181ae04ca173fb68d1a5456f18d20fa32cba73954052bda0473ecf8a7e36a829e75039a3b055e51b8332cbf03324ab4af2066bbd6fbf0021a0bbda34753d7aa6c38e603f360244e8f59611921d9e1f128372fec0d586d4f9e0a04e44caecff45c9891f74f6a2156735886eedf6f1a733628ebc802ec79d844648a0a5f3f2f7542148c973977c8a1e154c4300fec92f755f7846f1b734d3ab1d90e7a0e823850f50bf72baae9d1733a36a444ab65d0a6faaba404f0583ce0ca4dad92da0f7a00cbe7d4b30b11faea3ae61b7f1f2b315b61d9f6bd68bfe587ad0eeceb721a07117ef9fc932f1a88e908eaead8565c19b5645dc9e5b1b6e841c5edbdfd71681a069eb2de283f32c11f859d7bcf93da23990d3e662935ed4d6b39ce3673ec84472a0203d26456312bbc4da5cd293b75b840fc5045e493d6f904d180823ec22bfed8ea09287b5c21f2254af4e64fca76acc5cd87399c7f1ede818db4326c98ce2dc2208a06fc2d754e304c48ce6a517753c62b1a9c1d5925b89707486d7fc08919e0a94eca07b1c54f15e299bd58bdfef9741538c7828b5d7d11a489f9c20d052b3471df475a051f9dd3739a927c89e357580a4c97b40234aa01ed3d5e0390dc982a7975880a0a089d613f26159af43616fd9455bb461f4869bfede26f2130835ed067a8b967bfb80").as_slice(),
|
||||
hex!("f90211a0dae48f5b47930c28bb116fbd55e52cd47242c71bf55373b55eb2805ee2e4a929a00f1f37f337ec800e2e5974e2e7355f10f1a4832b39b846d916c3597a460e0676a0da8f627bb8fbeead17b318e0a8e4f528db310f591bb6ab2deda4a9f7ca902ab5a0971c662648d58295d0d0aa4b8055588da0037619951217c22052802549d94a2fa0ccc701efe4b3413fd6a61a6c9f40e955af774649a8d9fd212d046a5a39ddbb67a0d607cdb32e2bd635ee7f2f9e07bc94ddbd09b10ec0901b66628e15667aec570ba05b89203dc940e6fa70ec19ad4e01d01849d3a5baa0a8f9c0525256ed490b159fa0b84227d48df68aecc772939a59afa9e1a4ab578f7b698bdb1289e29b6044668ea0fd1c992070b94ace57e48cbf6511a16aa770c645f9f5efba87bbe59d0a042913a0e16a7ccea6748ae90de92f8aef3b3dc248a557b9ac4e296934313f24f7fced5fa042373cf4a00630d94de90d0a23b8f38ced6b0f7cb818b8925fee8f0c2a28a25aa05f89d2161c1741ff428864f7889866484cef622de5023a46e795dfdec336319fa07597a017664526c8c795ce1da27b8b72455c49657113e0455552dbc068c5ba31a0d5be9089012fda2c585a1b961e988ea5efcd3a06988e150a8682091f694b37c5a0f7b0352e38c315b2d9a14d51baea4ddee1770974c806e209355233c3c89dce6ea049bf6e8df0acafd0eff86defeeb305568e44d52d2235cf340ae15c6034e2b24180").as_slice(),
|
||||
hex!("f901f1a0cf67e0f5d5f8d70e53a6278056a14ddca46846f5ef69c7bde6810d058d4a9eda80a06732ada65afd192197fe7ce57792a7f25d26978e64e954b7b84a1f7857ac279da05439f8d011683a6fc07efb90afca198fd7270c795c835c7c85d91402cda992eaa0449b93033b6152d289045fdb0bf3f44926f831566faa0e616b7be1abaad2cb2da031be6c3752bcd7afb99b1bb102baf200f8567c394d464315323a363697646616a0a40e3ed11d906749aa501279392ffde868bd35102db41364d9c601fd651f974aa0044bfa4fe8dd1a58e6c7144da79326e94d1331c0b00373f6ae7f3662f45534b7a098005e3e48db68cb1dc9b9f034ff74d2392028ddf718b0f2084133017da2c2e7a02a62bc40414ee95b02e202a9e89babbabd24bef0abc3fc6dcd3e9144ceb0b725a0239facd895bbf092830390a8676f34b35b29792ae561f196f86614e0448a5792a0a4080f88925daff6b4ce26d188428841bd65655d8e93509f2106020e76d41eefa04918987904be42a6894256ca60203283d1b89139cf21f09f5719c44b8cdbb8f7a06201fc3ef0827e594d953b5e3165520af4fceb719e11cc95fd8d3481519bfd8ca05d0e353d596bd725b09de49c01ede0f29023f0153d7b6d401556aeb525b2959ba0cd367d0679950e9c5f2aa4298fd4b081ade2ea429d71ff390c50f8520e16e30880").as_slice(),
|
||||
hex!("f87180808080808080a0dbee8b33c73b86df839f309f7ac92eee19836e08b39302ffa33921b3c6a09f66a06068b283d51aeeee682b8fb5458354315d0b91737441ede5e137c18b4775174a8080808080a0fe7779c7d58c2fda43eba0a6644043c86ebb9ceb4836f89e30831f23eb059ece8080").as_slice(),
|
||||
hex!("f8719f20b71c90b0d523dd5004cf206f325748da347685071b34812e21801f5270c4b84ff84d80890ad78ebc5ac6200000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").as_slice(),
|
||||
];
|
||||
|
||||
assert_eq!(storage_root, EMPTY_ROOT);
|
||||
|
||||
assert_eq!(proof.len(), 5);
|
||||
|
||||
for (node, expected) in proof.into_iter().zip(expected.into_iter()) {
|
||||
assert_eq!(Bytes::from(node.as_slice()), Bytes::from(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_storage_proofs() {
|
||||
let db = create_test_rw_db();
|
||||
let mut tx = Transaction::new(db.as_ref()).unwrap();
|
||||
|
||||
let address = Address::from_str("9fe4abd71ad081f091bd06dd1c16f7e92927561e").unwrap();
|
||||
let hashed_address = keccak256(address);
|
||||
|
||||
let storage = HashMap::from([
|
||||
(H256::zero(), U256::from(3)),
|
||||
(H256::from_low_u64_be(2), U256::from(1)),
|
||||
]);
|
||||
|
||||
let code = "el buen fla";
|
||||
let account = Account {
|
||||
nonce: 155,
|
||||
balance: U256::from(414241124u32),
|
||||
bytecode_hash: Some(keccak256(code)),
|
||||
};
|
||||
tx.put::<tables::HashedAccount>(hashed_address, account).unwrap();
|
||||
|
||||
for (k, v) in storage.clone() {
|
||||
tx.put::<tables::HashedStorage>(
|
||||
hashed_address,
|
||||
StorageEntry { key: keccak256(k), value: v },
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let root = {
|
||||
let trie = create_test_loader(&tx);
|
||||
trie.calculate_root().expect("should be able to load trie")
|
||||
};
|
||||
|
||||
tx.commit().unwrap();
|
||||
|
||||
let trie = create_test_loader(&tx);
|
||||
let (account_proof, storage_root) = trie
|
||||
.generate_acount_proof(&tx.inner().tx().unwrap(), root, hashed_address)
|
||||
.expect("failed to generate proof");
|
||||
|
||||
// values extracted from geth via rpc:
|
||||
let expected_account = hex!("f86fa1205126413e7857595763591580306b3f228f999498c4c5dfa74f633364936e7651b84bf849819b8418b0d164a029ff6f4d518044318d75b118cf439d8d3d7249c8afcba06ba9ecdf8959410571a02ce1a85814ad94a94ed2a1abaf7c57e9b64326622c1b8c21b4ba4d0e7df61392").as_slice();
|
||||
let expected_storage = [
|
||||
[
|
||||
// 0x0000000000000000000000000000000000000000000000000000000000000002
|
||||
hex!("f8518080a04355bd3061ad2d17e0782413925b4fd81a56bd162d91eedb2a00d6c87611471480a015503e91f9250654cf72906e38a7cb14c3f1cc06658379d37f0c5b5c32482880808080808080808080808080").as_slice(),
|
||||
hex!("e2a0305787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace01").as_slice(),
|
||||
],
|
||||
[
|
||||
// 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||
hex!("f8518080a04355bd3061ad2d17e0782413925b4fd81a56bd162d91eedb2a00d6c87611471480a015503e91f9250654cf72906e38a7cb14c3f1cc06658379d37f0c5b5c32482880808080808080808080808080").as_slice(),
|
||||
hex!("e2a0390decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56303").as_slice(),
|
||||
]
|
||||
];
|
||||
|
||||
assert!(storage_root != EMPTY_ROOT);
|
||||
|
||||
assert_eq!(account_proof.len(), 1);
|
||||
assert_eq!(account_proof[0], expected_account);
|
||||
|
||||
let storage_proofs = trie
|
||||
.generate_storage_proofs(
|
||||
&tx.inner().tx().unwrap(),
|
||||
storage_root,
|
||||
hashed_address,
|
||||
&[keccak256(H256::from_low_u64_be(2)), keccak256(H256::zero())],
|
||||
)
|
||||
.expect("couldn't generate storage proof");
|
||||
|
||||
for (proof, expected) in storage_proofs.into_iter().zip(expected_storage) {
|
||||
assert_eq!(proof.len(), expected.len());
|
||||
for (got_node, expected_node) in proof.into_iter().zip(expected) {
|
||||
assert_eq!(got_node, expected_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user