use crate::{Nibbles, TrieAccount};
use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
use alloy_rlp::{encode_fixed_size, Decodable};
use alloy_trie::{
nodes::TrieNode,
proof::{verify_proof, ProofVerificationError},
EMPTY_ROOT_HASH,
};
use reth_primitives_traits::{constants::KECCAK_EMPTY, Account};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
#[derive(Clone, Default, Debug)]
pub struct MultiProof {
pub account_subtree: BTreeMap<Nibbles, Bytes>,
pub storages: HashMap<B256, StorageMultiProof>,
}
impl MultiProof {
pub fn account_proof(
&self,
address: Address,
slots: &[B256],
) -> Result<AccountProof, alloy_rlp::Error> {
let hashed_address = keccak256(address);
let nibbles = Nibbles::unpack(hashed_address);
let proof = self
.account_subtree
.iter()
.filter(|(path, _)| nibbles.starts_with(path))
.map(|(_, node)| node.clone())
.collect::<Vec<_>>();
let info = 'info: {
if let Some(last) = proof.last() {
if let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? {
if nibbles.ends_with(&leaf.key) {
let account = TrieAccount::decode(&mut &leaf.value[..])?;
break 'info Some(Account {
balance: account.balance,
nonce: account.nonce,
bytecode_hash: (account.code_hash != KECCAK_EMPTY)
.then_some(account.code_hash),
})
}
}
}
None
};
let storage_multiproof = self.storages.get(&hashed_address);
let storage_root = storage_multiproof.map(|m| m.root).unwrap_or(EMPTY_ROOT_HASH);
let mut storage_proofs = Vec::with_capacity(slots.len());
for slot in slots {
let proof = if let Some(multiproof) = &storage_multiproof {
multiproof.storage_proof(*slot)?
} else {
StorageProof::new(*slot)
};
storage_proofs.push(proof);
}
Ok(AccountProof { address, info, proof, storage_root, storage_proofs })
}
}
#[derive(Clone, Debug)]
pub struct StorageMultiProof {
pub root: B256,
pub subtree: BTreeMap<Nibbles, Bytes>,
}
impl Default for StorageMultiProof {
fn default() -> Self {
Self { root: EMPTY_ROOT_HASH, subtree: BTreeMap::default() }
}
}
impl StorageMultiProof {
pub fn storage_proof(&self, slot: B256) -> Result<StorageProof, alloy_rlp::Error> {
let nibbles = Nibbles::unpack(keccak256(slot));
let proof = self
.subtree
.iter()
.filter(|(path, _)| nibbles.starts_with(path))
.map(|(_, node)| node.clone())
.collect::<Vec<_>>();
let value = 'value: {
if let Some(last) = proof.last() {
if let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? {
if nibbles.ends_with(&leaf.key) {
break 'value U256::decode(&mut &leaf.value[..])?
}
}
}
U256::ZERO
};
Ok(StorageProof { key: slot, nibbles, value, proof })
}
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountProof {
pub address: Address,
pub info: Option<Account>,
pub proof: Vec<Bytes>,
pub storage_root: B256,
pub storage_proofs: Vec<StorageProof>,
}
impl Default for AccountProof {
fn default() -> Self {
Self::new(Address::default())
}
}
impl AccountProof {
pub const fn new(address: Address) -> Self {
Self {
address,
info: None,
proof: Vec::new(),
storage_root: EMPTY_ROOT_HASH,
storage_proofs: Vec::new(),
}
}
pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> {
for storage_proof in &self.storage_proofs {
storage_proof.verify(self.storage_root)?;
}
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)
}
}
#[derive(Clone, PartialEq, Eq, Default, Debug, Serialize, Deserialize)]
pub struct StorageProof {
pub key: B256,
pub nibbles: Nibbles,
pub value: U256,
pub proof: Vec<Bytes>,
}
impl StorageProof {
pub fn new(key: B256) -> Self {
let nibbles = Nibbles::unpack(keccak256(key));
Self { key, nibbles, ..Default::default() }
}
pub fn new_with_hashed(key: B256, hashed_key: B256) -> Self {
Self { key, nibbles: Nibbles::unpack(hashed_key), ..Default::default() }
}
pub fn new_with_nibbles(key: B256, nibbles: Nibbles) -> Self {
Self { key, nibbles, ..Default::default() }
}
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)
}
}
#[cfg(any(test, feature = "test-utils"))]
pub mod triehash {
use alloy_primitives::{keccak256, B256};
use hash_db::Hasher;
use plain_hasher::PlainHasher;
#[derive(Default, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct KeccakHasher;
#[cfg(any(test, feature = "test-utils"))]
impl Hasher for KeccakHasher {
type Out = B256;
type StdHasher = PlainHasher;
const LENGTH: usize = 32;
fn hash(x: &[u8]) -> Self::Out {
keccak256(x)
}
}
}