feat: proof verification (#8220)

This commit is contained in:
Roman Krasiuk
2024-05-14 17:27:33 +02:00
committed by GitHub
parent 19e5fcb003
commit d1f38f1661
7 changed files with 70 additions and 25 deletions

5
Cargo.lock generated
View File

@ -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",
]

View File

@ -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" }

View File

@ -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};

View File

@ -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)
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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(()));
}
}