From 74ba303255eac248c1e75216d6f8b958cac028ea Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 11 May 2023 14:56:10 +0200 Subject: [PATCH] MPT deletion (#1025) * First try * Fixes * Fix sstore * Comments * Clippy * Fix aggregator.rs * PR feedback --- evm/src/cpu/kernel/aggregator.rs | 2 + evm/src/cpu/kernel/asm/mpt/delete/delete.asm | 20 ++- .../kernel/asm/mpt/delete/delete_branch.asm | 151 ++++++++++++++++++ .../asm/mpt/delete/delete_extension.asm | 99 ++++++++++++ .../kernel/asm/mpt/storage/storage_write.asm | 16 +- evm/src/cpu/kernel/asm/mpt/util.asm | 31 ++++ evm/src/cpu/kernel/tests/mpt/delete.rs | 118 ++++++++++++++ evm/src/cpu/kernel/tests/mpt/mod.rs | 1 + 8 files changed, 436 insertions(+), 2 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/mpt/delete/delete_branch.asm create mode 100644 evm/src/cpu/kernel/asm/mpt/delete/delete_extension.asm create mode 100644 evm/src/cpu/kernel/tests/mpt/delete.rs diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 1091c9e3..f65ba4ec 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -111,6 +111,8 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/memory/txn_fields.asm"), include_str!("asm/mpt/accounts.asm"), include_str!("asm/mpt/delete/delete.asm"), + include_str!("asm/mpt/delete/delete_branch.asm"), + include_str!("asm/mpt/delete/delete_extension.asm"), include_str!("asm/mpt/hash/hash.asm"), include_str!("asm/mpt/hash/hash_trie_specific.asm"), include_str!("asm/mpt/hex_prefix.asm"), diff --git a/evm/src/cpu/kernel/asm/mpt/delete/delete.asm b/evm/src/cpu/kernel/asm/mpt/delete/delete.asm index 3e0b8afe..5df1e283 100644 --- a/evm/src/cpu/kernel/asm/mpt/delete/delete.asm +++ b/evm/src/cpu/kernel/asm/mpt/delete/delete.asm @@ -1,6 +1,24 @@ // Return a copy of the given node with the given key deleted. +// Assumes that the key is in the trie. // // Pre stack: node_ptr, num_nibbles, key, retdest // Post stack: updated_node_ptr global mpt_delete: - PANIC // TODO + // stack: node_ptr, num_nibbles, key, retdest + DUP1 %mload_trie_data + // stack: node_type, node_ptr, num_nibbles, key, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %increment SWAP1 + // stack: node_type, node_payload_ptr, num_nibbles, key, retdest + + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(mpt_delete_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(mpt_delete_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(mpt_delete_leaf) + %eq_const(@MPT_NODE_EMPTY) %jumpi(panic) // This should never happen. + PANIC + +mpt_delete_leaf: + // stack: node_type, node_payload_ptr, num_nibbles, key, retdest + %pop4 + PUSH 0 // empty node ptr + SWAP1 JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/delete/delete_branch.asm b/evm/src/cpu/kernel/asm/mpt/delete/delete_branch.asm new file mode 100644 index 00000000..a88f3b60 --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/delete/delete_branch.asm @@ -0,0 +1,151 @@ +// Delete from a branch node. +// Algorithm is roughly: +// - Delete `(num_nibbles-1, key[1:])` from `branch[key[0]]`. +// - If the returned node is non-empty, update the branch node and return it. +// - Otherwise, count the number of non-empty children of the branch node. +// - If there are more than one, update the branch node and return it. +// - If there is exactly one, transform the branch node into an leaf/extension node and return it. +// Assumes that `num_nibbles>0` and that the value of the branch node is zero. +// TODO: May need to revisit these assumptions depending on how the receipt trie is implemented. +global mpt_delete_branch: + // stack: node_type, node_payload_ptr, num_nibbles, key, retdest + POP + // stack: node_payload_ptr, num_nibbles, key, retdest + DUP2 ISZERO %jumpi(panic) // This should never happen. + DUP3 DUP3 + // stack: num_nibbles, key, node_payload_ptr, num_nibbles, key, retdest + %split_first_nibble + %stack (first_nibble, num_nibbles, key, node_payload_ptr, old_num_nibbles, old_key) -> + (node_payload_ptr, first_nibble, num_nibbles, key, after_mpt_delete_branch, first_nibble, node_payload_ptr) + ADD + // stack: child_ptr_ptr, num_nibbles, key, after_mpt_delete_branch, first_nibble, node_payload_ptr, retdest + %mload_trie_data + %jump(mpt_delete) + +after_mpt_delete_branch: + // stack: updated_child_ptr, first_nibble, node_payload_ptr, retdest + // If the updated child is empty, check if we need to normalize the branch node. + DUP1 %mload_trie_data ISZERO %jumpi(maybe_normalize_branch) + +// Make a copy of the branch node and set `branch[first_nibble] = updated_child_ptr`. +update_branch: + // stack: updated_child_ptr, first_nibble, node_payload_ptr, retdest + %get_trie_data_size + // stack: updated_branch_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + PUSH @MPT_NODE_BRANCH %append_to_trie_data + // stack: updated_branch_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + DUP4 %mload_trie_data %append_to_trie_data // Copy child[0] + DUP4 %add_const(1) %mload_trie_data %append_to_trie_data // ... + DUP4 %add_const(2) %mload_trie_data %append_to_trie_data + DUP4 %add_const(3) %mload_trie_data %append_to_trie_data + DUP4 %add_const(4) %mload_trie_data %append_to_trie_data + DUP4 %add_const(5) %mload_trie_data %append_to_trie_data + DUP4 %add_const(6) %mload_trie_data %append_to_trie_data + DUP4 %add_const(7) %mload_trie_data %append_to_trie_data + DUP4 %add_const(8) %mload_trie_data %append_to_trie_data + DUP4 %add_const(9) %mload_trie_data %append_to_trie_data + DUP4 %add_const(10) %mload_trie_data %append_to_trie_data + DUP4 %add_const(11) %mload_trie_data %append_to_trie_data + DUP4 %add_const(12) %mload_trie_data %append_to_trie_data + DUP4 %add_const(13) %mload_trie_data %append_to_trie_data + DUP4 %add_const(14) %mload_trie_data %append_to_trie_data + DUP4 %add_const(15) %mload_trie_data %append_to_trie_data // Copy child[15] + DUP4 %add_const(16) %mload_trie_data %append_to_trie_data // Copy value_ptr + // stack: updated_branch_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + SWAP1 + // stack: updated_child_ptr, updated_branch_ptr, first_nibble, node_payload_ptr, retdest + DUP2 %increment DUP4 ADD + // stack: updated_branch_ptr+first_nibble+1, updated_child_ptr, updated_branch_ptr, first_nibble, node_payload_ptr, retdest + %mstore_trie_data + %stack (updated_branch_ptr, first_nibble, node_payload_ptr, retdest) -> (retdest, updated_branch_ptr) + JUMP + +// The updated child is empty. Count how many non-empty children the branch node has. +// If it's one, transform the branch node into an leaf/extension node and return it. +maybe_normalize_branch: + // stack: updated_child_ptr, first_nibble, node_payload_ptr, retdest + PUSH 0 %mstore_kernel_general(0) PUSH 0 %mstore_kernel_general(1) + // stack: updated_child_ptr, first_nibble, node_payload_ptr, retdest + PUSH 0 +// Loop from i=0..16 excluding `first_nibble` and store the number of non-empty children in +// KernelGeneral[0]. Also store the last non-empty child in KernelGeneral[1]. +loop: + // stack: i, updated_child_ptr, first_nibble, node_payload_ptr, retdest + DUP1 DUP4 EQ %jumpi(loop_eq_first_nibble) + // stack: i, updated_child_ptr, first_nibble, node_payload_ptr, retdest + DUP1 %eq_const(16) %jumpi(loop_end) + DUP1 DUP5 ADD %mload_trie_data %mload_trie_data ISZERO ISZERO %jumpi(loop_non_empty) + // stack: i, updated_child_ptr, first_nibble, node_payload_ptr, retdest + %increment %jump(loop) +loop_eq_first_nibble: + // stack: i, updated_child_ptr, first_nibble, node_payload_ptr, retdest + %increment %jump(loop) +loop_non_empty: + // stack: i, updated_child_ptr, first_nibble, node_payload_ptr, retdest + %mload_kernel_general(0) %increment %mstore_kernel_general(0) + DUP1 %mstore_kernel_general(1) + %increment %jump(loop) +loop_end: + // stack: i, updated_child_ptr, first_nibble, node_payload_ptr, retdest + POP + // stack: updated_child_ptr, first_nibble, node_payload_ptr, retdest + // If there's more than one non-empty child, simply update the branch node. + %mload_kernel_general(0) %gt_const(1) %jumpi(update_branch) + %mload_kernel_general(0) ISZERO %jumpi(panic) // This should never happen. + // Otherwise, transform the branch node into a leaf/extension node. + // stack: updated_child_ptr, first_nibble, node_payload_ptr, retdest + %mload_kernel_general(1) + // stack: i, updated_child_ptr, first_nibble, node_payload_ptr, retdest + DUP4 ADD %mload_trie_data + // stack: only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + DUP1 %mload_trie_data %eq_const(@MPT_NODE_BRANCH) %jumpi(maybe_normalize_branch_branch) + DUP1 %mload_trie_data %eq_const(@MPT_NODE_EXTENSION) %jumpi(maybe_normalize_branch_leafext) + DUP1 %mload_trie_data %eq_const(@MPT_NODE_LEAF) %jumpi(maybe_normalize_branch_leafext) + PANIC // This should never happen. + +// The only child of the branch node is a branch node. +// Transform the branch node into an extension node of length 1. +maybe_normalize_branch_branch: + // stack: only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + %get_trie_data_size // pointer to the extension node we're about to create + // stack: extension_ptr, only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + PUSH @MPT_NODE_EXTENSION %append_to_trie_data + // stack: extension_ptr, only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + PUSH 1 %append_to_trie_data // Append node_len to our node + // stack: extension_ptr, only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + %mload_kernel_general(1) %append_to_trie_data // Append node_key to our node + // stack: extension_ptr, only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + SWAP1 %append_to_trie_data // Append updated_child_node_ptr to our node + %stack (extension_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest) -> (retdest, extension_ptr) + JUMP + +// The only child of the branch node is a leaf/extension node. +// Transform the branch node into an leaf/extension node of length 1+len(child). +maybe_normalize_branch_leafext: + // stack: only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + DUP1 %mload_trie_data + // stack: child_type, only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + DUP2 %increment %mload_trie_data + // stack: child_len, child_type, only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + DUP3 %add_const(2) %mload_trie_data + // stack: child_key, child_len, child_type, only_child_ptr, updated_child_ptr, first_nibble, node_payload_ptr, retdest + SWAP3 %add_const(3) %mload_trie_data + // stack: child_value_ptr, child_len, child_type, child_key, updated_child_ptr, first_nibble, node_payload_ptr, retdest + %mload_kernel_general(1) + %stack (i, child_value_ptr, child_len, child_type, child_key, updated_child_ptr, first_nibble, node_payload_ptr) -> + (1, i, child_len, child_key, child_type, child_value_ptr) + %merge_nibbles + // stack: len, key, child_type, value_ptr, retdest + %get_trie_data_size // pointer to the leaf/extension node we're about to create + // stack: node_ptr, len, key, child_type, value_ptr, retdest + SWAP3 + // stack: child_type, len, key, node_ptr, value_ptr, retdest + %append_to_trie_data // Append type to our node + // stack: len, key, node_ptr, value_ptr, retdest + %append_to_trie_data // Append len to our node + // stack: key, node_ptr, value_ptr, retdest + %append_to_trie_data // Append key to our node + // stack: node_ptr, value_ptr, retdest + SWAP1 %append_to_trie_data // Append value_ptr to our node + // stack: node_ptr, retdest + SWAP1 JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/delete/delete_extension.asm b/evm/src/cpu/kernel/asm/mpt/delete/delete_extension.asm new file mode 100644 index 00000000..40fb31ee --- /dev/null +++ b/evm/src/cpu/kernel/asm/mpt/delete/delete_extension.asm @@ -0,0 +1,99 @@ +// Delete from an extension node. +// Algorithm is roughly: +// - Let `k = length(node)` +// - Delete `(num_nibbles-k, key[k:])` from `node.child`. +// - If the returned child node is a branch node, the current node is replaced with an extension node with updated child. +// - If the returned child node is an extension node, we merge the two extension nodes into one extension node. +// - If the returned child node is a leaf node, we merge the two nodes into one leaf node. +global mpt_delete_extension: + // stack: node_type, node_payload_ptr, num_nibbles, key, retdest + POP + // stack: node_payload_ptr, num_nibbles, key, retdest + DUP1 %mload_trie_data + // stack: node_len, node_payload_ptr, num_nibbles, key, retdest + DUP2 %increment %mload_trie_data + %stack (node_key, node_len, node_payload_ptr, num_nibbles, key, retdest) -> + (node_len, num_nibbles, key, node_payload_ptr, node_len, node_key, retdest) + %truncate_nibbles + // stack: num_nibbles, key, node_payload_ptr, node_len, node_key, retdest + SWAP2 + // stack: node_payload_ptr, key, num_nibbles, node_len, node_key, retdest + %add_const(2) %mload_trie_data + %stack (node_child_ptr, key, num_nibbles, node_len, node_key, retdest) -> + (node_child_ptr, num_nibbles, key, after_mpt_delete_extension, node_len, node_key, retdest) + %jump(mpt_delete) + +after_mpt_delete_extension: + // stack: updated_child_node_ptr, node_len, node_key, retdest + DUP1 %mload_trie_data + // stack: child_type, updated_child_node_ptr, node_len, node_key, retdest + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(after_mpt_delete_extension_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(after_mpt_delete_extension_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(after_mpt_delete_extension_leaf) + %eq_const(@MPT_NODE_EMPTY) %jumpi(panic) // This should never happen. + PANIC + +after_mpt_delete_extension_branch: + // stack: child_type, updated_child_node_ptr, node_len, node_key, retdest + POP + // stack: updated_child_node_ptr, node_len, node_key, retdest + %get_trie_data_size // pointer to the extension node we're about to create + // stack: extension_ptr, updated_child_node_ptr, node_len, node_key, retdest + PUSH @MPT_NODE_EXTENSION %append_to_trie_data + // stack: extension_ptr, updated_child_node_ptr, node_len, node_key, retdest + SWAP2 %append_to_trie_data // Append node_len to our node + // stack: updated_child_node_ptr, extension_ptr, node_key, retdest + SWAP2 %append_to_trie_data // Append node_key to our node + // stack: extension_ptr, updated_child_node_ptr, retdest + SWAP1 %append_to_trie_data // Append updated_child_node_ptr to our node + // stack: extension_ptr, retdest + SWAP1 JUMP + +after_mpt_delete_extension_extension: + // stack: child_type, updated_child_node_ptr, node_len, node_key, retdest + POP + // stack: updated_child_node_ptr, node_len, node_key, retdest + DUP1 %increment %mload_trie_data + // stack: child_len, updated_child_node_ptr, node_len, node_key, retdest + DUP2 %add_const(2) %mload_trie_data + // stack: child_key, child_len, updated_child_node_ptr, node_len, node_key, retdest + SWAP2 %add_const(3) %mload_trie_data + %stack (grandchild_ptr, child_len, child_key, node_len, node_key) -> (node_len, node_key, child_len, child_key, grandchild_ptr) + %merge_nibbles + // stack: len, key, grandchild_ptr, retdest + %get_trie_data_size // pointer to the extension node we're about to create + // stack: extension_ptr, len, key, grandchild_ptr, retdest + PUSH @MPT_NODE_EXTENSION %append_to_trie_data + // stack: extension_ptr, len, key, grandchild_ptr, retdest + SWAP1 %append_to_trie_data // Append len to our node + // stack: extension_ptr, key, grandchild_ptr, retdest + SWAP1 %append_to_trie_data // Append key to our node + // stack: extension_ptr, grandchild_ptr, retdest + SWAP1 %append_to_trie_data // Append grandchild_ptr to our node + // stack: extension_ptr, retdest + SWAP1 JUMP + +// Essentially the same as `after_mpt_delete_extension_extension`. TODO: Could merge in a macro or common function. +after_mpt_delete_extension_leaf: + // stack: child_type, updated_child_node_ptr, node_len, node_key, retdest + POP + // stack: updated_child_node_ptr, node_len, node_key, retdest + DUP1 %increment %mload_trie_data + // stack: child_len, updated_child_node_ptr, node_len, node_key, retdest + DUP2 %add_const(2) %mload_trie_data + // stack: child_key, child_len, updated_child_node_ptr, node_len, node_key, retdest + SWAP2 %add_const(3) %mload_trie_data + %stack (value_ptr, child_len, child_key, node_len, node_key) -> (node_len, node_key, child_len, child_key, value_ptr) + %merge_nibbles + // stack: len, key, value_ptr, retdest + %get_trie_data_size // pointer to the leaf node we're about to create + // stack: leaf_ptr, len, key, value_ptr, retdest + PUSH @MPT_NODE_LEAF %append_to_trie_data + // stack: leaf_ptr, len, key, value_ptr, retdest + SWAP1 %append_to_trie_data // Append len to our node + // stack: leaf_ptr, key, value_ptr, retdest + SWAP1 %append_to_trie_data // Append key to our node + // stack: leaf_ptr, value_ptr, retdest + SWAP1 %append_to_trie_data // Append value_ptr to our node + // stack: leaf_ptr, retdest + SWAP1 JUMP diff --git a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm index 109a5577..12066894 100644 --- a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm @@ -88,8 +88,9 @@ sstore_after_refund: %stack (kexit_info, current_value, slot, value) -> (value, current_value, slot, value, kexit_info) EQ %jumpi(sstore_noop) - // TODO: If value = 0, delete the key instead of inserting 0. + // If the value is zero, delete the slot from the storage trie. // stack: slot, value, kexit_info + DUP2 ISZERO %jumpi(sstore_delete) // First we write the value to MPT data, and get a pointer to it. %get_trie_data_size @@ -136,3 +137,16 @@ sstore_noop: // stack: slot, value, kexit_info %pop2 EXIT_KERNEL + +// Delete the slot from the storage trie. +sstore_delete: + // stack: slot, value, kexit_info + SWAP1 POP + PUSH after_storage_insert SWAP1 + // stack: slot, after_storage_insert, kexit_info + %slot_to_storage_key + // stack: storage_key, after_storage_insert, kexit_info + PUSH 64 // storage_key has 64 nibbles + %current_storage_trie + // stack: storage_root_ptr, 64, storage_key, after_storage_insert, kexit_info + %jump(mpt_delete) diff --git a/evm/src/cpu/kernel/asm/mpt/util.asm b/evm/src/cpu/kernel/asm/mpt/util.asm index c7662c41..80e5c6f7 100644 --- a/evm/src/cpu/kernel/asm/mpt/util.asm +++ b/evm/src/cpu/kernel/asm/mpt/util.asm @@ -90,6 +90,26 @@ // stack: first_nibble, num_nibbles, key %endmacro +// Remove the first `k` nibbles from a key part. +// def truncate_nibbles(k, num_nibbles, key): +// num_nibbles -= k +// num_nibbles_x4 = num_nibbles * 4 +// lead_nibbles = key >> num_nibbles_x4 +// key -= (lead_nibbles << num_nibbles_x4) +// return (num_nibbles, key) +%macro truncate_nibbles + // stack: k, num_nibbles, key + SWAP1 SUB + // stack: num_nibbles, key + DUP1 %mul_const(4) + %stack (num_nibbles_x4, num_nibbles, key) -> (num_nibbles_x4, key, num_nibbles_x4, num_nibbles, key) + SHR + %stack (lead_nibbles, num_nibbles_x4, num_nibbles, key) -> (num_nibbles_x4, lead_nibbles, key, num_nibbles) + SHL SWAP1 SUB + // stack: key, num_nibbles + SWAP1 +%endmacro + // Split off the common prefix among two key parts. // // Pre stack: len_1, key_1, len_2, key_2 @@ -183,6 +203,17 @@ // stack: len_common, key_common, len_1, key_1, len_2, key_2 %endmacro +// Remove the first `k` nibbles from a key part. +// def merge_nibbles(front_len, front_key, back_len, back_key): +// return (front_len + back_len, (front_key<<(back_len*4)) + back_key) +%macro merge_nibbles + // stack: front_len, front_key, back_len, back_key + %stack (front_len, front_key, back_len, back_key) -> (back_len, front_key, back_key, back_len, front_len) + %mul_const(4) SHL ADD + // stack: new_key, back_len, front_len + SWAP2 ADD +%endmacro + // Computes state_key = Keccak256(addr). Clobbers @SEGMENT_KERNEL_GENERAL. %macro addr_to_state_key %keccak256_word(20) diff --git a/evm/src/cpu/kernel/tests/mpt/delete.rs b/evm/src/cpu/kernel/tests/mpt/delete.rs new file mode 100644 index 00000000..76f1a049 --- /dev/null +++ b/evm/src/cpu/kernel/tests/mpt/delete.rs @@ -0,0 +1,118 @@ +use anyhow::Result; +use eth_trie_utils::nibbles::Nibbles; +use eth_trie_utils::partial_trie::{HashedPartialTrie, PartialTrie}; +use ethereum_types::{BigEndianHash, H256}; + +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::{nibbles_64, test_account_1_rlp, test_account_2}; +use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; +use crate::generation::TrieInputs; +use crate::Node; + +#[test] +fn mpt_delete_empty() -> Result<()> { + test_state_trie(Default::default(), nibbles_64(0xABC), test_account_2()) +} + +#[test] +fn mpt_delete_leaf_nonoverlapping_keys() -> Result<()> { + let state_trie = Node::Leaf { + nibbles: nibbles_64(0xABC), + value: test_account_1_rlp(), + } + .into(); + test_state_trie(state_trie, nibbles_64(0x123), test_account_2()) +} + +#[test] +fn mpt_delete_leaf_overlapping_keys() -> Result<()> { + let state_trie = Node::Leaf { + nibbles: nibbles_64(0xABC), + value: test_account_1_rlp(), + } + .into(); + test_state_trie(state_trie, nibbles_64(0xADE), test_account_2()) +} + +/// 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: HashedPartialTrie, + k: Nibbles, + mut account: AccountRlp, +) -> Result<()> { + assert_eq!(k.count, 64); + + // Ignore any storage_root; see documentation note. + account.storage_root = HashedPartialTrie::from(Node::Empty).hash(); + + let trie_inputs = TrieInputs { + state_trie: state_trie.clone(), + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + let mpt_insert_state_trie = KERNEL.global_labels["mpt_insert_state_trie"]; + let mpt_delete = KERNEL.global_labels["mpt_delete"]; + let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; + + 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![]); + + // Next, execute mpt_insert_state_trie. + interpreter.generation_state.registers.program_counter = mpt_insert_state_trie; + let trie_data = interpreter.get_trie_data_mut(); + if trie_data.is_empty() { + // In the assembly we skip over 0, knowing trie_data[0] = 0 by default. + // Since we don't explicitly set it to 0, we need to do so here. + trie_data.push(0.into()); + } + let value_ptr = trie_data.len(); + 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()); + interpreter.push(value_ptr.into()); // value_ptr + interpreter.push(k.packed); // key + interpreter.run()?; + assert_eq!( + interpreter.stack().len(), + 0, + "Expected empty stack after insert, found {:?}", + interpreter.stack() + ); + + // Next, execute mpt_delete, deleting the account we just inserted. + let state_trie_ptr = interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot); + interpreter.generation_state.registers.program_counter = mpt_delete; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.push(k.packed); + interpreter.push(64.into()); + interpreter.push(state_trie_ptr); + interpreter.run()?; + let state_trie_ptr = interpreter.pop(); + interpreter.set_global_metadata_field(GlobalMetadata::StateTrieRoot, state_trie_ptr); + + // Now, execute mpt_hash_state_trie. + interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.run()?; + + let state_trie_hash = H256::from_uint(&interpreter.pop()); + let expected_state_trie_hash = state_trie.hash(); + assert_eq!(state_trie_hash, expected_state_trie_hash); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs index 638f1a4d..18982b94 100644 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm/src/cpu/kernel/tests/mpt/mod.rs @@ -5,6 +5,7 @@ use ethereum_types::{BigEndianHash, H256, U256}; use crate::generation::mpt::AccountRlp; use crate::Node; +mod delete; mod hash; mod hex_prefix; mod insert;