Skip to content

Commit

Permalink
feat: implement VerkleTrie
Browse files Browse the repository at this point in the history
  • Loading branch information
morph-dev committed Jul 25, 2024
1 parent 2ba5151 commit d472411
Show file tree
Hide file tree
Showing 13 changed files with 1,672 additions and 7 deletions.
15 changes: 9 additions & 6 deletions portal-verkle-primitives/src/ec/crs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ impl CRS {

/// Single scalar multiplication.
pub fn commit_single(index: usize, scalar: ScalarField) -> Point {
Point::new(INSTANCE.wnaf_precomp.mul_index(scalar.inner(), index))
if scalar.is_zero() {
Point::zero()
} else {
Point::new(INSTANCE.wnaf_precomp.mul_index(scalar.inner(), index))
}
}

/// Commit to sparse set of scalars.
Expand All @@ -88,11 +92,10 @@ impl CRS {
}
Self::commit(&dense)
} else {
let mut result = Point::zero();
for (index, value) in scalars {
result += Self::commit_single(*index, value.clone())
}
result
scalars
.iter()
.map(|(index, value)| Self::commit_single(*index, value.clone()))
.sum()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion portal-verkle-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ pub mod portal;
pub mod proof;
pub mod ssz;
mod stem;
pub mod storage;
mod trie_key;
mod trie_value;
pub mod verkle;
11 changes: 11 additions & 0 deletions portal-verkle-primitives/src/verkle/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use thiserror::Error;

use crate::Stem;

#[derive(Debug, Error)]
pub enum VerkleTrieError {
#[error("Expected stem {expected}, but received {actual}")]
UnexpectedStem { expected: Stem, actual: Stem },
#[error("Node not found at depth {depth} for stem {stem} during the trie traversal")]
NodeNotFound { stem: Stem, depth: usize },
}
98 changes: 98 additions & 0 deletions portal-verkle-primitives/src/verkle/genesis_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use std::collections::{BTreeMap, HashMap};

use alloy_primitives::{b256, keccak256, Address, Bytes, B256, U256};
use serde::{Deserialize, Serialize};

use crate::{Stem, TrieKey, TrieValue};

use super::{storage::AccountStorageLayout, StateWrites, StemStateWrite};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AccountAlloc {
pub balance: U256,
pub nonce: Option<U256>,
pub code: Option<Bytes>,
pub storage: Option<HashMap<U256, TrieValue>>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenesisConfig {
pub alloc: HashMap<Address, AccountAlloc>,
}

impl GenesisConfig {
pub const DEVNET6_BLOCK_HASH: B256 =
b256!("3fe165c03e7a77d1e3759362ebeeb16fd964cb411ce11fbe35c7032fab5b9a8a");
pub const DEVNET6_STATE_ROOT: B256 =
b256!("1fbf85345a3cbba9a6d44f991b721e55620a22397c2a93ee8d5011136ac300ee");

pub fn into_state_writes(self) -> StateWrites {
let mut state_writes = BTreeMap::<Stem, StemStateWrite>::new();
let mut insert_state_write = |key: TrieKey, value: TrieValue| {
let stem = key.stem();
state_writes
.entry(stem)
.or_insert_with(|| StemStateWrite {
stem,
writes: HashMap::new(),
})
.writes
.insert(key.suffix(), value);
};

for (address, account_alloc) in self.alloc {
let storage_layout = AccountStorageLayout::new(address);
insert_state_write(storage_layout.version_key(), U256::ZERO.into());
insert_state_write(storage_layout.balance_key(), account_alloc.balance.into());
insert_state_write(
storage_layout.nonce_key(),
account_alloc.nonce.unwrap_or(U256::ZERO).into(),
);

match &account_alloc.code {
None => insert_state_write(storage_layout.code_hash_key(), keccak256([]).into()),
Some(code) => {
insert_state_write(storage_layout.code_hash_key(), keccak256(code).into());
insert_state_write(
storage_layout.code_size_key(),
U256::from(code.len()).into(),
);
for (key, value) in storage_layout.chunkify_code(code) {
insert_state_write(key, value);
}
}
}

if let Some(storage) = account_alloc.storage {
for (storage_key, value) in storage {
insert_state_write(storage_layout.storage_slot_key(storage_key), value);
}
}
}

StateWrites::new(state_writes.into_values().collect())
}
}

impl From<GenesisConfig> for StateWrites {
fn from(genesis_config: GenesisConfig) -> Self {
genesis_config.into_state_writes()
}
}

#[cfg(test)]
mod tests {
use std::{fs::File, io::BufReader};

use super::*;

#[test]
fn parse_genesis() -> anyhow::Result<()> {
let reader = BufReader::new(File::open("../testdata/genesis.json")?);
let genesis_config: GenesisConfig = serde_json::from_reader(reader)?;
let alloc = genesis_config.alloc;
assert_eq!(alloc.len(), 278);
Ok(())
}
}
37 changes: 37 additions & 0 deletions portal-verkle-primitives/src/verkle/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::collections::{HashMap, HashSet};

use derive_more::{Constructor, Deref, Index};

use crate::{ssz::TriePath, Stem, TrieValue};

use nodes::{branch::BranchNode, leaf::LeafNode};
pub use trie::VerkleTrie;

pub mod error;
pub mod genesis_config;
pub mod nodes;
pub mod storage;
mod trie;
pub mod trie_printer;

#[derive(Debug, Clone, PartialEq, Eq, Constructor, Deref, Index)]
pub struct StateWrites(Vec<StemStateWrite>);

#[derive(Debug, Clone, PartialEq, Eq, Constructor)]
pub struct StemStateWrite {
pub stem: Stem,
pub writes: HashMap<u8, TrieValue>,
}

pub type NewBranchNode = Option<TriePath>;

#[derive(Clone)]
pub struct AuxiliaryTrieModifications {
pub new_branch_nodes: HashSet<TriePath>,
}

#[derive(Clone)]
pub struct PathToLeaf<'a> {
pub branches: Vec<&'a BranchNode>,
pub leaf: &'a LeafNode,
}
118 changes: 118 additions & 0 deletions portal-verkle-primitives/src/verkle/nodes/branch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use std::{array, mem};

use crate::{
constants::VERKLE_NODE_WIDTH,
ssz::TriePath,
verkle::{NewBranchNode, StemStateWrite},
Point, ScalarField, Stem, TrieKey, TrieValue,
};

use super::{commitment::Commitment, leaf::LeafNode, Node};

pub struct BranchNode {
depth: usize,
commitment: Commitment,
children: [Node; VERKLE_NODE_WIDTH],
}

impl BranchNode {
pub fn new(depth: usize) -> Self {
if depth >= Stem::len_bytes() {
panic!("Invalid branch depth!")
}
Self {
depth,
commitment: Commitment::zero(),
children: array::from_fn(|_| Node::Empty),
}
}

pub fn commitment(&self) -> &Point {
self.commitment.commitment()
}

pub fn commitment_hash(&mut self) -> ScalarField {
self.commitment.commitment_hash()
}

pub fn get(&self, key: &TrieKey) -> Option<&TrieValue> {
let index = key[self.depth] as usize;
match &self.children[index] {
Node::Empty => None,
Node::Branch(branch_node) => branch_node.get(key),
Node::Leaf(leaf_node) => {
if key.starts_with_stem(leaf_node.stem()) {
leaf_node.get(key.suffix() as usize)
} else {
None
}
}
}
}

pub(crate) fn get_child(&self, index: usize) -> &Node {
&self.children[index]
}

fn set_child(&mut self, index: usize, mut child: Node) {
self.commitment.update_single(
index,
child.commitment_hash() - self.children[index].commitment_hash(),
);
self.children[index] = child;
}

/// Returns by how much the commitmant hash has changed and the path to the new branch node if
/// one was created.
pub fn update(&mut self, state_write: &StemStateWrite) -> (ScalarField, NewBranchNode) {
let index = state_write.stem[self.depth] as usize;
let child = &mut self.children[index];
match child {
Node::Empty => {
let mut leaf_node = Box::new(LeafNode::new(state_write.stem));
leaf_node.update(&state_write.writes);
*child = Node::Leaf(leaf_node);
(
self.commitment
.update_single(index, child.commitment_hash()),
None,
)
}
Node::Branch(branch_node) => {
let (commitment_hash_diff, new_branch_node) = branch_node.update(state_write);
(
self.commitment.update_single(index, commitment_hash_diff),
new_branch_node,
)
}
Node::Leaf(leaf_node) => {
if leaf_node.stem() == &state_write.stem {
let commitment_hash_diff = leaf_node.update(&state_write.writes);
(
self.commitment.update_single(index, commitment_hash_diff),
None,
)
} else {
let old_commitment_hash = leaf_node.commitment_hash();

let old_child_index_in_new_branch = leaf_node.stem()[self.depth + 1] as usize;
let old_child = mem::replace(child, Node::Empty);

let mut branch_node = Box::new(Self::new(self.depth + 1));
branch_node.set_child(old_child_index_in_new_branch, old_child);
branch_node.update(state_write);

let new_branch_node = Some(TriePath::from(
state_write.stem[..branch_node.depth].to_vec(),
));
*child = Node::Branch(branch_node);
(
self.commitment
.update_single(index, child.commitment_hash() - old_commitment_hash),
new_branch_node,
)
}
}
}
}
}
60 changes: 60 additions & 0 deletions portal-verkle-primitives/src/verkle/nodes/commitment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::ops::AddAssign;

use crate::{Point, ScalarField, CRS};

pub struct Commitment {
commitment: Point,
commitment_hash: Option<ScalarField>,
}

impl Commitment {
pub fn new(commitment: Point) -> Self {
Self {
commitment,
commitment_hash: None,
}
}

pub fn commitment(&self) -> &Point {
&self.commitment
}

pub fn commitment_hash(&mut self) -> ScalarField {
self.commitment_hash
.get_or_insert_with(|| self.commitment.map_to_scalar_field())
.clone()
}

/// Updates this commitment and returns by how much the commitment hash changed.
///
/// @param diff By how much scalar changed.
pub fn update_single(&mut self, index: usize, diff: ScalarField) -> ScalarField {
let old_commitment_hash = self.commitment_hash();
*self += CRS::commit_single(index, diff);
self.commitment_hash() - old_commitment_hash
}

/// Updates this commitment and returns by how much the commitment hash changed.
///
/// @param diff By how much each inner scalar changed.
pub fn update(&mut self, diff: &[(usize, ScalarField)]) -> ScalarField {
let old_commitment_hash = self.commitment_hash();
*self += CRS::commit_sparse(diff);
self.commitment_hash() - old_commitment_hash
}

pub fn zero() -> Self {
Self::new(Point::zero())
}

pub fn is_zero(&self) -> bool {
self.commitment.is_zero()
}
}

impl AddAssign<Point> for Commitment {
fn add_assign(&mut self, rhs: Point) {
self.commitment += rhs;
self.commitment_hash = None;
}
}
Loading

0 comments on commit d472411

Please sign in to comment.