Merge pull request #785 from mir-protocol/storage_trie_inside_state_trie

Treat storage tries as sub-tries of the state trie
This commit is contained in:
Daniel Lubarov 2022-10-16 20:48:49 -07:00 committed by GitHub
commit e0fe4bcbe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 396 additions and 291 deletions

View File

@ -14,7 +14,7 @@ ethereum-types = "0.14.0"
hex = { version = "0.4.3", optional = true }
hex-literal = "0.3.4"
itertools = "0.10.3"
keccak-hash = "0.9.0"
keccak-hash = "0.10.0"
log = "0.4.14"
num = "0.4.0"
maybe_rayon = { path = "../maybe_rayon" }

View File

@ -48,6 +48,7 @@ pub(crate) fn combined_kernel() -> Kernel {
include_str!("asm/mpt/insert_leaf.asm"),
include_str!("asm/mpt/insert_trie_specific.asm"),
include_str!("asm/mpt/load.asm"),
include_str!("asm/mpt/load_trie_specific.asm"),
include_str!("asm/mpt/read.asm"),
include_str!("asm/mpt/storage_read.asm"),
include_str!("asm/mpt/storage_write.asm"),

View File

@ -47,7 +47,29 @@ mpt_hash_hash_rlp_after_unpacking:
// Pre stack: node_ptr, encode_value, retdest
// Post stack: result, result_len
global encode_or_hash_node:
%stack (node_ptr, encode_value) -> (node_ptr, encode_value, maybe_hash_node)
// stack: node_ptr, encode_value, retdest
DUP1 %mload_trie_data
// Check if we're dealing with a concrete node, i.e. not a hash node.
// stack: node_type, node_ptr, encode_value, retdest
DUP1
PUSH @MPT_NODE_HASH
SUB
%jumpi(encode_or_hash_concrete_node)
// If we got here, node_type == @MPT_NODE_HASH.
// Load the hash and return (hash, 32).
// stack: node_type, node_ptr, encode_value, retdest
POP
// stack: node_ptr, encode_value, retdest
%increment // Skip over node type prefix
// stack: hash_ptr, encode_value, retdest
%mload_trie_data
// stack: hash, encode_value, retdest
%stack (hash, encode_value, retdest) -> (retdest, hash, 32)
JUMP
encode_or_hash_concrete_node:
%stack (node_type, node_ptr, encode_value) -> (node_type, node_ptr, encode_value, maybe_hash_node)
%jump(encode_node)
maybe_hash_node:
// stack: result_ptr, result_len, retdest
@ -75,22 +97,22 @@ after_packed_small_rlp:
// RLP encode the given trie node, and return an (pointer, length) pair
// indicating where the data lives within @SEGMENT_RLP_RAW.
//
// Pre stack: node_ptr, encode_value, retdest
// Pre stack: node_type, node_ptr, encode_value, retdest
// Post stack: result_ptr, result_len
global encode_node:
// stack: node_ptr, encode_value, retdest
DUP1 %mload_trie_data
encode_node:
// stack: node_type, node_ptr, encode_value, retdest
// Increment node_ptr, so it points to the node payload instead of its type.
SWAP1 %increment SWAP1
// stack: node_type, node_payload_ptr, encode_value, retdest
DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(encode_node_empty)
DUP1 %eq_const(@MPT_NODE_HASH) %jumpi(encode_node_hash)
DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(encode_node_branch)
DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(encode_node_extension)
DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(encode_node_leaf)
PANIC // Invalid node type? Shouldn't get here.
// If we got here, node_type is either @MPT_NODE_HASH, which should have
// been handled earlier in encode_or_hash_node, or something invalid.
PANIC
global encode_node_empty:
// stack: node_type, node_payload_ptr, encode_value, retdest
@ -105,14 +127,6 @@ global encode_node_empty:
%stack (retdest) -> (retdest, 0, 1)
JUMP
global encode_node_hash:
// stack: node_type, node_payload_ptr, encode_value, retdest
POP
// stack: node_payload_ptr, encode_value, retdest
%mload_trie_data
%stack (hash, encode_value, retdest) -> (retdest, hash, 32)
JUMP
encode_node_branch:
// stack: node_type, node_payload_ptr, encode_value, retdest
POP

View File

@ -72,8 +72,13 @@ global encode_account:
// stack: balance, rlp_pos_4, value_ptr, retdest
SWAP1 %encode_rlp_scalar
// stack: rlp_pos_5, value_ptr, retdest
DUP2 %add_const(2) %mload_trie_data // storage_root = value[2]
// stack: storage_root, rlp_pos_5, value_ptr, retdest
PUSH encode_account_after_hash_storage_trie
PUSH encode_storage_value
DUP4 %add_const(2) %mload_trie_data // storage_root_ptr = value[2]
// stack: storage_root_ptr, encode_storage_value, encode_account_after_hash_storage_trie, rlp_pos_5, value_ptr, retdest
%jump(mpt_hash)
encode_account_after_hash_storage_trie:
// stack: storage_root_digest, rlp_pos_5, value_ptr, retdest
SWAP1 %encode_rlp_256
// stack: rlp_pos_6, value_ptr, retdest
SWAP1 %add_const(3) %mload_trie_data // code_hash = value[3]
@ -88,3 +93,6 @@ encode_txn:
encode_receipt:
PANIC // TODO
encode_storage_value:
PANIC // TODO

View File

@ -1,6 +1,3 @@
// TODO: Receipt trie leaves are variable-length, so we need to be careful not
// to permit buffer over-reads.
// Load all partial trie data from prover inputs.
global load_all_mpts:
// stack: retdest
@ -9,47 +6,20 @@ global load_all_mpts:
PUSH 1
%set_trie_data_size
%load_mpt %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT)
%load_mpt %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT)
%load_mpt %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT)
%load_mpt(mpt_load_state_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT)
%load_mpt(mpt_load_txn_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT)
%load_mpt(mpt_load_receipt_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT)
PROVER_INPUT(mpt)
// stack: num_storage_tries, retdest
DUP1 %mstore_global_metadata(@GLOBAL_METADATA_NUM_STORAGE_TRIES)
// stack: num_storage_tries, retdest
PUSH 0 // i = 0
// stack: i, num_storage_tries, retdest
storage_trie_loop:
DUP2 DUP2 EQ
// stack: i == num_storage_tries, i, num_storage_tries, retdest
%jumpi(storage_trie_loop_end)
// stack: i, num_storage_tries, retdest
PROVER_INPUT(mpt)
// stack: storage_trie_addr, i, num_storage_tries, retdest
DUP2
// stack: i, storage_trie_addr, i, num_storage_tries, retdest
%mstore_kernel(@SEGMENT_STORAGE_TRIE_ADDRS)
// stack: i, num_storage_tries, retdest
%load_mpt
// stack: root_ptr, i, num_storage_tries, retdest
DUP2
// stack: i, root_ptr, i, num_storage_tries, retdest
%mstore_kernel(@SEGMENT_STORAGE_TRIE_PTRS)
// stack: i, num_storage_tries, retdest
%jump(storage_trie_loop)
storage_trie_loop_end:
// stack: i, num_storage_tries, retdest
%pop2
// stack: retdest
JUMP
// Load an MPT from prover inputs.
// Pre stack: retdest
// Pre stack: load_value, retdest
// Post stack: node_ptr
load_mpt:
// stack: retdest
global load_mpt:
// stack: load_value, retdest
PROVER_INPUT(mpt)
// stack: node_type, retdest
// stack: node_type, load_value, retdest
DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(load_mpt_empty)
DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(load_mpt_branch)
@ -60,146 +30,144 @@ load_mpt:
load_mpt_empty:
// TRIE_DATA[0] = 0, and an empty node has type 0, so we can simply return the null pointer.
%stack (node_type, retdest) -> (retdest, 0)
%stack (node_type, load_value, retdest) -> (retdest, 0)
JUMP
load_mpt_branch:
// stack: node_type, retdest
// stack: node_type, load_value, retdest
%get_trie_data_size
// stack: node_ptr, node_type, retdest
// stack: node_ptr, node_type, load_value, retdest
SWAP1 %append_to_trie_data
// stack: node_ptr, retdest
// stack: node_ptr, load_value, retdest
// Save the offset of our 16 child pointers so we can write them later.
// Then advance our current trie pointer beyond them, so we can load the
// value and have it placed after our child pointers.
%get_trie_data_size
// stack: children_ptr, node_ptr, retdest
// stack: children_ptr, node_ptr, load_value, retdest
DUP1 %add_const(17) // Skip over 16 children plus the value pointer
// stack: value_ptr, children_ptr, node_ptr, retdest
%set_trie_data_size
// stack: children_ptr, node_ptr, retdest
%load_value
// stack: end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest
DUP1 %set_trie_data_size
// Now the top of the stack points to where the branch node will end and the
// value will begin, if there is a value. But we need to ask the prover if a
// value is present, and point to null if not.
// stack: end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest
PROVER_INPUT(mpt)
// stack: is_value_present, end_of_branch_ptr, children_ptr, node_ptr, load_value, retdest
%jumpi(load_mpt_branch_value_present)
// There is no value present, so value_ptr = null.
%stack (end_of_branch_ptr) -> (0)
// stack: value_ptr, children_ptr, node_ptr, load_value, retdest
%jump(load_mpt_branch_after_load_value)
load_mpt_branch_value_present:
// stack: value_ptr, children_ptr, node_ptr, load_value, retdest
PUSH load_mpt_branch_after_load_value
DUP5 // load_value
JUMP
load_mpt_branch_after_load_value:
// stack: value_ptr, children_ptr, node_ptr, load_value, retdest
SWAP1
// stack: children_ptr, value_ptr, node_ptr, retdest
// stack: children_ptr, value_ptr, node_ptr, load_value, retdest
// Load the 16 children.
%rep 16
DUP4 // load_value
%load_mpt
// stack: child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, retdest
// stack: child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest
DUP2
// stack: next_child_ptr_ptr, child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, retdest
// stack: next_child_ptr_ptr, child_ptr, next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest
%mstore_trie_data
// stack: next_child_ptr_ptr, value_ptr, node_ptr, retdest
// stack: next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest
%increment
// stack: next_child_ptr_ptr, value_ptr, node_ptr, retdest
// stack: next_child_ptr_ptr, value_ptr, node_ptr, load_value, retdest
%endrep
// stack: value_ptr_ptr, value_ptr, node_ptr, retdest
// stack: value_ptr_ptr, value_ptr, node_ptr, load_value, retdest
%mstore_trie_data
// stack: node_ptr, retdest
SWAP1
%stack (node_ptr, load_value, retdest) -> (retdest, node_ptr)
JUMP
load_mpt_extension:
// stack: node_type, retdest
// stack: node_type, load_value, retdest
%get_trie_data_size
// stack: node_ptr, node_type, retdest
// stack: node_ptr, node_type, load_value, retdest
SWAP1 %append_to_trie_data
// stack: node_ptr, retdest
// stack: node_ptr, load_value, retdest
PROVER_INPUT(mpt) // read num_nibbles
%append_to_trie_data
PROVER_INPUT(mpt) // read packed_nibbles
%append_to_trie_data
// stack: node_ptr, retdest
// stack: node_ptr, load_value, retdest
%get_trie_data_size
// stack: child_ptr_ptr, node_ptr, retdest
// stack: child_ptr_ptr, node_ptr, load_value, retdest
// Increment trie_data_size, to leave room for child_ptr_ptr, before we load our child.
DUP1 %increment %set_trie_data_size
// stack: child_ptr_ptr, node_ptr, retdest
%load_mpt
// stack: child_ptr, child_ptr_ptr, node_ptr, retdest
SWAP1
%mstore_trie_data
// stack: node_ptr, retdest
SWAP1
%stack (child_ptr_ptr, node_ptr, load_value, retdest)
-> (load_value, load_mpt_extension_after_load_mpt,
child_ptr_ptr, retdest, node_ptr)
%jump(load_mpt)
load_mpt_extension_after_load_mpt:
// stack: child_ptr, child_ptr_ptr, retdest, node_ptr
SWAP1 %mstore_trie_data
// stack: retdest, node_ptr
JUMP
load_mpt_leaf:
// stack: node_type, retdest
// stack: node_type, load_value, retdest
%get_trie_data_size
// stack: node_ptr, node_type, retdest
// stack: node_ptr, node_type, load_value, retdest
SWAP1 %append_to_trie_data
// stack: node_ptr, retdest
// stack: node_ptr, load_value, retdest
PROVER_INPUT(mpt) // read num_nibbles
%append_to_trie_data
PROVER_INPUT(mpt) // read packed_nibbles
%append_to_trie_data
// stack: node_ptr, retdest
// stack: node_ptr, load_value, retdest
// We save value_ptr_ptr = get_trie_data_size, then increment trie_data_size
// to skip over the slot for value_ptr. We will write value_ptr after the
// load_value call.
// to skip over the slot for value_ptr_ptr. We will write to value_ptr_ptr
// after the load_value call.
%get_trie_data_size
// stack: value_ptr_ptr, node_ptr, retdest
DUP1 %increment %set_trie_data_size
// stack: value_ptr_ptr, node_ptr, retdest
%load_value
// stack: value_ptr, value_ptr_ptr, node_ptr, retdest
SWAP1 %mstore_trie_data
// stack: node_ptr, retdest
SWAP1
// stack: value_ptr_ptr, node_ptr, load_value, retdest
DUP1 %increment
// stack: value_ptr, value_ptr_ptr, node_ptr, load_value, retdest
DUP1 %set_trie_data_size
// stack: value_ptr, value_ptr_ptr, node_ptr, load_value, retdest
%stack (value_ptr, value_ptr_ptr, node_ptr, load_value, retdest)
-> (load_value, load_mpt_leaf_after_load_value,
value_ptr_ptr, value_ptr, retdest, node_ptr)
JUMP
load_mpt_leaf_after_load_value:
// stack: value_ptr_ptr, value_ptr, retdest, node_ptr
%mstore_trie_data
// stack: retdest, node_ptr
JUMP
load_mpt_digest:
// stack: node_type, retdest
// stack: node_type, load_value, retdest
%get_trie_data_size
// stack: node_ptr, node_type, retdest
// stack: node_ptr, node_type, load_value, retdest
SWAP1 %append_to_trie_data
// stack: node_ptr, retdest
// stack: node_ptr, load_value, retdest
PROVER_INPUT(mpt) // read digest
%append_to_trie_data
// stack: node_ptr, retdest
SWAP1
%stack (node_ptr, load_value, retdest) -> (retdest, node_ptr)
JUMP
// Convenience macro to call load_mpt and return where we left off.
// Pre stack: load_value
// Post stack: node_ptr
%macro load_mpt
PUSH %%after
%stack (load_value) -> (load_value, %%after)
%jump(load_mpt)
%%after:
%endmacro
// Load a value from prover input, append it to trie data, and return a pointer to it.
// Return null if the value is empty.
%macro load_value
// stack: (empty)
PROVER_INPUT(mpt)
// stack: value_len
DUP1 %jumpi(%%has_value)
%stack (value_len) -> (0)
%jump(%%end)
%%has_value:
// stack: value_len
%get_trie_data_size
// stack: value_ptr, value_len
SWAP1
// stack: value_len, value_ptr
%%loop:
DUP1 ISZERO
// stack: value_len == 0, value_len, value_ptr
%jumpi(%%finish_loop)
// stack: value_len, value_ptr
PROVER_INPUT(mpt)
// stack: value_part, value_len, value_ptr
%append_to_trie_data
// stack: value_len, value_ptr
%decrement
// stack: value_len', value_ptr
%jump(%%loop)
%%finish_loop:
// stack: value_len, value_ptr
POP
// stack: value_ptr
%%end:
// Convenience macro to call load_mpt and return where we left off.
// Pre stack: (empty)
// Post stack: node_ptr
%macro load_mpt(load_value)
PUSH %%after
PUSH $load_value
%jump(load_mpt)
%%after:
%endmacro

View File

@ -0,0 +1,40 @@
global mpt_load_state_trie_value:
// stack: retdest
// Load and append the nonce and balance.
PROVER_INPUT(mpt) %append_to_trie_data
PROVER_INPUT(mpt) %append_to_trie_data
// Now increment the trie data size by 2, to leave room for our storage trie
// pointer and code hash fields, before calling load_mpt which will append
// our storage trie data.
%get_trie_data_size
// stack: storage_trie_ptr_ptr, retdest
DUP1 %add_const(2)
// stack: storage_trie_ptr, storage_trie_ptr_ptr, retdest
%set_trie_data_size
// stack: storage_trie_ptr_ptr, retdest
%load_mpt(mpt_load_storage_trie_value)
// stack: storage_trie_ptr, storage_trie_ptr_ptr, retdest
DUP2 %mstore_trie_data
// stack: storage_trie_ptr_ptr, retdest
%increment
// stack: code_hash_ptr, retdest
PROVER_INPUT(mpt)
// stack: code_hash, code_hash_ptr, retdest
SWAP1 %mstore_trie_data
// stack: retdest
JUMP
global mpt_load_txn_trie_value:
// stack: retdest
PANIC // TODO
global mpt_load_receipt_trie_value:
// stack: retdest
PANIC // TODO
global mpt_load_storage_trie_value:
// stack: retdest
PANIC // TODO

View File

@ -21,7 +21,7 @@ pub(crate) enum ContextMetadata {
/// prohibited.
Static = 8,
/// Pointer to the initial version of the state trie, at the creation of this context. Used when
/// we need to revert a context. See also `StorageTrieCheckpointPointers`.
/// we need to revert a context.
StateTrieCheckpointPointer = 9,
}

View File

@ -18,9 +18,6 @@ pub(crate) enum GlobalMetadata {
TransactionTrieRoot = 5,
/// A pointer to the root of the receipt trie within the `TrieData` buffer.
ReceiptTrieRoot = 6,
/// The number of storage tries involved in these transactions. I.e. the number of values in
/// `StorageTrieAddresses`, `StorageTriePointers` and `StorageTrieCheckpointPointers`.
NumStorageTries = 7,
// The root digests of each Merkle trie before these transactions.
StateTrieRootDigestBefore = 8,
@ -38,7 +35,7 @@ pub(crate) enum GlobalMetadata {
}
impl GlobalMetadata {
pub(crate) const COUNT: usize = 15;
pub(crate) const COUNT: usize = 14;
pub(crate) fn all() -> [Self; Self::COUNT] {
[
@ -49,7 +46,6 @@ impl GlobalMetadata {
Self::StateTrieRoot,
Self::TransactionTrieRoot,
Self::ReceiptTrieRoot,
Self::NumStorageTries,
Self::StateTrieRootDigestBefore,
Self::TransactionTrieRootDigestBefore,
Self::ReceiptTrieRootDigestBefore,
@ -70,7 +66,6 @@ impl GlobalMetadata {
GlobalMetadata::StateTrieRoot => "GLOBAL_METADATA_STATE_TRIE_ROOT",
GlobalMetadata::TransactionTrieRoot => "GLOBAL_METADATA_TXN_TRIE_ROOT",
GlobalMetadata::ReceiptTrieRoot => "GLOBAL_METADATA_RECEIPT_TRIE_ROOT",
GlobalMetadata::NumStorageTries => "GLOBAL_METADATA_NUM_STORAGE_TRIES",
GlobalMetadata::StateTrieRootDigestBefore => "GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE",
GlobalMetadata::TransactionTrieRootDigestBefore => {
"GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE"

View File

@ -1,12 +1,12 @@
use anyhow::Result;
use eth_trie_utils::partial_trie::PartialTrie;
use ethereum_types::{BigEndianHash, H256, U256};
use ethereum_types::{BigEndianHash, H256};
use super::nibbles;
use crate::cpu::kernel::aggregator::KERNEL;
use crate::cpu::kernel::interpreter::Interpreter;
use crate::cpu::kernel::tests::mpt::extension_to_leaf;
use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp};
use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1_rlp, test_account_2_rlp};
use crate::generation::mpt::all_mpt_prover_inputs_reversed;
use crate::generation::TrieInputs;
// TODO: Test with short leaf. Might need to be a storage trie.
@ -24,73 +24,69 @@ fn mpt_hash_empty() -> Result<()> {
}
#[test]
fn mpt_hash_leaf() -> Result<()> {
let account = AccountRlp {
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
fn mpt_hash_empty_branch() -> Result<()> {
let children = std::array::from_fn(|_| PartialTrie::Empty.into());
let state_trie = PartialTrie::Branch {
children,
value: vec![],
};
let account_rlp = rlp::encode(&account);
let state_trie = PartialTrie::Leaf {
nibbles: nibbles(0xABC),
value: account_rlp.to_vec(),
};
let trie_inputs = TrieInputs {
state_trie,
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
test_state_trie(trie_inputs)
}
#[test]
fn mpt_hash_hash() -> Result<()> {
let hash = H256::random();
let trie_inputs = TrieInputs {
state_trie: PartialTrie::Hash(hash),
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
test_state_trie(trie_inputs)
}
#[test]
fn mpt_hash_leaf() -> Result<()> {
let state_trie = PartialTrie::Leaf {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
};
let trie_inputs = TrieInputs {
state_trie,
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
test_state_trie(trie_inputs)
}
#[test]
fn mpt_hash_extension_to_leaf() -> Result<()> {
let account = AccountRlp {
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
};
let account_rlp = rlp::encode(&account);
let state_trie = extension_to_leaf(account_rlp.to_vec());
let state_trie = extension_to_leaf(test_account_1_rlp());
let trie_inputs = TrieInputs {
state_trie,
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
test_state_trie(trie_inputs)
}
#[test]
fn mpt_hash_branch_to_leaf() -> Result<()> {
let account = AccountRlp {
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
};
let account_rlp = rlp::encode(&account);
let leaf = PartialTrie::Leaf {
nibbles: nibbles(0xABC),
value: account_rlp.to_vec(),
value: test_account_2_rlp(),
}
.into();
let mut children = std::array::from_fn(|_| PartialTrie::Empty.into());
children[5] = PartialTrie::Branch {
children: children.clone(),
value: vec![],
}
.into();
children[3] = leaf;
let state_trie = PartialTrie::Branch {
children,

View File

@ -6,13 +6,13 @@ use super::nibbles;
use crate::cpu::kernel::aggregator::KERNEL;
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
use crate::cpu::kernel::interpreter::Interpreter;
use crate::cpu::kernel::tests::mpt::{test_account_1_rlp, test_account_2_rlp};
use crate::cpu::kernel::tests::mpt::{test_account_1_rlp, test_account_2};
use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp};
use crate::generation::TrieInputs;
#[test]
fn mpt_insert_empty() -> Result<()> {
test_state_trie(Default::default(), nibbles(0xABC), test_account_2_rlp())
test_state_trie(Default::default(), nibbles(0xABC), test_account_2())
}
#[test]
@ -22,7 +22,7 @@ fn mpt_insert_leaf_identical_keys() -> Result<()> {
nibbles: key,
value: test_account_1_rlp(),
};
test_state_trie(state_trie, key, test_account_2_rlp())
test_state_trie(state_trie, key, test_account_2())
}
#[test]
@ -31,7 +31,7 @@ fn mpt_insert_leaf_nonoverlapping_keys() -> Result<()> {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
};
test_state_trie(state_trie, nibbles(0x123), test_account_2_rlp())
test_state_trie(state_trie, nibbles(0x123), test_account_2())
}
#[test]
@ -40,7 +40,7 @@ fn mpt_insert_leaf_overlapping_keys() -> Result<()> {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
};
test_state_trie(state_trie, nibbles(0xADE), test_account_2_rlp())
test_state_trie(state_trie, nibbles(0xADE), test_account_2())
}
#[test]
@ -49,7 +49,7 @@ fn mpt_insert_leaf_insert_key_extends_leaf_key() -> Result<()> {
nibbles: nibbles(0xABC),
value: test_account_1_rlp(),
};
test_state_trie(state_trie, nibbles(0xABCDE), test_account_2_rlp())
test_state_trie(state_trie, nibbles(0xABCDE), test_account_2())
}
#[test]
@ -58,7 +58,7 @@ fn mpt_insert_leaf_leaf_key_extends_insert_key() -> Result<()> {
nibbles: nibbles(0xABCDE),
value: test_account_1_rlp(),
};
test_state_trie(state_trie, nibbles(0xABC), test_account_2_rlp())
test_state_trie(state_trie, nibbles(0xABC), test_account_2())
}
#[test]
@ -69,10 +69,13 @@ fn mpt_insert_branch_replacing_empty_child() -> Result<()> {
value: vec![],
};
test_state_trie(state_trie, nibbles(0xABC), test_account_2_rlp())
test_state_trie(state_trie, nibbles(0xABC), test_account_2())
}
#[test]
// TODO: Not a valid test because branches state trie cannot have branch values.
// We should change it to use a different trie.
#[ignore]
fn mpt_insert_extension_nonoverlapping_keys() -> Result<()> {
// Existing keys are 0xABC, 0xABCDEF; inserted key is 0x12345.
let mut children = std::array::from_fn(|_| PartialTrie::Empty.into());
@ -89,10 +92,13 @@ fn mpt_insert_extension_nonoverlapping_keys() -> Result<()> {
}
.into(),
};
test_state_trie(state_trie, nibbles(0x12345), test_account_2_rlp())
test_state_trie(state_trie, nibbles(0x12345), test_account_2())
}
#[test]
// TODO: Not a valid test because branches state trie cannot have branch values.
// We should change it to use a different trie.
#[ignore]
fn mpt_insert_extension_insert_key_extends_node_key() -> Result<()> {
// Existing keys are 0xA, 0xABCD; inserted key is 0xABCDEF.
let mut children = std::array::from_fn(|_| PartialTrie::Empty.into());
@ -109,7 +115,7 @@ fn mpt_insert_extension_insert_key_extends_node_key() -> Result<()> {
}
.into(),
};
test_state_trie(state_trie, nibbles(0xABCDEF), test_account_2_rlp())
test_state_trie(state_trie, nibbles(0xABCDEF), test_account_2())
}
#[test]
@ -126,10 +132,14 @@ fn mpt_insert_branch_to_leaf_same_key() -> Result<()> {
value: vec![],
};
test_state_trie(state_trie, nibbles(0xABCD), test_account_2_rlp())
test_state_trie(state_trie, nibbles(0xABCD), test_account_2())
}
fn test_state_trie(state_trie: PartialTrie, k: Nibbles, v: Vec<u8>) -> Result<()> {
/// Note: The account's storage_root is ignored, as we can't insert a new storage_root without the
/// accompanying trie data. An empty trie's storage_root is used instead.
fn test_state_trie(state_trie: PartialTrie, k: Nibbles, mut account: AccountRlp) -> Result<()> {
account.storage_root = PartialTrie::Empty.calc_hash();
let trie_inputs = TrieInputs {
state_trie: state_trie.clone(),
transactions_trie: Default::default(),
@ -155,9 +165,13 @@ fn test_state_trie(state_trie: PartialTrie, k: Nibbles, v: Vec<u8>) -> Result<()
trie_data.push(0.into());
}
let value_ptr = trie_data.len();
let account: AccountRlp = rlp::decode(&v).expect("Decoding failed");
let account_data = account.to_vec();
trie_data.extend(account_data);
trie_data.push(account.nonce);
trie_data.push(account.balance);
// In memory, storage_root gets interpreted as a pointer to a storage trie,
// so we have to ensure the pointer is valid. It's easiest to set it to 0,
// which works as an empty node, since trie_data[0] = 0 = MPT_TYPE_EMPTY.
trie_data.push(H256::zero().into_uint());
trie_data.push(account.code_hash.into_uint());
let trie_data_len = trie_data.len().into();
interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len);
interpreter.push(0xDEADBEEFu32.into());
@ -186,7 +200,7 @@ fn test_state_trie(state_trie: PartialTrie, k: Nibbles, v: Vec<u8>) -> Result<()
);
let hash = H256::from_uint(&interpreter.stack()[0]);
let updated_trie = state_trie.insert(k, v);
let updated_trie = state_trie.insert(k, rlp::encode(&account).to_vec());
let expected_state_trie_hash = updated_trie.calc_hash();
assert_eq!(hash, expected_state_trie_hash);

View File

@ -1,6 +1,6 @@
use anyhow::Result;
use eth_trie_utils::partial_trie::PartialTrie;
use ethereum_types::{BigEndianHash, U256};
use ethereum_types::{BigEndianHash, H256, U256};
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
use crate::cpu::kernel::constants::trie_type::PartialTrieType;
@ -42,11 +42,6 @@ fn load_all_mpts_empty() -> Result<()> {
0.into()
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries),
trie_inputs.storage_tries.len().into()
);
Ok(())
}
@ -81,8 +76,11 @@ fn load_all_mpts_leaf() -> Result<()> {
5.into(), // value ptr
test_account_1().nonce,
test_account_1().balance,
test_account_1().storage_root.into_uint(),
9.into(), // pointer to storage trie root
test_account_1().code_hash.into_uint(),
// These last two elements encode the storage trie, which is a hash node.
(PartialTrieType::Hash as u32).into(),
test_account_1().storage_root.into_uint(),
]
);
@ -95,9 +93,40 @@ fn load_all_mpts_leaf() -> Result<()> {
0.into()
);
Ok(())
}
#[test]
fn load_all_mpts_hash() -> Result<()> {
let hash = H256::random();
let trie_inputs = TrieInputs {
state_trie: PartialTrie::Hash(hash),
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
};
let load_all_mpts = KERNEL.global_labels["load_all_mpts"];
let initial_stack = vec![0xDEADBEEFu32.into()];
let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack);
interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs);
interpreter.run()?;
assert_eq!(interpreter.stack(), vec![]);
let type_hash = U256::from(PartialTrieType::Hash as u32);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries),
trie_inputs.storage_tries.len().into()
interpreter.get_trie_data(),
vec![0.into(), type_hash, hash.into_uint(),]
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot),
0.into()
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot),
0.into()
);
Ok(())
@ -160,11 +189,6 @@ fn load_all_mpts_empty_branch() -> Result<()> {
0.into()
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries),
trie_inputs.storage_tries.len().into()
);
Ok(())
}
@ -201,15 +225,13 @@ fn load_all_mpts_ext_to_leaf() -> Result<()> {
9.into(), // value pointer
test_account_1().nonce,
test_account_1().balance,
test_account_1().storage_root.into_uint(),
13.into(), // pointer to storage trie root
test_account_1().code_hash.into_uint(),
// These last two elements encode the storage trie, which is a hash node.
(PartialTrieType::Hash as u32).into(),
test_account_1().storage_root.into_uint(),
]
);
assert_eq!(
interpreter.get_global_metadata_field(GlobalMetadata::NumStorageTries),
trie_inputs.storage_tries.len().into()
);
Ok(())
}

View File

@ -1,25 +1,17 @@
use anyhow::Result;
use ethereum_types::{BigEndianHash, H256, U256};
use ethereum_types::BigEndianHash;
use crate::cpu::kernel::aggregator::KERNEL;
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
use crate::cpu::kernel::interpreter::Interpreter;
use crate::cpu::kernel::tests::mpt::extension_to_leaf;
use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp};
use crate::cpu::kernel::tests::mpt::{extension_to_leaf, test_account_1, test_account_1_rlp};
use crate::generation::mpt::all_mpt_prover_inputs_reversed;
use crate::generation::TrieInputs;
#[test]
fn mpt_read() -> Result<()> {
let account = AccountRlp {
nonce: U256::from(1111),
balance: U256::from(2222),
storage_root: H256::from_uint(&U256::from(3333)),
code_hash: H256::from_uint(&U256::from(4444)),
};
let account_rlp = rlp::encode(&account);
let trie_inputs = TrieInputs {
state_trie: extension_to_leaf(account_rlp.to_vec()),
state_trie: extension_to_leaf(test_account_1_rlp()),
transactions_trie: Default::default(),
receipts_trie: Default::default(),
storage_tries: vec![],
@ -45,10 +37,11 @@ fn mpt_read() -> Result<()> {
assert_eq!(interpreter.stack().len(), 1);
let result_ptr = interpreter.stack()[0].as_usize();
let result = &interpreter.get_trie_data()[result_ptr..][..4];
assert_eq!(result[0], account.nonce);
assert_eq!(result[1], account.balance);
assert_eq!(result[2], account.storage_root.into_uint());
assert_eq!(result[3], account.code_hash.into_uint());
assert_eq!(result[0], test_account_1().nonce);
assert_eq!(result[1], test_account_1().balance);
// result[2] is the storage root pointer. We won't check that it matches a
// particular address, since that seems like over-specifying.
assert_eq!(result[3], test_account_1().code_hash.into_uint());
Ok(())
}

View File

@ -1,5 +1,8 @@
use eth_trie_utils::partial_trie::PartialTrie;
use std::collections::HashMap;
use eth_trie_utils::partial_trie::{Nibbles, PartialTrie};
use ethereum_types::{BigEndianHash, H256, U256};
use keccak_hash::keccak;
use rlp_derive::{RlpDecodable, RlpEncodable};
use crate::cpu::kernel::constants::trie_type::PartialTrieType;
@ -13,17 +16,6 @@ pub(crate) struct AccountRlp {
pub(crate) code_hash: H256,
}
impl AccountRlp {
pub(crate) fn to_vec(&self) -> Vec<U256> {
vec![
self.nonce,
self.balance,
self.storage_root.into_uint(),
self.code_hash.into_uint(),
]
}
}
pub(crate) fn all_mpt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec<U256> {
let mut inputs = all_mpt_prover_inputs(trie_inputs);
inputs.reverse();
@ -34,10 +26,18 @@ pub(crate) fn all_mpt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec<U2
pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec<U256> {
let mut prover_inputs = vec![];
mpt_prover_inputs(&trie_inputs.state_trie, &mut prover_inputs, &|rlp| {
let account: AccountRlp = rlp::decode(rlp).expect("Decoding failed");
account.to_vec()
});
let storage_tries_by_state_key = trie_inputs
.storage_tries
.iter()
.map(|(address, storage_trie)| (Nibbles::from(keccak(address)), storage_trie))
.collect();
mpt_prover_inputs_state_trie(
&trie_inputs.state_trie,
empty_nibbles(),
&mut prover_inputs,
&storage_tries_by_state_key,
);
mpt_prover_inputs(&trie_inputs.transactions_trie, &mut prover_inputs, &|rlp| {
rlp::decode_list(rlp)
@ -48,14 +48,6 @@ pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec<U256> {
vec![]
});
prover_inputs.push(trie_inputs.storage_tries.len().into());
for (addr, storage_trie) in &trie_inputs.storage_tries {
prover_inputs.push(addr.0.as_ref().into());
mpt_prover_inputs(storage_trie, &mut prover_inputs, &|leaf_be| {
vec![U256::from_big_endian(leaf_be)]
});
}
prover_inputs
}
@ -66,7 +58,7 @@ pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec<U256> {
pub(crate) fn mpt_prover_inputs<F>(
trie: &PartialTrie,
prover_inputs: &mut Vec<U256>,
parse_leaf: &F,
parse_value: &F,
) where
F: Fn(&[u8]) -> Vec<U256>,
{
@ -76,28 +68,108 @@ pub(crate) fn mpt_prover_inputs<F>(
PartialTrie::Hash(h) => prover_inputs.push(U256::from_big_endian(h.as_bytes())),
PartialTrie::Branch { children, value } => {
if value.is_empty() {
// There's no value, so length=0.
// There's no value, so value_len = 0.
prover_inputs.push(U256::zero());
} else {
let leaf = parse_leaf(value);
prover_inputs.push(leaf.len().into());
prover_inputs.extend(leaf);
let parsed_value = parse_value(value);
prover_inputs.push(parsed_value.len().into());
prover_inputs.extend(parsed_value);
}
for child in children {
mpt_prover_inputs(child, prover_inputs, parse_leaf);
mpt_prover_inputs(child, prover_inputs, parse_value);
}
}
PartialTrie::Extension { nibbles, child } => {
prover_inputs.push(nibbles.count.into());
prover_inputs.push(nibbles.packed);
mpt_prover_inputs(child, prover_inputs, parse_leaf);
mpt_prover_inputs(child, prover_inputs, parse_value);
}
PartialTrie::Leaf { nibbles, value } => {
prover_inputs.push(nibbles.count.into());
prover_inputs.push(nibbles.packed);
let leaf = parse_leaf(value);
prover_inputs.push(leaf.len().into());
let leaf = parse_value(value);
prover_inputs.extend(leaf);
}
}
}
/// Like `mpt_prover_inputs`, but for the state trie, which is a bit unique since each value
/// leads to a storage trie which we recursively traverse.
pub(crate) fn mpt_prover_inputs_state_trie(
trie: &PartialTrie,
key: Nibbles,
prover_inputs: &mut Vec<U256>,
storage_tries_by_state_key: &HashMap<Nibbles, &PartialTrie>,
) {
prover_inputs.push((PartialTrieType::of(trie) as u32).into());
match trie {
PartialTrie::Empty => {}
PartialTrie::Hash(h) => prover_inputs.push(U256::from_big_endian(h.as_bytes())),
PartialTrie::Branch { children, value } => {
assert!(value.is_empty(), "State trie should not have branch values");
// There's no value, so value_len = 0.
prover_inputs.push(U256::zero());
for (i, child) in children.iter().enumerate() {
let extended_key = key.merge(&Nibbles {
count: 1,
packed: i.into(),
});
mpt_prover_inputs_state_trie(
child,
extended_key,
prover_inputs,
storage_tries_by_state_key,
);
}
}
PartialTrie::Extension { nibbles, child } => {
prover_inputs.push(nibbles.count.into());
prover_inputs.push(nibbles.packed);
let extended_key = key.merge(nibbles);
mpt_prover_inputs_state_trie(
child,
extended_key,
prover_inputs,
storage_tries_by_state_key,
);
}
PartialTrie::Leaf { nibbles, value } => {
let account: AccountRlp = rlp::decode(value).expect("Decoding failed");
let AccountRlp {
nonce,
balance,
storage_root,
code_hash,
} = account;
let storage_hash_only = PartialTrie::Hash(storage_root);
let storage_trie: &PartialTrie = storage_tries_by_state_key
.get(&key)
.copied()
.unwrap_or(&storage_hash_only);
assert_eq!(storage_trie.calc_hash(), storage_root,
"In TrieInputs, an account's storage_root didn't match the associated storage trie hash");
prover_inputs.push(nibbles.count.into());
prover_inputs.push(nibbles.packed);
prover_inputs.push(nonce);
prover_inputs.push(balance);
mpt_prover_inputs(storage_trie, prover_inputs, &parse_storage_value);
prover_inputs.push(code_hash.into_uint());
}
}
}
fn parse_storage_value(value_rlp: &[u8]) -> Vec<U256> {
let value: U256 = rlp::decode(value_rlp).expect("Decoding failed");
vec![value]
}
fn empty_nibbles() -> Nibbles {
Nibbles {
count: 0,
packed: U256::zero(),
}
}

View File

@ -29,23 +29,14 @@ pub(crate) enum Segment {
/// Contains all trie data. Tries are stored as immutable, copy-on-write trees, so this is an
/// append-only buffer. It is owned by the kernel, so it only lives on context 0.
TrieData = 12,
/// The account address associated with the `i`th storage trie. Only lives on context 0.
StorageTrieAddresses = 13,
/// A pointer to the `i`th storage trie within the `TrieData` buffer. Only lives on context 0.
StorageTriePointers = 14,
/// Like `StorageTriePointers`, except that these pointers correspond to the version of each
/// trie at the creation of a given context. This lets us easily revert a context by replacing
/// `StorageTriePointers` with `StorageTrieCheckpointPointers`.
/// See also `StateTrieCheckpointPointer`.
StorageTrieCheckpointPointers = 15,
/// A buffer used to store the encodings of a branch node's children.
TrieEncodedChild = 16,
TrieEncodedChild = 13,
/// A buffer used to store the lengths of the encodings of a branch node's children.
TrieEncodedChildLen = 17,
TrieEncodedChildLen = 14,
}
impl Segment {
pub(crate) const COUNT: usize = 18;
pub(crate) const COUNT: usize = 15;
pub(crate) fn all() -> [Self; Self::COUNT] {
[
@ -62,9 +53,6 @@ impl Segment {
Self::TxnData,
Self::RlpRaw,
Self::TrieData,
Self::StorageTrieAddresses,
Self::StorageTriePointers,
Self::StorageTrieCheckpointPointers,
Self::TrieEncodedChild,
Self::TrieEncodedChildLen,
]
@ -86,9 +74,6 @@ impl Segment {
Segment::TxnData => "SEGMENT_TXN_DATA",
Segment::RlpRaw => "SEGMENT_RLP_RAW",
Segment::TrieData => "SEGMENT_TRIE_DATA",
Segment::StorageTrieAddresses => "SEGMENT_STORAGE_TRIE_ADDRS",
Segment::StorageTriePointers => "SEGMENT_STORAGE_TRIE_PTRS",
Segment::StorageTrieCheckpointPointers => "SEGMENT_STORAGE_TRIE_CHECKPOINT_PTRS",
Segment::TrieEncodedChild => "SEGMENT_TRIE_ENCODED_CHILD",
Segment::TrieEncodedChildLen => "SEGMENT_TRIE_ENCODED_CHILD_LEN",
}
@ -110,9 +95,6 @@ impl Segment {
Segment::TxnData => 256,
Segment::RlpRaw => 8,
Segment::TrieData => 256,
Segment::StorageTrieAddresses => 160,
Segment::StorageTriePointers => 32,
Segment::StorageTrieCheckpointPointers => 32,
Segment::TrieEncodedChild => 256,
Segment::TrieEncodedChildLen => 6,
}