mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 19:09:54 +00:00
feat: proof verification (#8220)
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -619,9 +619,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-trie"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beb28aa4ecd32fdfa1b1bdd111ff7357dd562c6b2372694cf9e613434fcba659"
|
||||
checksum = "d55bd16fdb7ff4bd74cc4c878eeac7e8a27c0d7ba9df4ab58d9310aaafb62d43"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
@ -633,7 +633,6 @@ dependencies = [
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
|
||||
@ -290,7 +290,7 @@ alloy-primitives = "0.7.2"
|
||||
alloy-dyn-abi = "0.7.2"
|
||||
alloy-sol-types = "0.7.2"
|
||||
alloy-rlp = "0.3.4"
|
||||
alloy-trie = "0.3.1"
|
||||
alloy-trie = "0.4.0"
|
||||
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "dd7a999" }
|
||||
alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "dd7a999" }
|
||||
alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "dd7a999" }
|
||||
|
||||
@ -24,4 +24,4 @@ pub use storage::StorageTrieEntry;
|
||||
mod subnode;
|
||||
pub use subnode::StoredSubNode;
|
||||
|
||||
pub use alloy_trie::{BranchNodeCompact, HashBuilder, TrieMask, EMPTY_ROOT_HASH};
|
||||
pub use alloy_trie::{proof, BranchNodeCompact, HashBuilder, TrieMask, EMPTY_ROOT_HASH};
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
//! Merkle trie proofs.
|
||||
|
||||
use super::Nibbles;
|
||||
use super::{
|
||||
proof::{verify_proof, ProofVerificationError},
|
||||
Nibbles, TrieAccount,
|
||||
};
|
||||
use crate::{keccak256, Account, Address, Bytes, B256, U256};
|
||||
use alloy_rlp::encode_fixed_size;
|
||||
use alloy_trie::EMPTY_ROOT_HASH;
|
||||
|
||||
/// The merkle proof with the relevant account info.
|
||||
#[derive(PartialEq, Eq, Default, Debug)]
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct AccountProof {
|
||||
/// The address associated with the account.
|
||||
pub address: Address,
|
||||
@ -22,7 +27,13 @@ pub struct AccountProof {
|
||||
impl AccountProof {
|
||||
/// Create new account proof entity.
|
||||
pub fn new(address: Address) -> Self {
|
||||
Self { address, ..Default::default() }
|
||||
Self {
|
||||
address,
|
||||
info: None,
|
||||
proof: Vec::new(),
|
||||
storage_root: EMPTY_ROOT_HASH,
|
||||
storage_proofs: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set account info, storage root and requested storage proofs.
|
||||
@ -41,6 +52,26 @@ impl AccountProof {
|
||||
pub fn set_proof(&mut self, proof: Vec<Bytes>) {
|
||||
self.proof = proof;
|
||||
}
|
||||
|
||||
/// Verify the storage proofs and account proof against the provided state root.
|
||||
pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
|
||||
// Verify storage proofs.
|
||||
for storage_proof in &self.storage_proofs {
|
||||
storage_proof.verify(self.storage_root)?;
|
||||
}
|
||||
|
||||
// Verify the account proof.
|
||||
let expected = if self.info.is_none() && self.storage_root == EMPTY_ROOT_HASH {
|
||||
None
|
||||
} else {
|
||||
Some(alloy_rlp::encode(TrieAccount::from((
|
||||
self.info.unwrap_or_default(),
|
||||
self.storage_root,
|
||||
))))
|
||||
};
|
||||
let nibbles = Nibbles::unpack(keccak256(self.address));
|
||||
verify_proof(root, nibbles, expected, &self.proof)
|
||||
}
|
||||
}
|
||||
|
||||
/// The merkle proof of the storage entry.
|
||||
@ -83,4 +114,11 @@ impl StorageProof {
|
||||
pub fn set_proof(&mut self, proof: Vec<Bytes>) {
|
||||
self.proof = proof;
|
||||
}
|
||||
|
||||
/// Verify the proof against the provided storage root.
|
||||
pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
|
||||
let expected =
|
||||
if self.value.is_zero() { None } else { Some(encode_fixed_size(&self.value).to_vec()) };
|
||||
verify_proof(root, self.nibbles.clone(), expected, &self.proof)
|
||||
}
|
||||
}
|
||||
|
||||
@ -560,8 +560,8 @@ impl StateProvider for MockEthProvider {
|
||||
}))
|
||||
}
|
||||
|
||||
fn proof(&self, _address: Address, _keys: &[B256]) -> ProviderResult<AccountProof> {
|
||||
Ok(AccountProof::default())
|
||||
fn proof(&self, address: Address, _keys: &[B256]) -> ProviderResult<AccountProof> {
|
||||
Ok(AccountProof::new(address))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -319,8 +319,8 @@ impl StateProvider for NoopProvider {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn proof(&self, _address: Address, _keys: &[B256]) -> ProviderResult<AccountProof> {
|
||||
Ok(AccountProof::default())
|
||||
fn proof(&self, address: Address, _keys: &[B256]) -> ProviderResult<AccountProof> {
|
||||
Ok(AccountProof::new(address))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ use reth_interfaces::trie::{StateRootError, StorageRootError};
|
||||
use reth_primitives::{
|
||||
constants::EMPTY_ROOT_HASH,
|
||||
keccak256,
|
||||
trie::{AccountProof, HashBuilder, Nibbles, StorageProof, TrieAccount},
|
||||
trie::{proof::ProofRetainer, AccountProof, HashBuilder, Nibbles, StorageProof, TrieAccount},
|
||||
Address, B256,
|
||||
};
|
||||
|
||||
@ -60,8 +60,8 @@ where
|
||||
let walker = TrieWalker::new(trie_cursor, prefix_set.freeze());
|
||||
|
||||
// Create a hash builder to rebuild the root node since it is not available in the database.
|
||||
let mut hash_builder =
|
||||
HashBuilder::default().with_proof_retainer(Vec::from([target_nibbles]));
|
||||
let retainer = ProofRetainer::from_iter([target_nibbles]);
|
||||
let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
|
||||
|
||||
let mut account_rlp = Vec::with_capacity(128);
|
||||
let mut account_node_iter = AccountNodeIter::new(walker, hashed_account_cursor);
|
||||
@ -126,7 +126,8 @@ where
|
||||
);
|
||||
let walker = TrieWalker::new(trie_cursor, prefix_set);
|
||||
|
||||
let mut hash_builder = HashBuilder::default().with_proof_retainer(target_nibbles);
|
||||
let retainer = ProofRetainer::from_iter(target_nibbles);
|
||||
let mut hash_builder = HashBuilder::default().with_proof_retainer(retainer);
|
||||
let mut storage_node_iter =
|
||||
StorageNodeIter::new(walker, hashed_storage_cursor, hashed_address);
|
||||
while let Some(node) = storage_node_iter.try_next()? {
|
||||
@ -200,7 +201,7 @@ mod tests {
|
||||
fn insert_genesis<DB: Database>(
|
||||
provider_factory: &ProviderFactory<DB>,
|
||||
chain_spec: Arc<ChainSpec>,
|
||||
) -> RethResult<()> {
|
||||
) -> RethResult<B256> {
|
||||
let mut provider = provider_factory.provider_rw()?;
|
||||
|
||||
// Hash accounts and insert them into hashing table.
|
||||
@ -224,21 +225,21 @@ mod tests {
|
||||
});
|
||||
provider.insert_storage_for_hashing(alloc_storage)?;
|
||||
|
||||
let (_, updates) = StateRoot::from_tx(provider.tx_ref())
|
||||
let (root, updates) = StateRoot::from_tx(provider.tx_ref())
|
||||
.root_with_updates()
|
||||
.map_err(Into::<reth_db::DatabaseError>::into)?;
|
||||
updates.flush(provider.tx_mut())?;
|
||||
|
||||
provider.commit()?;
|
||||
|
||||
Ok(())
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn testspec_proofs() {
|
||||
// Create test database and insert genesis accounts.
|
||||
let factory = create_test_provider_factory();
|
||||
insert_genesis(&factory, TEST_SPEC.clone()).unwrap();
|
||||
let root = insert_genesis(&factory, TEST_SPEC.clone()).unwrap();
|
||||
|
||||
let data = Vec::from([
|
||||
(
|
||||
@ -288,6 +289,7 @@ mod tests {
|
||||
expected_proof,
|
||||
"proof for {target:?} does not match"
|
||||
);
|
||||
assert_eq!(account_proof.verify(root), Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +297,7 @@ mod tests {
|
||||
fn testspec_empty_storage_proof() {
|
||||
// Create test database and insert genesis accounts.
|
||||
let factory = create_test_provider_factory();
|
||||
insert_genesis(&factory, TEST_SPEC.clone()).unwrap();
|
||||
let root = insert_genesis(&factory, TEST_SPEC.clone()).unwrap();
|
||||
|
||||
let target = Address::from_str("0x1ed9b1dd266b607ee278726d324b855a093394a6").unwrap();
|
||||
let slots = Vec::from([B256::with_last_byte(1), B256::with_last_byte(3)]);
|
||||
@ -306,15 +308,18 @@ mod tests {
|
||||
|
||||
assert_eq!(slots.len(), account_proof.storage_proofs.len());
|
||||
for (idx, slot) in slots.into_iter().enumerate() {
|
||||
assert_eq!(account_proof.storage_proofs.get(idx), Some(&StorageProof::new(slot)));
|
||||
let proof = account_proof.storage_proofs.get(idx).unwrap();
|
||||
assert_eq!(proof, &StorageProof::new(slot));
|
||||
assert_eq!(proof.verify(account_proof.storage_root), Ok(()));
|
||||
}
|
||||
assert_eq!(account_proof.verify(root), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mainnet_genesis_account_proof() {
|
||||
// Create test database and insert genesis accounts.
|
||||
let factory = create_test_provider_factory();
|
||||
insert_genesis(&factory, MAINNET.clone()).unwrap();
|
||||
let root = insert_genesis(&factory, MAINNET.clone()).unwrap();
|
||||
|
||||
// Address from mainnet genesis allocation.
|
||||
// keccak256 - `0xcf67b71c90b0d523dd5004cf206f325748da347685071b34812e21801f5270c4`
|
||||
@ -332,13 +337,14 @@ mod tests {
|
||||
let provider = factory.provider().unwrap();
|
||||
let account_proof = Proof::new(provider.tx_ref()).account_proof(target, &[]).unwrap();
|
||||
similar_asserts::assert_eq!(account_proof.proof, expected_account_proof);
|
||||
assert_eq!(account_proof.verify(root), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mainnet_genesis_account_proof_nonexistent() {
|
||||
// Create test database and insert genesis accounts.
|
||||
let factory = create_test_provider_factory();
|
||||
insert_genesis(&factory, MAINNET.clone()).unwrap();
|
||||
let root = insert_genesis(&factory, MAINNET.clone()).unwrap();
|
||||
|
||||
// Address that does not exist in mainnet genesis allocation.
|
||||
// keccak256 - `0x18f415ffd7f66bb1924d90f0e82fb79ca8c6d8a3473cd9a95446a443b9db1761`
|
||||
@ -354,13 +360,14 @@ mod tests {
|
||||
let provider = factory.provider().unwrap();
|
||||
let account_proof = Proof::new(provider.tx_ref()).account_proof(target, &[]).unwrap();
|
||||
similar_asserts::assert_eq!(account_proof.proof, expected_account_proof);
|
||||
assert_eq!(account_proof.verify(root), Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn holesky_deposit_contract_proof() {
|
||||
// Create test database and insert genesis accounts.
|
||||
let factory = create_test_provider_factory();
|
||||
insert_genesis(&factory, HOLESKY.clone()).unwrap();
|
||||
let root = insert_genesis(&factory, HOLESKY.clone()).unwrap();
|
||||
|
||||
let target = Address::from_str("0x4242424242424242424242424242424242424242").unwrap();
|
||||
// existent
|
||||
@ -439,5 +446,6 @@ mod tests {
|
||||
let provider = factory.provider().unwrap();
|
||||
let account_proof = Proof::new(provider.tx_ref()).account_proof(target, &slots).unwrap();
|
||||
similar_asserts::assert_eq!(account_proof, expected);
|
||||
assert_eq!(account_proof.verify(root), Ok(()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user