Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby committed Apr 22, 2024
1 parent 34a999a commit c90f868
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 325 deletions.
1 change: 1 addition & 0 deletions crates/mpt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ alloy-primitives = { workspace = true, features = ["rlp"] }
# External
alloy-trie = { version = "0.3.1", default-features = false }
alloy-rlp = { version = "0.3.4", default-features = false }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", default-features = false }
smallvec = "1.13"
revm-primitives = { version = "3.1.1", default-features = false }
revm = { version = "8.0.0", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion crates/mpt/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# `kona-mpt`

Utilities for interacting with and iterating through a merkle patricia trie
Utilities for interacting with a merkle patricia trie in the client program.
48 changes: 48 additions & 0 deletions crates/mpt/src/db/account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! This module contains the [TrieAccount] struct.

use alloy_consensus::constants::KECCAK_EMPTY;
use alloy_primitives::{B256, U256};
use alloy_rlp::{RlpDecodable, RlpEncodable};
use revm_primitives::{Account, AccountInfo};

/// An Ethereum account as represented in the trie.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, RlpEncodable, RlpDecodable)]
pub struct TrieAccount {
/// Account nonce.
nonce: u64,
/// Account balance.
balance: U256,
/// Account's storage root.
storage_root: B256,
/// Hash of the account's bytecode.
code_hash: B256,
}

impl From<(Account, B256)> for TrieAccount {
fn from((account, storage_root): (Account, B256)) -> Self {
Self {
nonce: account.info.nonce,
balance: account.info.balance,
storage_root,
code_hash: account.info.code_hash,
}
}
}

impl From<(AccountInfo, B256)> for TrieAccount {
fn from((account, storage_root): (AccountInfo, B256)) -> Self {
Self {
nonce: account.nonce,
balance: account.balance,
storage_root,
code_hash: account.code_hash,
}
}
}

impl TrieAccount {
/// Get account's storage root.
pub fn storage_root(&self) -> B256 {
self.storage_root
}
}
105 changes: 19 additions & 86 deletions crates/mpt/src/db/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! This module contains an implementation of an in-memory Trie DB, that allows for incremental updates through fetching
//! node preimages on the fly.
//! This module contains an implementation of an in-memory Trie DB for [revm], that allows for
//! incremental updates through fetching node preimages on the fly during execution.

#![allow(dead_code, unused)]

use crate::{NodeElement, TrieNode};
use alloc::collections::VecDeque;
use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
use alloy_rlp::Decodable;
Expand All @@ -12,21 +13,25 @@ use core::marker::PhantomData;
use revm::{db::DbAccount, Database, DatabaseCommit, DatabaseRef, InMemoryDB};
use revm_primitives::{hash_map::Entry, Account, AccountInfo, Bytecode, HashMap};

use crate::{NodeElement, TrieNode};
mod account;
pub use account::TrieAccount;

/// A Trie DB that caches account state in-memory. When accounts that don't already exist within the cache are queried,
/// the database fetches the preimages of the trie nodes on the path to the account using the `PreimageFetcher`
/// (`PF` generic) and `CodeHashFetcher` (`CHF` generic). This allows for data to be fetched in a verifiable manner
/// given an initial trusted state root as it is needed during execution.
/// A Trie DB that caches account state in-memory. When accounts that don't already exist within the
/// cache are queried, the database fetches the preimages of the trie nodes on the path to the
/// account using the `PreimageFetcher` (`PF` generic) and `CodeHashFetcher` (`CHF` generic). This
/// allows for data to be fetched in a verifiable manner given an initial trusted state root as it
/// is needed during execution.
///
/// **Behavior**:
/// - When an account is queried and it does not already exist in the inner cache database, we fall through to the
/// `PreimageFetcher` to fetch the preimages of the trie nodes on the path to the account. After it has been fetched,
/// the account is inserted into the cache database and will be read from there on subsequent queries.
/// - When querying for the code hash of an account, the `CodeHashFetcher` is consulted to fetch the code hash of the
/// account.
/// - When a changeset is committed to the database, the changes are first applied to the cache database and then the
/// trie is recomputed. The root hash of the trie is then persisted as
/// - When an account is queried and it does not already exist in the inner cache database, we fall
/// through to the `PreimageFetcher` to fetch the preimages of the trie nodes on the path to the
/// account. After it has been fetched, the account is inserted into the cache database and will
/// be read from there on subsequent queries.
/// - When querying for the code hash of an account, the `CodeHashFetcher` is consulted to fetch the
/// code hash of the account.
/// - When a changeset is committed to the database, the changes are first applied to the cache
/// database and then the trie hash is recomputed. The root hash of the trie is then persisted to
/// the struct.
#[derive(Debug, Default, Clone)]
pub struct TrieCacheDB<PF, CHF> {
/// The underlying DB that stores the account state in-memory.
Expand Down Expand Up @@ -88,78 +93,6 @@ where

todo!()
}

/// Walks down the trie to a leaf value with the given key, if it exists. Preimages for blinded nodes along the
/// path are fetched using the `fetcher` function.
///
/// TODO: Fix nibble relations
fn get_trie(
&self,
item_key: &Bytes,
trie_node: TrieNode,
mut pos: usize,
fetcher: PF,
) -> Result<(Bytes, Bytes)> {
match trie_node {
TrieNode::Branch { stack } => {
let next = item_key[pos];
extern crate std;
std::dbg!(next);

// for (i, node) in stack.into_iter().enumerate() {
// match node {
// NodeElement::String(s) => {
// // If the string is a hash, we need to grab the preimage for it and
// // continue recursing.
// let hash: B256 = s.as_ref().try_into().map_err(|e| anyhow!("Conversion error: {e}"))?;
// let trie_node = TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?;
//
// // If the value was found in the blinded node, return it.
// if let Ok((key, value)) = self.get_trie(item_key, trie_node, pos, fetcher) {
// return Ok((key, value));
// }
// }
// list @ NodeElement::List(_) => {
// let trie_node = list.try_list_into_node()?;
//
// // If the value was found in the blinded node, return it.
// if let Ok((key, value)) = self.get_trie(item_key, trie_node, pos, fetcher) {
// return Ok((key, value));
// }
// }
// _ => { /* Skip over empty lists and strings; We're looking for leaves */ }
// };
// }

anyhow::bail!("Key does not exist in trie");
}
TrieNode::Leaf { key, value } => {
let shared_nibbles = key[1..].as_ref();
let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref();
if item_key_nibbles == shared_nibbles {
Ok((key, value))
} else {
anyhow::bail!("Key does not exist in trie");
}
}
TrieNode::Extension { prefix, node } => {
let shared_nibbles = prefix[1..].as_ref();
let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref();
if item_key_nibbles == shared_nibbles {
// Increase the offset within the key by the length of the shared nibbles
pos += shared_nibbles.len();

// Follow extension branch
let hash = B256::from_slice(node.as_ref());
let extension_link =
TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?;
self.get_trie(item_key, extension_link, pos, fetcher)
} else {
anyhow::bail!("Key does not exist in trie");
}
}
}
}
}

impl<PF, CHF> DatabaseCommit for TrieCacheDB<PF, CHF>
Expand Down
5 changes: 4 additions & 1 deletion crates/mpt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ mod list_walker;
pub use list_walker::OrderedListWalker;

mod db;
pub use db::TrieCacheDB;
pub use db::{TrieAccount, TrieCacheDB};

mod retrieval;
pub use retrieval::retrieve;

#[cfg(test)]
mod test_util;
4 changes: 0 additions & 4 deletions crates/mpt/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ impl TrieNode {
match path[0] >> 4 {
PREFIX_EXTENSION_EVEN | PREFIX_EXTENSION_ODD => {
// extension node
{
extern crate std;
std::dbg!("FOUND EXTENSION NODE", &path, &value);
}
Ok(TrieNode::Extension { prefix: path, node: value })
}
PREFIX_LEAF_EVEN | PREFIX_LEAF_ODD => {
Expand Down
138 changes: 138 additions & 0 deletions crates/mpt/src/retrieval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! Contains the [retrieve] function, allowing for retrieving values from leaves within a Merkle
//! Patricia Trie by key.

use crate::{NodeElement, TrieNode};
use alloy_primitives::{Bytes, B256};
use alloy_rlp::Decodable;
use alloy_trie::Nibbles;
use anyhow::{anyhow, Result};

/// Walks down the trie to a leaf value with the given key, if it exists. Preimages for blinded
/// nodes along the path are fetched using the `fetcher` function.
///
/// ## Takes
/// - `item_key` - The nibbles representation of the key being retrieved
/// - `trie_node` - The root trie node
/// - `pos` - The number of nibbles that have already been traversed in the `item_key`
/// - `fetcher` - The preimage fetcher for intermediate blinded nodes
///
/// ## Returns
/// - `Err(_)` - Could not retrieve the node with the given key from the trie.
/// - `Ok((_, _))` - The key and value of the node
pub fn retrieve(
item_key: &Nibbles,
trie_node: TrieNode,
mut pos: usize,
fetcher: impl Fn(B256) -> Result<Bytes> + Copy,
) -> Result<Bytes> {
match trie_node {
TrieNode::Branch { mut stack } => {
let branch_nibble = item_key[pos];
pos += 1;

match stack
.remove(branch_nibble as usize)
.ok_or(anyhow!("Key does not exist in trie"))?
{
NodeElement::String(s) => {
// If the string is a hash, we need to grab the preimage for it and
// continue recursing.
let hash: B256 =
s.as_ref().try_into().map_err(|e| anyhow!("Conversion error: {e}"))?;
let trie_node =
TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?;

// If the value was found in the blinded node, return it.
if let Ok(value) = retrieve(item_key, trie_node, pos, fetcher) {
return Ok(value);
}
}
list @ NodeElement::List(_) => {
let trie_node = list.try_list_into_node()?;

// If the value was found in the blinded node, return it.
if let Ok(value) = retrieve(item_key, trie_node, pos, fetcher) {
return Ok(value);
}
}
_ => { /* Skip over empty lists and strings; We're looking for leaves */ }
};

anyhow::bail!("Key does not exist in trie");
}
TrieNode::Leaf { key, value } => {
// If the key length is one, it only contains the prefix and no shared nibbles. Return
// the key and value.
if key.len() == 1 {
return Ok(value);
}

let key_nibbles = Nibbles::unpack(key.clone());
let shared_nibbles = key_nibbles[1..].as_ref();
let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref();

if item_key_nibbles == shared_nibbles {
Ok(value)
} else {
anyhow::bail!("Key does not exist in trie");
}
}
TrieNode::Extension { prefix, node } => {
let prefix_nibbles = Nibbles::unpack(prefix);
let shared_nibbles = prefix_nibbles[1..].as_ref();
let item_key_nibbles = item_key[pos..pos + shared_nibbles.len()].as_ref();
if item_key_nibbles == shared_nibbles {
// Increase the offset within the key by the length of the shared nibbles
pos += shared_nibbles.len();

// Follow extension branch
let hash = B256::from_slice(node.as_ref());
let extension_link =
TrieNode::decode(&mut fetcher(hash)?.as_ref()).map_err(|e| anyhow!(e))?;
retrieve(item_key, extension_link, pos, fetcher)
} else {
anyhow::bail!("Key does not exist in trie");
}
}
}
}

#[cfg(test)]
mod test {
use alloc::{collections::BTreeMap, vec::Vec};
use alloy_primitives::{keccak256, Bytes, B256};
use alloy_rlp::{Decodable, Encodable, EMPTY_STRING_CODE};
use alloy_trie::Nibbles;
use anyhow::{anyhow, Result};

use crate::{retrieve, test_util::ordered_trie_with_encoder, TrieNode};

#[test]
fn test_retrieve_from_trie_simple() {
const VALUES: [&str; 5] = ["yeah", "dog", ", ", "laminar", "flow"];

let mut trie = ordered_trie_with_encoder(&VALUES, |v, buf| v.encode(buf));
let root = trie.root();

let preimages =
trie.take_proofs().into_iter().fold(BTreeMap::default(), |mut acc, (_, value)| {
acc.insert(keccak256(value.as_ref()), value);
acc
});
let fetcher = |h: B256| -> Result<Bytes> {
preimages.get(&h).cloned().ok_or(anyhow!("Failed to find preimage"))
};

let root = TrieNode::decode(&mut fetcher(root).unwrap().as_ref()).unwrap();

for (i, value) in VALUES.iter().enumerate() {
let key_nibbles = Nibbles::unpack([if i == 0 { EMPTY_STRING_CODE } else { i as u8 }]);
let v = retrieve(&key_nibbles, root.clone(), 0, fetcher).unwrap();

let mut encoded_value = Vec::with_capacity(value.length());
value.encode(&mut encoded_value);

assert_eq!(v, encoded_value);
}
}
}
Loading

0 comments on commit c90f868

Please sign in to comment.