mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 19:09:54 +00:00
fix(trie): witness empty root node (#10972)
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{Nibbles, TrieAccount};
|
||||
use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
|
||||
use alloy_rlp::{encode_fixed_size, Decodable};
|
||||
use alloy_rlp::{encode_fixed_size, Decodable, EMPTY_STRING_CODE};
|
||||
use alloy_trie::{
|
||||
nodes::TrieNode,
|
||||
proof::{verify_proof, ProofNodes, ProofVerificationError},
|
||||
@ -86,13 +86,18 @@ pub struct StorageMultiProof {
|
||||
pub subtree: ProofNodes,
|
||||
}
|
||||
|
||||
impl Default for StorageMultiProof {
|
||||
fn default() -> Self {
|
||||
Self { root: EMPTY_ROOT_HASH, subtree: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl StorageMultiProof {
|
||||
/// Create new storage multiproof for empty trie.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
root: EMPTY_ROOT_HASH,
|
||||
subtree: ProofNodes::from_iter([(
|
||||
Nibbles::default(),
|
||||
Bytes::from([EMPTY_STRING_CODE]),
|
||||
)]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return storage proofs for the target storage slot (unhashed).
|
||||
pub fn storage_proof(&self, slot: B256) -> Result<StorageProof, alloy_rlp::Error> {
|
||||
let nibbles = Nibbles::unpack(keccak256(slot));
|
||||
@ -209,6 +214,12 @@ impl StorageProof {
|
||||
Self { key, nibbles, ..Default::default() }
|
||||
}
|
||||
|
||||
/// Set proof nodes on storage proof.
|
||||
pub fn with_proof(mut self, proof: Vec<Bytes>) -> Self {
|
||||
self.proof = proof;
|
||||
self
|
||||
}
|
||||
|
||||
/// Verify the proof against the provided storage root.
|
||||
pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
|
||||
let expected =
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
|
||||
use alloy_rlp::EMPTY_STRING_CODE;
|
||||
use reth_chainspec::{Chain, ChainSpec, HOLESKY, MAINNET};
|
||||
use reth_primitives::{constants::EMPTY_ROOT_HASH, Account};
|
||||
use reth_provider::test_utils::{create_test_provider_factory, insert_genesis};
|
||||
@ -111,7 +112,10 @@ fn testspec_empty_storage_proof() {
|
||||
assert_eq!(slots.len(), account_proof.storage_proofs.len());
|
||||
for (idx, slot) in slots.into_iter().enumerate() {
|
||||
let proof = account_proof.storage_proofs.get(idx).unwrap();
|
||||
assert_eq!(proof, &StorageProof::new(slot));
|
||||
assert_eq!(
|
||||
proof,
|
||||
&StorageProof::new(slot).with_proof(vec![Bytes::from([EMPTY_STRING_CODE])])
|
||||
);
|
||||
assert_eq!(proof.verify(account_proof.storage_root), Ok(()));
|
||||
}
|
||||
assert_eq!(account_proof.verify(root), Ok(()));
|
||||
|
||||
57
crates/trie/db/tests/witness.rs
Normal file
57
crates/trie/db/tests/witness.rs
Normal file
@ -0,0 +1,57 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use alloy_primitives::{
|
||||
keccak256,
|
||||
map::{HashMap, HashSet},
|
||||
Address, Bytes, B256, U256,
|
||||
};
|
||||
use alloy_rlp::EMPTY_STRING_CODE;
|
||||
use reth_primitives::{constants::EMPTY_ROOT_HASH, Account};
|
||||
use reth_provider::{test_utils::create_test_provider_factory, HashingWriter};
|
||||
use reth_trie::{proof::Proof, witness::TrieWitness, HashedPostState, HashedStorage, StateRoot};
|
||||
use reth_trie_db::{DatabaseProof, DatabaseStateRoot, DatabaseTrieWitness};
|
||||
|
||||
#[test]
|
||||
fn includes_empty_node_preimage() {
|
||||
let factory = create_test_provider_factory();
|
||||
let provider = factory.provider_rw().unwrap();
|
||||
|
||||
let address = Address::random();
|
||||
let hashed_address = keccak256(address);
|
||||
let hashed_slot = B256::random();
|
||||
|
||||
// witness includes empty state trie root node
|
||||
assert_eq!(
|
||||
TrieWitness::from_tx(provider.tx_ref())
|
||||
.compute(HashedPostState {
|
||||
accounts: HashMap::from([(hashed_address, Some(Account::default()))]),
|
||||
storages: HashMap::default(),
|
||||
})
|
||||
.unwrap(),
|
||||
HashMap::from_iter([(EMPTY_ROOT_HASH, Bytes::from([EMPTY_STRING_CODE]))])
|
||||
);
|
||||
|
||||
// Insert account into database
|
||||
provider.insert_account_for_hashing([(address, Some(Account::default()))]).unwrap();
|
||||
|
||||
let state_root = StateRoot::from_tx(provider.tx_ref()).root().unwrap();
|
||||
let multiproof = Proof::from_tx(provider.tx_ref())
|
||||
.multiproof(HashMap::from_iter([(hashed_address, HashSet::from_iter([hashed_slot]))]))
|
||||
.unwrap();
|
||||
|
||||
let witness = TrieWitness::from_tx(provider.tx_ref())
|
||||
.compute(HashedPostState {
|
||||
accounts: HashMap::from([(hashed_address, Some(Account::default()))]),
|
||||
storages: HashMap::from([(
|
||||
hashed_address,
|
||||
HashedStorage::from_iter(false, [(hashed_slot, U256::from(1))]),
|
||||
)]),
|
||||
})
|
||||
.unwrap();
|
||||
assert!(witness.contains_key(&state_root));
|
||||
for node in multiproof.account_subtree.values() {
|
||||
assert_eq!(witness.get(&keccak256(node)), Some(node));
|
||||
}
|
||||
// witness includes empty state trie root node
|
||||
assert_eq!(witness.get(&EMPTY_ROOT_HASH), Some(&Bytes::from([EMPTY_STRING_CODE])));
|
||||
}
|
||||
@ -192,7 +192,7 @@ where
|
||||
|
||||
// short circuit on empty storage
|
||||
if hashed_storage_cursor.is_storage_empty()? {
|
||||
return Ok(StorageMultiProof::default())
|
||||
return Ok(StorageMultiProof::empty())
|
||||
}
|
||||
|
||||
let target_nibbles = targets.into_iter().map(Nibbles::unpack).collect::<Vec<_>>();
|
||||
|
||||
@ -17,7 +17,7 @@ use itertools::{Either, Itertools};
|
||||
use reth_execution_errors::TrieWitnessError;
|
||||
use reth_primitives::constants::EMPTY_ROOT_HASH;
|
||||
use reth_trie_common::{
|
||||
BranchNode, HashBuilder, Nibbles, TrieAccount, TrieNode, CHILD_INDEX_RANGE,
|
||||
BranchNode, HashBuilder, Nibbles, StorageMultiProof, TrieAccount, TrieNode, CHILD_INDEX_RANGE,
|
||||
};
|
||||
|
||||
/// State transition witness for the trie.
|
||||
@ -110,8 +110,10 @@ where
|
||||
let mut account_rlp = Vec::with_capacity(128);
|
||||
let mut account_trie_nodes = BTreeMap::default();
|
||||
for (hashed_address, hashed_slots) in proof_targets {
|
||||
let storage_multiproof =
|
||||
account_multiproof.storages.remove(&hashed_address).unwrap_or_default();
|
||||
let storage_multiproof = account_multiproof
|
||||
.storages
|
||||
.remove(&hashed_address)
|
||||
.unwrap_or_else(StorageMultiProof::empty);
|
||||
|
||||
// Gather and record account trie nodes.
|
||||
let account = state
|
||||
@ -215,7 +217,8 @@ where
|
||||
proof: impl IntoIterator<Item = (&'b Nibbles, &'b Bytes)>,
|
||||
) -> Result<BTreeMap<Nibbles, Either<B256, Vec<u8>>>, TrieWitnessError> {
|
||||
let mut trie_nodes = BTreeMap::default();
|
||||
for (path, encoded) in proof {
|
||||
let mut proof_iter = proof.into_iter().enumerate().peekable();
|
||||
while let Some((idx, (path, encoded))) = proof_iter.next() {
|
||||
// Record the node in witness.
|
||||
self.witness.insert(keccak256(encoded.as_ref()), encoded.clone());
|
||||
|
||||
@ -239,7 +242,11 @@ where
|
||||
trie_nodes.insert(next_path.clone(), Either::Right(leaf.value.clone()));
|
||||
}
|
||||
}
|
||||
TrieNode::EmptyRoot => return Err(TrieWitnessError::UnexpectedEmptyRoot(next_path)),
|
||||
TrieNode::EmptyRoot => {
|
||||
if idx != 0 || proof_iter.peek().is_some() {
|
||||
return Err(TrieWitnessError::UnexpectedEmptyRoot(next_path))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user