diff --git a/evm/Cargo.toml b/evm/Cargo.toml index e328aa0c97..3d217131f8 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -37,6 +37,7 @@ static_assertions = "1.1.0" hashbrown = { version = "0.14.0" } tiny-keccak = "2.0.2" serde_json = "1.0" +smt_utils = { git = "https://github.com/0xPolygonZero/smt_utils" } [target.'cfg(not(target_env = "msvc"))'.dependencies] jemallocator = "0.5.0" diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index bda2ab610e..522da72a25 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -110,7 +110,12 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/memory/packing.asm"), include_str!("asm/memory/syscalls.asm"), include_str!("asm/memory/txn_fields.asm"), - include_str!("asm/mpt/accounts.asm"), + include_str!("asm/smt/load.asm"), + include_str!("asm/smt/hash.asm"), + include_str!("asm/smt/insert.asm"), + include_str!("asm/smt/read.asm"), + include_str!("asm/smt/utils.asm"), + include_str!("asm/smt/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"), diff --git a/evm/src/cpu/kernel/asm/account_code.asm b/evm/src/cpu/kernel/asm/account_code.asm index 4dddc6dfd6..65b2962672 100644 --- a/evm/src/cpu/kernel/asm/account_code.asm +++ b/evm/src/cpu/kernel/asm/account_code.asm @@ -24,7 +24,7 @@ extcodehash_dead: global extcodehash: // stack: address, retdest - %mpt_read_state_trie + %smt_read_state // stack: account_ptr, retdest DUP1 ISZERO %jumpi(retzero) %add_const(3) diff --git a/evm/src/cpu/kernel/asm/balance.asm b/evm/src/cpu/kernel/asm/balance.asm index f175d027c9..1dc480b5f7 100644 --- a/evm/src/cpu/kernel/asm/balance.asm +++ b/evm/src/cpu/kernel/asm/balance.asm @@ -27,9 +27,9 @@ global sys_balance: global balance: // stack: address, retdest - %mpt_read_state_trie + %smt_read_state // stack: account_ptr, retdest - DUP1 ISZERO %jumpi(retzero) // If the account pointer is null, return 0. + // No need to consider the case where `account_ptr=0` because `trie_data[1]=0`. %add_const(1) // stack: balance_ptr, retdest %mload_trie_data diff --git a/evm/src/cpu/kernel/asm/core/nonce.asm b/evm/src/cpu/kernel/asm/core/nonce.asm index 48486be9e2..2a4796645c 100644 --- a/evm/src/cpu/kernel/asm/core/nonce.asm +++ b/evm/src/cpu/kernel/asm/core/nonce.asm @@ -3,7 +3,7 @@ // Post stack: (empty) global nonce: // stack: address, retdest - %mpt_read_state_trie + %smt_read_state // stack: account_ptr, retdest // The nonce is the first account field, so we deref the account pointer itself. // Note: We don't need to handle account_ptr=0, as trie_data[0] = 0, @@ -23,7 +23,7 @@ global nonce: global increment_nonce: // stack: address, retdest DUP1 - %mpt_read_state_trie + %smt_read_state // stack: account_ptr, address, retdest DUP1 ISZERO %jumpi(increment_nonce_no_such_account) // stack: nonce_ptr, address, retdest diff --git a/evm/src/cpu/kernel/asm/core/transfer.asm b/evm/src/cpu/kernel/asm/core/transfer.asm index 0517cf3a8f..05127061af 100644 --- a/evm/src/cpu/kernel/asm/core/transfer.asm +++ b/evm/src/cpu/kernel/asm/core/transfer.asm @@ -29,7 +29,7 @@ global transfer_eth_failure: global deduct_eth: // stack: addr, amount, retdest DUP1 %insert_touched_addresses - %mpt_read_state_trie + %smt_read_state // stack: account_ptr, amount, retdest DUP1 ISZERO %jumpi(deduct_eth_no_such_account) // If the account pointer is null, return 1. %add_const(1) @@ -65,7 +65,7 @@ global deduct_eth_insufficient_balance: global add_eth: // stack: addr, amount, retdest DUP1 %insert_touched_addresses - DUP1 %mpt_read_state_trie + DUP1 %smt_read_state // stack: account_ptr, addr, amount, retdest DUP1 ISZERO %jumpi(add_eth_new_account) // If the account pointer is null, we need to create the account. %add_const(1) @@ -90,6 +90,7 @@ global add_eth_new_account: // stack: new_account_ptr, addr, amount, retdest SWAP2 // stack: amount, addr, new_account_ptr, retdest + PUSH 0 %append_to_trie_data // key PUSH 0 %append_to_trie_data // nonce %append_to_trie_data // balance // stack: addr, new_account_ptr, retdest @@ -98,7 +99,7 @@ global add_eth_new_account: // stack: addr, new_account_ptr, retdest %addr_to_state_key // stack: key, new_account_ptr, retdest - %jump(mpt_insert_state_trie) + %jump(smt_insert_state) add_eth_new_account_zero: // stack: addr, amount, retdest diff --git a/evm/src/cpu/kernel/asm/core/util.asm b/evm/src/cpu/kernel/asm/core/util.asm index ee33ff26ca..a186520cd7 100644 --- a/evm/src/cpu/kernel/asm/core/util.asm +++ b/evm/src/cpu/kernel/asm/core/util.asm @@ -40,7 +40,7 @@ // Returns 1 if the account is empty, 0 otherwise. %macro is_empty // stack: addr - %mpt_read_state_trie + %smt_read_state // stack: account_ptr DUP1 ISZERO %jumpi(%%false) // stack: account_ptr diff --git a/evm/src/cpu/kernel/asm/main.asm b/evm/src/cpu/kernel/asm/main.asm index bd555218be..24ed0a43d9 100644 --- a/evm/src/cpu/kernel/asm/main.asm +++ b/evm/src/cpu/kernel/asm/main.asm @@ -10,7 +10,7 @@ global main: %jump(load_all_mpts) global hash_initial_tries: - %mpt_hash_state_trie %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) %assert_eq + %smt_hash_state %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) %assert_eq %mpt_hash_txn_trie %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE) %assert_eq %mpt_hash_receipt_trie %mload_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_BEFORE) %assert_eq @@ -55,7 +55,7 @@ global hash_final_tries: DUP3 %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_AFTER) %assert_eq %pop3 %check_metadata_block_bloom - %mpt_hash_state_trie %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER) %assert_eq + %smt_hash_state %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER) %assert_eq %mpt_hash_txn_trie %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_AFTER) %assert_eq %mpt_hash_receipt_trie %mload_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER) %assert_eq %jump(halt) diff --git a/evm/src/cpu/kernel/asm/mpt/load/load.asm b/evm/src/cpu/kernel/asm/mpt/load/load.asm index d787074b4f..fd378685e4 100644 --- a/evm/src/cpu/kernel/asm/mpt/load/load.asm +++ b/evm/src/cpu/kernel/asm/mpt/load/load.asm @@ -1,12 +1,8 @@ // Load all partial trie data from prover inputs. global load_all_mpts: // stack: retdest - // First set @GLOBAL_METADATA_TRIE_DATA_SIZE = 1. - // We don't want it to start at 0, as we use 0 as a null pointer. - PUSH 1 - %set_trie_data_size - %load_mpt(mpt_load_state_trie_value) %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %load_state_smt %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) diff --git a/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm index 84d8d0efc9..7b889120a0 100644 --- a/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_read.asm @@ -8,10 +8,9 @@ global sload_current: %stack (slot) -> (slot, after_storage_read) %slot_to_storage_key // stack: storage_key, after_storage_read - PUSH 64 // storage_key has 64 nibbles - %current_storage_trie - // stack: storage_root_ptr, 64, storage_key, after_storage_read - %jump(mpt_read) + %current_storage_smt + // stack: storage_root_ptr, storage_key, after_storage_read + %jump(smt_read) global after_storage_read: // stack: value_ptr, retdest 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 08270dfa9e..5caafb3b5f 100644 --- a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm @@ -1,4 +1,4 @@ -// Write a word to the current account's storage trie. +// Write a word to the current account's storage SMT. // // Pre stack: kexit_info, slot, value // Post stack: (empty) @@ -92,26 +92,27 @@ sstore_after_refund: DUP2 %address %journal_add_storage_change // stack: slot, value, kexit_info - // If the value is zero, delete the slot from the storage trie. + // If the value is zero, delete the slot from the storage SMT. // stack: slot, value, kexit_info DUP2 ISZERO %jumpi(sstore_delete) - // First we write the value to MPT data, and get a pointer to it. + // First we write the value to SMT data, and get a pointer to it. %get_trie_data_size // stack: value_ptr, slot, value, kexit_info + PUSH 0 %append_to_trie_data // For the key. + // stack: value_ptr, slot, value, kexit_info SWAP2 // stack: value, slot, value_ptr, kexit_info %append_to_trie_data // stack: slot, value_ptr, kexit_info - // Next, call mpt_insert on the current account's storage root. + // Next, call smt_insert on the current account's storage root. %stack (slot, value_ptr) -> (slot, value_ptr, after_storage_insert) %slot_to_storage_key // stack: storage_key, value_ptr, after_storage_insert, kexit_info - PUSH 64 // storage_key has 64 nibbles - %current_storage_trie - // stack: storage_root_ptr, 64, storage_key, value_ptr, after_storage_insert, kexit_info - %jump(mpt_insert) + %current_storage_smt + // stack: storage_root_ptr, storage_key, value_ptr, after_storage_insert, kexit_info + %jump(smt_insert) after_storage_insert: // stack: new_storage_root_ptr, kexit_info @@ -130,8 +131,9 @@ sstore_noop: %pop3 EXIT_KERNEL -// Delete the slot from the storage trie. +// Delete the slot from the storage SMT. sstore_delete: + PANIC // TODO: Not implemented for SMT. // stack: slot, value, kexit_info SWAP1 POP PUSH after_storage_insert SWAP1 @@ -139,6 +141,6 @@ sstore_delete: %slot_to_storage_key // stack: storage_key, after_storage_insert, kexit_info PUSH 64 // storage_key has 64 nibbles - %current_storage_trie + %current_storage_smt // stack: storage_root_ptr, 64, storage_key, after_storage_insert, kexit_info %jump(mpt_delete) diff --git a/evm/src/cpu/kernel/asm/mpt/accounts.asm b/evm/src/cpu/kernel/asm/smt/accounts.asm similarity index 91% rename from evm/src/cpu/kernel/asm/mpt/accounts.asm rename to evm/src/cpu/kernel/asm/smt/accounts.asm index 0ee987b4c1..439e18783b 100644 --- a/evm/src/cpu/kernel/asm/mpt/accounts.asm +++ b/evm/src/cpu/kernel/asm/smt/accounts.asm @@ -1,6 +1,6 @@ // Return a pointer to the current account's data in the state trie. %macro current_account_data - %address %mpt_read_state_trie + %address %smt_read_state // stack: account_ptr // account_ptr should be non-null as long as the prover provided the proper // Merkle data. But a bad prover may not have, and we don't want return a @@ -10,7 +10,7 @@ %endmacro // Returns a pointer to the root of the storage trie associated with the current account. -%macro current_storage_trie +%macro current_storage_smt // stack: (empty) %current_account_data // stack: account_ptr diff --git a/evm/src/cpu/kernel/asm/smt/hash.asm b/evm/src/cpu/kernel/asm/smt/hash.asm new file mode 100644 index 0000000000..5ac4fe3c6e --- /dev/null +++ b/evm/src/cpu/kernel/asm/smt/hash.asm @@ -0,0 +1,172 @@ +%macro smt_hash_state + PUSH %%after %jump(smt_hash_state) +%%after: +%endmacro + +// Root hash of the state SMT. +global smt_hash_state: + // stack: retdest + PUSH 0 %mstore_kernel_general(@SMT_IS_STORAGE) // is_storage flag. + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + +// Root hash of SMT stored at `trie_data[ptr]`. +// Pseudocode: +// ``` +// hash( HashNode { h } ) = h +// hash( InternalNode { left, right } ) = keccak(1 || hash(left) || hash(right)) // TODO: Domain separation in capacity when using Poseidon. See https://github.com/0xPolygonZero/plonky2/pull/1315#discussion_r1374780333. +// hash( Leaf { key, val_hash } ) = keccak(0 || key || val_hash) // TODO: Domain separation in capacity when using Poseidon. +// ``` +// where `val_hash` is `keccak(nonce || balance || storage_root || code_hash)` for accounts and +// `val` for a storage value. +global smt_hash: + // stack: ptr, retdest + DUP1 + %mload_trie_data + // stack: node, node_ptr, retdest + DUP1 %eq_const(@SMT_NODE_HASH) %jumpi(smt_hash_hash) + DUP1 %eq_const(@SMT_NODE_INTERNAL) %jumpi(smt_hash_internal) + DUP1 %eq_const(@SMT_NODE_LEAF) %jumpi(smt_hash_leaf) +smt_hash_unknown_node_type: + PANIC + +smt_hash_hash: + // stack: node, node_ptr, retdest + POP + // stack: node_ptr, retdest + %increment + // stack: node_ptr+1, retdest + %mload_trie_data + // stack: hash, retdest + SWAP1 JUMP + +smt_hash_internal: + // stack: node, node_ptr, retdest + POP + // stack: node_ptr, retdest + %increment + // stack: node_ptr+1, retdest + DUP1 + %mload_trie_data + %stack (left_child_ptr, node_ptr_plus_1, retdest) -> (left_child_ptr, smt_hash_internal_after_left, node_ptr_plus_1, retdest) + %jump(smt_hash) +smt_hash_internal_after_left: + // stack: left_hash, node_ptr+1, retdest + SWAP1 %increment + // stack: node_ptr+2, left_hash, retdest + %mload_trie_data + %stack (right_child_ptr, left_hash, retdest) -> (right_child_ptr, smt_hash_internal_after_right, left_hash, retdest) + %jump(smt_hash) +smt_hash_internal_after_right: + // stack: right_hash, left_hash, retdest + %stack (right_hash) -> (0, @SEGMENT_KERNEL_GENERAL, 33, right_hash, 32) + %mstore_unpacking POP + %stack (left_hash) -> (0, @SEGMENT_KERNEL_GENERAL, 1, left_hash, 32) + %mstore_unpacking POP + // stack: retdest + // Internal node flag. + PUSH 1 %mstore_kernel_general(0) + %stack () -> (0, @SEGMENT_KERNEL_GENERAL, 0, 65) + KECCAK_GENERAL + // stack: hash, retdest + SWAP1 JUMP + +smt_hash_leaf: + // stack: node, node_ptr, retdest + POP + // stack: node_ptr, retdest + %increment + // stack: node_ptr+1, retdest + %mload_trie_data + // stack: payload_ptr, retdest + %mload_kernel_general(@SMT_IS_STORAGE) + // stack: is_value, payload_ptr, retdest + %jumpi(smt_hash_leaf_value) +smt_hash_leaf_account: + // stack: payload_ptr, retdest + DUP1 %mload_trie_data + // stack: key, payload_ptr, retdest + SWAP1 %increment + // stack: payload_ptr+1, key, retdest + DUP1 %mload_trie_data + // stack: nonce, payload_ptr+1, key, retdest + SWAP1 + // stack: payload_ptr+1, nonce, key, retdest + %increment + // stack: payload_ptr+2, nonce, key, retdest + DUP1 %mload_trie_data + // stack: balance, payload_ptr+2, nonce, key, retdest + SWAP1 + // stack: payload_ptr+2, balance, nonce, key, retdest + %increment + // stack: payload_ptr+3, balance, nonce, key, retdest + DUP1 %mload_trie_data + // stack: storage_root, payload_ptr+3, balance, nonce, key, retdest + PUSH 1 %mstore_kernel_general(@SMT_IS_STORAGE) + %stack (storage_root) -> (storage_root, smt_hash_leaf_account_after_storage) + %jump(smt_hash) +smt_hash_leaf_account_after_storage: + PUSH 0 %mstore_kernel_general(@SMT_IS_STORAGE) + // stack: storage_root_hash, payload_ptr+3, balance, nonce, key, retdest + SWAP1 + // stack: payload_ptr+3, storage_root_hash, balance, nonce, key, retdest + %increment + // stack: payload_ptr+4, storage_root_hash, balance, nonce, key, retdest + %mload_trie_data + // stack: code_hash, storage_root_hash, balance, nonce, key, retdest + + // 0----7 | 8----39 | 40--------71 | 72----103 + // nonce | balance | storage_root | code_hash + + // TODO: The way we do the `mstore_unpacking`s could be optimized. See https://github.com/0xPolygonZero/plonky2/pull/1315#discussion_r1378207927. + %stack (code_hash) -> (0, @SEGMENT_KERNEL_GENERAL, 72, code_hash, 32) + %mstore_unpacking POP + + %stack (storage_root) -> (0, @SEGMENT_KERNEL_GENERAL, 40, storage_root, 32) + %mstore_unpacking POP + + %stack (balance) -> (0, @SEGMENT_KERNEL_GENERAL, 8, balance, 32) + %mstore_unpacking POP + + %stack (nonce) -> (0, @SEGMENT_KERNEL_GENERAL, 0, nonce) + %mstore_unpacking_u64_LE + + // stack: key, retdest + %stack () -> (0, @SEGMENT_KERNEL_GENERAL, 0, 104) + KECCAK_GENERAL + // stack: hash, key, retdest + + // Leaf flag + PUSH 0 %mstore_kernel_general(0) + + %stack (hash) -> (0, @SEGMENT_KERNEL_GENERAL, 33, hash, 32) + %mstore_unpacking POP + + %stack (key) -> (0, @SEGMENT_KERNEL_GENERAL, 1, key, 32) + %mstore_unpacking POP + + %stack () -> (0, @SEGMENT_KERNEL_GENERAL, 0, 65) + KECCAK_GENERAL + + SWAP1 JUMP + +smt_hash_leaf_value: + // stack: payload_ptr, retdest + DUP1 %mload_trie_data + // stack: key, payload_ptr, retdest + SWAP1 + // stack: payload_ptr, key, retdest + %increment + // stack: payload_ptr+1, key, retdest + %mload_trie_data + // stack: value, key, retdest + PUSH 0 %mstore_kernel_general(0) + %stack (value) -> (0, @SEGMENT_KERNEL_GENERAL, 33, value, 32) + %mstore_unpacking POP + // stack: key, retdest + %stack (key) -> (0, @SEGMENT_KERNEL_GENERAL, 1, key, 32) + %mstore_unpacking POP + // stack: retdest + %stack () -> (0, @SEGMENT_KERNEL_GENERAL, 0, 65) + KECCAK_GENERAL + // stack: hash, retdest + SWAP1 JUMP diff --git a/evm/src/cpu/kernel/asm/smt/insert.asm b/evm/src/cpu/kernel/asm/smt/insert.asm new file mode 100644 index 0000000000..32b248c66a --- /dev/null +++ b/evm/src/cpu/kernel/asm/smt/insert.asm @@ -0,0 +1,109 @@ +// Insert a key-value pair in the state SMT. +global smt_insert_state: + // stack: key, new_account_ptr, retdest + %stack (key, new_account_ptr) -> (key, new_account_ptr, smt_insert_state_set_root) + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + // stack: root_ptr, key, new_account_ptr, smt_insert_state_set_root, retdest + %jump(smt_insert) + +smt_insert_state_set_root: + // stack: root_ptr, retdest + %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + // stack: retdest + JUMP + +// Insert a key-value pair in the SMT at `trie_data[node_ptr]`. +// `value_ptr` should point to a an empty slot reserved for `rem_key`, followed by the actual value. +// Pseudocode: +// ``` +// insert( HashNode { h }, key, value_ptr ) = if h == 0 then Leaf { key, value_ptr } else PANIC +// insert( InternalNode { left, right }, key, value_ptr ) = if key&1 { insert( right, key>>1, value_ptr ) } else { insert( left, key>>1, value_ptr ) } +// insert( Leaf { key', value_ptr' }, key, value_ptr ) = { +// let internal = new InternalNode; +// insert(internal, key', value_ptr'); +// insert(internal, key, value_ptr); +// return internal;} +// ``` +global smt_insert: + // stack: node_ptr, key, value_ptr, retdest + DUP1 %mload_trie_data + // stack: node_type, node_ptr, key, value_ptr, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %increment SWAP1 + // stack: node_type, node_payload_ptr, key, value_ptr, retdest + + DUP1 %eq_const(@SMT_NODE_HASH) %jumpi(smt_insert_hash) + DUP1 %eq_const(@SMT_NODE_INTERNAL) %jumpi(smt_insert_internal) + DUP1 %eq_const(@SMT_NODE_LEAF) %jumpi(smt_insert_leaf) + PANIC + +smt_insert_hash: + // stack: node_type, node_payload_ptr, key, value_ptr, retdest + POP + // stack: node_payload_ptr, key, value_ptr, retdest + %mload_trie_data + // stack: hash, key, value_ptr, retdest + ISZERO %jumpi(smt_insert_empty) + PANIC // Trying to insert in a non-empty hash node. +smt_insert_empty: + // stack: key, value_ptr, retdest + %get_trie_data_size + // stack: index, key, value_ptr, retdest + PUSH @SMT_NODE_LEAF %append_to_trie_data + %stack (index, key, value_ptr) -> (value_ptr, key, value_ptr, index) + %mstore_trie_data + // stack: value_ptr, index, retdest + %append_to_trie_data + // stack: index, retdest + SWAP1 JUMP + +smt_insert_internal: + // stack: node_type, node_payload_ptr, key, value_ptr, retdest + POP + // stack: node_payload_ptr, key, value_ptr, retdest + SWAP1 + // stack: key, node_payload_ptr, value_ptr, retdest + %pop_bit + %stack (bit, key, node_payload_ptr, value_ptr, retdest) -> (bit, node_payload_ptr, node_payload_ptr, key, value_ptr, smt_insert_internal_after, retdest) + ADD + // stack: child_ptr_ptr, node_payload_ptr, key, value_ptr, smt_insert_internal_after, retdest + DUP1 %mload_trie_data + %stack (child_ptr, child_ptr_ptr, node_payload_ptr, key, value_ptr, smt_insert_internal_after) -> (child_ptr, key, value_ptr, smt_insert_internal_after, child_ptr_ptr, node_payload_ptr) + %jump(smt_insert) + +smt_insert_internal_after: + // stack: new_node_ptr, child_ptr_ptr, node_payload_ptr, retdest + SWAP1 %mstore_trie_data + // stack: node_payload_ptr retdest + %decrement + SWAP1 JUMP + +smt_insert_leaf: + // stack: node_type, node_payload_ptr_ptr, key, value_ptr, retdest + POP + %stack (node_payload_ptr_ptr, key) -> (node_payload_ptr_ptr, key, node_payload_ptr_ptr, key) + %mload_trie_data %mload_trie_data EQ %jumpi(smt_insert_leaf_same_key) + // stack: node_payload_ptr_ptr, key, value_ptr, retdest + // We create an internal node with two empty children, and then we insert the two leaves. + %get_trie_data_size + // stack: index, node_payload_ptr_ptr, key, value_ptr, retdest + PUSH @SMT_NODE_INTERNAL %append_to_trie_data + PUSH 0 %append_to_trie_data // Empty hash node + PUSH 0 %append_to_trie_data // Empty hash node + %stack (index, node_payload_ptr_ptr, key, value_ptr) -> (index, key, value_ptr, after_first_leaf, node_payload_ptr_ptr) + %jump(smt_insert) +after_first_leaf: + // stack: internal_ptr, node_payload_ptr_ptr, retdest + SWAP1 + // stack: node_payload_ptr_ptr, internal_ptr, retdest + %mload_trie_data DUP1 %mload_trie_data + %stack (key, node_payload_ptr, internal_ptr) -> (internal_ptr, key, node_payload_ptr, after_second_leaf) + %jump(smt_insert) +after_second_leaf: + // stack: internal_ptr, retdest + SWAP1 JUMP + + +smt_insert_leaf_same_key: + // stack: node_payload_ptr, key, value_ptr, retdest + PANIC // Not sure if this should happen. diff --git a/evm/src/cpu/kernel/asm/smt/load.asm b/evm/src/cpu/kernel/asm/smt/load.asm new file mode 100644 index 0000000000..72a469f139 --- /dev/null +++ b/evm/src/cpu/kernel/asm/smt/load.asm @@ -0,0 +1,35 @@ +%macro load_state_smt + PUSH %%after %jump(load_state_smt) +%%after: +%endmacro + +// Simply copy the serialized state SMT to `TrieData`. +// First entry is the length of the serialized data. +global load_state_smt: + // stack: retdest + PROVER_INPUT(smt::state) + // stack: len, retdest + %get_trie_data_size + // stack: i, len, retdest + DUP2 %mstore_global_metadata(@GLOBAL_METADATA_TRIE_DATA_SIZE) + // stack: i, len, retdest + DUP1 %add_const(2) // First two entries are [0,0] for an empty hash node. + %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + // stack: i, len, retdest + %stack (i, len) -> (len, i, i) + ADD SWAP1 + // stack: i, len, retdest +loop: + // stack: i, len, retdest + DUP2 DUP2 EQ %jumpi(loop_end) + // stack: i, len, retdest + PROVER_INPUT(smt::state) + DUP2 + // stack: i, x, i, len, retdest + %mstore_trie_data + // stack: i, len, retdest + %increment + %jump(loop) +loop_end: + // stack: i, len, retdest + %pop2 JUMP diff --git a/evm/src/cpu/kernel/asm/smt/read.asm b/evm/src/cpu/kernel/asm/smt/read.asm new file mode 100644 index 0000000000..58ae5690cb --- /dev/null +++ b/evm/src/cpu/kernel/asm/smt/read.asm @@ -0,0 +1,81 @@ +// Given an address, return a pointer to the associated account data, which +// consists of four words (nonce, balance, storage_root_ptr, code_hash), in the +// state SMT. Returns null if the address is not found. +global smt_read_state: + // stack: addr, retdest + %addr_to_state_key + // stack: key, retdest + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) // node_ptr + // stack: node_ptr, key, retdest + %jump(smt_read) + +// Convenience macro to call smt_read_state and return where we left off. +%macro smt_read_state + %stack (addr) -> (addr, %%after) + %jump(smt_read_state) +%%after: +%endmacro + +// Return the data at the given key in the SMT at `trie_data[node_ptr]`. +// Pseudocode: +// ``` +// read( HashNode { h }, key ) = if h == 0 then 0 else PANIC +// read( InternalNode { left, right }, key ) = if key&1 { read( right, key>>1 ) } else { read( left, key>>1 ) } +// read( Leaf { key', value_ptr }, key ) = if key == key' then value_ptr' else 0 +// ``` +global smt_read: + // stack: node_ptr, key, retdest + DUP1 %mload_trie_data + // stack: node_type, node_ptr, 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, key, retdest + + DUP1 %eq_const(@SMT_NODE_HASH) %jumpi(smt_read_hash) + DUP1 %eq_const(@SMT_NODE_INTERNAL) %jumpi(smt_read_internal) + DUP1 %eq_const(@SMT_NODE_LEAF) %jumpi(smt_read_leaf) + PANIC + +smt_read_hash: + // stack: node_type, node_payload_ptr, key, retdest + POP + // stack: node_payload_ptr, key, retdest + %mload_trie_data + // stack: hash, key, retdest + ISZERO %jumpi(smt_read_empty) + PANIC // Trying to read a non-empty hash node. Should never happen. + +smt_read_empty: + %stack (key, retdest) -> (retdest, 0) + JUMP + +smt_read_internal: + // stack: node_type, node_payload_ptr, key, retdest + POP + // stack: node_payload_ptr, key, retdest + SWAP1 + // stack: key, node_payload_ptr, retdest + %pop_bit + %stack (bit, key, node_payload_ptr) -> (bit, node_payload_ptr, key) + ADD + // stack: child_ptr_ptr, key, retdest + %mload_trie_data + %jump(smt_read) + +smt_read_leaf: + // stack: node_type, node_payload_ptr_ptr, key, retdest + POP + // stack: node_payload_ptr_ptr, key, retdest + %mload_trie_data + %stack (node_payload_ptr, key) -> (node_payload_ptr, key, node_payload_ptr) + %mload_trie_data EQ %jumpi(smt_read_existing_leaf) // Checking if the key exists +smt_read_non_existing_leaf: + %stack (node_payload_ptr_ptr, retdest) -> (retdest, 0) + JUMP + +smt_read_existing_leaf: + // stack: node_payload_ptr, retdest + %increment // We want to point to the account values, not the key. + SWAP1 JUMP + + diff --git a/evm/src/cpu/kernel/asm/smt/utils.asm b/evm/src/cpu/kernel/asm/smt/utils.asm new file mode 100644 index 0000000000..145f61310a --- /dev/null +++ b/evm/src/cpu/kernel/asm/smt/utils.asm @@ -0,0 +1,7 @@ +%macro pop_bit + // stack: key + DUP1 %shr_const(1) + // stack: key>>1, key + SWAP1 %and_const(1) + // stack: key&1, key>>1 +%endmacro diff --git a/evm/src/cpu/kernel/constants/mod.rs b/evm/src/cpu/kernel/constants/mod.rs index 77abde994f..99e6404440 100644 --- a/evm/src/cpu/kernel/constants/mod.rs +++ b/evm/src/cpu/kernel/constants/mod.rs @@ -6,6 +6,7 @@ use hex_literal::hex; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::journal_entry::JournalEntry; +use crate::cpu::kernel::constants::smt_type::PartialSmtType; use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField; use crate::memory::segments::Segment; @@ -14,6 +15,7 @@ pub(crate) mod context_metadata; mod exc_bitfields; pub(crate) mod global_metadata; pub(crate) mod journal_entry; +pub(crate) mod smt_type; pub(crate) mod trie_type; pub(crate) mod txn_fields; @@ -57,6 +59,8 @@ pub fn evm_constants() -> HashMap { c.insert(MAX_NONCE.0.into(), U256::from(MAX_NONCE.1)); c.insert(CALL_STACK_LIMIT.0.into(), U256::from(CALL_STACK_LIMIT.1)); + c.insert(SMT_IS_STORAGE.0.into(), U256::from(SMT_IS_STORAGE.1)); + for segment in Segment::all() { c.insert(segment.var_name().into(), (segment as u32).into()); } @@ -72,6 +76,9 @@ pub fn evm_constants() -> HashMap { for trie_type in PartialTrieType::all() { c.insert(trie_type.var_name().into(), (trie_type as u32).into()); } + for trie_type in PartialSmtType::all() { + c.insert(trie_type.var_name().into(), (trie_type as u32).into()); + } for entry in JournalEntry::all() { c.insert(entry.var_name().into(), (entry as u32).into()); } @@ -270,3 +277,6 @@ const CODE_SIZE_LIMIT: [(&str, u64); 3] = [ const MAX_NONCE: (&str, u64) = ("MAX_NONCE", 0xffffffffffffffff); const CALL_STACK_LIMIT: (&str, u64) = ("CALL_STACK_LIMIT", 1024); + +// Holds a flag that is set to 1 when hashing storage SMTs. Used in `smt_hash`. +const SMT_IS_STORAGE: (&str, u64) = ("SMT_IS_STORAGE", 13371337); diff --git a/evm/src/cpu/kernel/constants/smt_type.rs b/evm/src/cpu/kernel/constants/smt_type.rs new file mode 100644 index 0000000000..b134598bc6 --- /dev/null +++ b/evm/src/cpu/kernel/constants/smt_type.rs @@ -0,0 +1,23 @@ +#[derive(Copy, Clone, Debug)] +pub(crate) enum PartialSmtType { + Hash = 0, + Internal = 1, + Leaf = 2, +} + +impl PartialSmtType { + pub(crate) const COUNT: usize = 3; + + pub(crate) fn all() -> [Self; Self::COUNT] { + [Self::Hash, Self::Internal, Self::Leaf] + } + + /// The variable name that gets passed into kernel assembly code. + pub(crate) fn var_name(&self) -> &'static str { + match self { + Self::Hash => "SMT_NODE_HASH", + Self::Internal => "SMT_NODE_INTERNAL", + Self::Leaf => "SMT_NODE_LEAF", + } + } +} diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 1b9160d695..96e2810c2d 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -188,6 +188,7 @@ impl<'a> Interpreter<'a> { .set(field as usize, value) } + #[allow(unused)] pub(crate) fn get_trie_data(&self) -> &[U256] { &self.generation_state.memory.contexts[0].segments[Segment::TrieData as usize].content } diff --git a/evm/src/cpu/kernel/tests/account_code.rs b/evm/src/cpu/kernel/tests/account_code.rs index 278593787d..9283af77e7 100644 --- a/evm/src/cpu/kernel/tests/account_code.rs +++ b/evm/src/cpu/kernel/tests/account_code.rs @@ -1,26 +1,25 @@ use std::collections::HashMap; use anyhow::{anyhow, Result}; -use eth_trie_utils::partial_trie::{HashedPartialTrie, PartialTrie}; use ethereum_types::{Address, BigEndianHash, H256, U256}; use keccak_hash::keccak; -use rand::{thread_rng, Rng}; +use rand::{random, thread_rng, Rng}; +use smt_utils::account::Account; +use smt_utils::smt::Smt; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::context_metadata::ContextMetadata::GasLimit; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; -use crate::cpu::kernel::tests::mpt::nibbles_64; -use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; +use crate::generation::mpt::{all_mpt_prover_inputs_reversed, state_smt_prover_inputs_reversed}; use crate::memory::segments::Segment; -use crate::Node; // Test account with a given code hash. -fn test_account(code: &[u8]) -> AccountRlp { - AccountRlp { - nonce: U256::from(1111), +fn test_account(code: &[u8]) -> Account { + Account { + nonce: 1111, balance: U256::from(2222), - storage_root: HashedPartialTrie::from(Node::Empty).hash(), + storage_smt: Smt::empty(), code_hash: keccak(code), } } @@ -36,36 +35,32 @@ fn random_code() -> Vec { fn prepare_interpreter( interpreter: &mut Interpreter, address: Address, - account: &AccountRlp, + account: Account, ) -> Result<()> { let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; - let mpt_insert_state_trie = KERNEL.global_labels["mpt_insert_state_trie"]; - let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; - let mut state_trie: HashedPartialTrie = Default::default(); + let smt_insert_state = KERNEL.global_labels["smt_insert_state"]; + let smt_hash_state = KERNEL.global_labels["smt_hash_state"]; + let mut state_smt = Smt::empty(); let trie_inputs = Default::default(); interpreter.generation_state.registers.program_counter = load_all_mpts; interpreter.push(0xDEADBEEFu32.into()); + interpreter.generation_state.state_smt_prover_inputs = + state_smt_prover_inputs_reversed(&trie_inputs); interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs) .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; interpreter.run()?; assert_eq!(interpreter.stack(), vec![]); - let k = nibbles_64(U256::from_big_endian( - keccak(address.to_fixed_bytes()).as_bytes(), - )); - // Next, execute mpt_insert_state_trie. - interpreter.generation_state.registers.program_counter = mpt_insert_state_trie; + let k = keccak(address.to_fixed_bytes()); + // Next, execute smt_insert_state. + interpreter.generation_state.registers.program_counter = smt_insert_state; 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(U256::zero()); // For key. + trie_data.push(account.nonce.into()); 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, @@ -76,7 +71,7 @@ fn prepare_interpreter( interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len); interpreter.push(0xDEADBEEFu32.into()); interpreter.push(value_ptr.into()); // value_ptr - interpreter.push(k.try_into_u256().unwrap()); // key + interpreter.push(k.into_uint()); // key interpreter.run()?; assert_eq!( @@ -87,7 +82,7 @@ fn prepare_interpreter( ); // Now, execute mpt_hash_state_trie. - interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; + interpreter.generation_state.registers.program_counter = smt_hash_state; interpreter.push(0xDEADBEEFu32.into()); interpreter.run()?; @@ -99,8 +94,8 @@ fn prepare_interpreter( ); let hash = H256::from_uint(&interpreter.stack()[0]); - state_trie.insert(k, rlp::encode(account).to_vec()); - let expected_state_trie_hash = state_trie.hash(); + state_smt.insert(k.into(), account.into()).unwrap(); + let expected_state_trie_hash = state_smt.root; assert_eq!(hash, expected_state_trie_hash); Ok(()) @@ -112,9 +107,9 @@ fn test_extcodesize() -> Result<()> { let account = test_account(&code); let mut interpreter = Interpreter::new_with_kernel(0, vec![]); - let address: Address = thread_rng().gen(); + let address: Address = random(); // Prepare the interpreter by inserting the account in the state trie. - prepare_interpreter(&mut interpreter, address, &account)?; + prepare_interpreter(&mut interpreter, address, account)?; let extcodesize = KERNEL.global_labels["extcodesize"]; @@ -141,7 +136,7 @@ fn test_extcodecopy() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(0, vec![]); let address: Address = thread_rng().gen(); // Prepare the interpreter by inserting the account in the state trie. - prepare_interpreter(&mut interpreter, address, &account)?; + prepare_interpreter(&mut interpreter, address, account)?; interpreter.generation_state.memory.contexts[interpreter.context].segments [Segment::ContextMetadata as usize] diff --git a/evm/src/cpu/kernel/tests/balance.rs b/evm/src/cpu/kernel/tests/balance.rs index 40214405c7..f83a6eb1f3 100644 --- a/evm/src/cpu/kernel/tests/balance.rs +++ b/evm/src/cpu/kernel/tests/balance.rs @@ -1,22 +1,21 @@ use anyhow::{anyhow, Result}; -use eth_trie_utils::partial_trie::{HashedPartialTrie, PartialTrie}; use ethereum_types::{Address, BigEndianHash, H256, U256}; use keccak_hash::keccak; use rand::{thread_rng, Rng}; +use smt_utils::account::Account; +use smt_utils::smt::Smt; 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; -use crate::generation::mpt::{all_mpt_prover_inputs_reversed, AccountRlp}; -use crate::Node; +use crate::generation::mpt::all_mpt_prover_inputs_reversed; // Test account with a given code hash. -fn test_account(balance: U256) -> AccountRlp { - AccountRlp { - nonce: U256::from(1111), +fn test_account(balance: U256) -> Account { + Account { + nonce: 1111, balance, - storage_root: HashedPartialTrie::from(Node::Empty).hash(), + storage_smt: Smt::empty(), code_hash: H256::from_uint(&U256::from(8888)), } } @@ -26,12 +25,12 @@ fn test_account(balance: U256) -> AccountRlp { fn prepare_interpreter( interpreter: &mut Interpreter, address: Address, - account: &AccountRlp, + account: Account, ) -> Result<()> { let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; - let mpt_insert_state_trie = KERNEL.global_labels["mpt_insert_state_trie"]; - let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; - let mut state_trie: HashedPartialTrie = Default::default(); + let smt_insert_state = KERNEL.global_labels["smt_insert_state"]; + let smt_hash_state = KERNEL.global_labels["smt_hash_state"]; + let mut state_smt = Smt::empty(); let trie_inputs = Default::default(); interpreter.generation_state.registers.program_counter = load_all_mpts; @@ -43,19 +42,18 @@ fn prepare_interpreter( interpreter.run()?; assert_eq!(interpreter.stack(), vec![]); - let k = nibbles_64(U256::from_big_endian( - keccak(address.to_fixed_bytes()).as_bytes(), - )); - // Next, execute mpt_insert_state_trie. - interpreter.generation_state.registers.program_counter = mpt_insert_state_trie; + let k = keccak(address.to_fixed_bytes()); + // Next, execute smt_insert_state. + interpreter.generation_state.registers.program_counter = smt_insert_state; 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. + // In the assembly we skip over 0, knowing trie_data[0:4] = 0 by default. // Since we don't explicitly set it to 0, we need to do so here. - trie_data.push(0.into()); + trie_data.extend((0..4).map(|_| U256::zero())); } let value_ptr = trie_data.len(); - trie_data.push(account.nonce); + trie_data.push(U256::zero()); // For key. + trie_data.push(account.nonce.into()); 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, @@ -66,7 +64,7 @@ fn prepare_interpreter( interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, trie_data_len); interpreter.push(0xDEADBEEFu32.into()); interpreter.push(value_ptr.into()); // value_ptr - interpreter.push(k.try_into_u256().unwrap()); // key + interpreter.push(k.into_uint()); interpreter.run()?; assert_eq!( @@ -77,7 +75,7 @@ fn prepare_interpreter( ); // Now, execute mpt_hash_state_trie. - interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; + interpreter.generation_state.registers.program_counter = smt_hash_state; interpreter.push(0xDEADBEEFu32.into()); interpreter.run()?; @@ -89,8 +87,8 @@ fn prepare_interpreter( ); let hash = H256::from_uint(&interpreter.stack()[0]); - state_trie.insert(k, rlp::encode(account).to_vec()); - let expected_state_trie_hash = state_trie.hash(); + state_smt.insert(k.into(), account.into()).unwrap(); + let expected_state_trie_hash = state_smt.root; assert_eq!(hash, expected_state_trie_hash); Ok(()) @@ -105,7 +103,7 @@ fn test_balance() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(0, vec![]); let address: Address = rng.gen(); // Prepare the interpreter by inserting the account in the state trie. - prepare_interpreter(&mut interpreter, address, &account)?; + prepare_interpreter(&mut interpreter, address, account)?; // Test `balance` interpreter.generation_state.registers.program_counter = KERNEL.global_labels["balance"]; diff --git a/evm/src/cpu/kernel/tests/mod.rs b/evm/src/cpu/kernel/tests/mod.rs index b66c016266..d49a22c7d8 100644 --- a/evm/src/cpu/kernel/tests/mod.rs +++ b/evm/src/cpu/kernel/tests/mod.rs @@ -10,11 +10,11 @@ mod ecc; mod exp; mod hash; mod log; -mod mpt; mod packing; mod receipt; mod rlp; mod signed_syscalls; +mod smt; mod transaction_parsing; use std::str::FromStr; diff --git a/evm/src/cpu/kernel/tests/mpt/delete.rs b/evm/src/cpu/kernel/tests/mpt/delete.rs deleted file mode 100644 index 074eea26ef..0000000000 --- a/evm/src/cpu/kernel/tests/mpt/delete.rs +++ /dev/null @@ -1,130 +0,0 @@ -use anyhow::{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()) -} - -#[test] -fn mpt_delete_branch_into_hash() -> Result<()> { - let hash = Node::Hash(H256::random()); - let state_trie = Node::Extension { - nibbles: nibbles_64(0xADF), - child: hash.into(), - } - .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).map_err(|_| anyhow!("Invalid MPT data"))?; - 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.try_into_u256().unwrap()); // 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.try_into_u256().unwrap()); - 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/hash.rs b/evm/src/cpu/kernel/tests/mpt/hash.rs deleted file mode 100644 index 05077a94da..0000000000 --- a/evm/src/cpu/kernel/tests/mpt/hash.rs +++ /dev/null @@ -1,137 +0,0 @@ -use anyhow::{anyhow, Result}; -use eth_trie_utils::partial_trie::PartialTrie; -use ethereum_types::{BigEndianHash, H256}; - -use crate::cpu::kernel::aggregator::KERNEL; -use crate::cpu::kernel::interpreter::Interpreter; -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; -use crate::Node; - -// TODO: Test with short leaf. Might need to be a storage trie. - -#[test] -fn mpt_hash_empty() -> Result<()> { - let trie_inputs = TrieInputs { - state_trie: Default::default(), - transactions_trie: Default::default(), - receipts_trie: Default::default(), - storage_tries: vec![], - }; - - test_state_trie(trie_inputs) -} - -#[test] -fn mpt_hash_empty_branch() -> Result<()> { - let children = core::array::from_fn(|_| Node::Empty.into()); - let state_trie = Node::Branch { - children, - value: vec![], - } - .into(); - 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: Node::Hash(hash).into(), - 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 = Node::Leaf { - nibbles: 0xABC_u64.into(), - value: test_account_1_rlp(), - } - .into(); - 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 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 leaf = Node::Leaf { - nibbles: 0xABC_u64.into(), - value: test_account_2_rlp(), - } - .into(); - - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[3] = leaf; - let state_trie = Node::Branch { - children, - value: vec![], - } - .into(); - - let trie_inputs = TrieInputs { - state_trie, - transactions_trie: Default::default(), - receipts_trie: Default::default(), - storage_tries: vec![], - }; - - test_state_trie(trie_inputs) -} - -fn test_state_trie(trie_inputs: TrieInputs) -> Result<()> { - let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; - 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).map_err(|_| anyhow!("Invalid MPT data"))?; - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); - - // Now, execute mpt_hash_state_trie. - interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; - interpreter.push(0xDEADBEEFu32.into()); - interpreter.run()?; - - assert_eq!( - interpreter.stack().len(), - 1, - "Expected 1 item on stack, found {:?}", - interpreter.stack() - ); - let hash = H256::from_uint(&interpreter.stack()[0]); - let expected_state_trie_hash = trie_inputs.state_trie.hash(); - assert_eq!(hash, expected_state_trie_hash); - - Ok(()) -} diff --git a/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs b/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs deleted file mode 100644 index c13b812220..0000000000 --- a/evm/src/cpu/kernel/tests/mpt/hex_prefix.rs +++ /dev/null @@ -1,87 +0,0 @@ -use anyhow::Result; - -use crate::cpu::kernel::aggregator::KERNEL; -use crate::cpu::kernel::interpreter::Interpreter; - -#[test] -fn hex_prefix_even_nonterminated() -> Result<()> { - let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; - - let retdest = 0xDEADBEEFu32.into(); - let terminated = 0.into(); - let packed_nibbles = 0xABCDEF.into(); - let num_nibbles = 6.into(); - let rlp_pos = 0.into(); - let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; - let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![5.into()]); - - assert_eq!( - interpreter.get_rlp_memory(), - vec![ - 0x80 + 4, // prefix - 0, // neither flag is set - 0xAB, - 0xCD, - 0xEF - ] - ); - - Ok(()) -} - -#[test] -fn hex_prefix_odd_terminated() -> Result<()> { - let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; - - let retdest = 0xDEADBEEFu32.into(); - let terminated = 1.into(); - let packed_nibbles = 0xABCDE.into(); - let num_nibbles = 5.into(); - let rlp_pos = 0.into(); - let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; - let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![4.into()]); - - assert_eq!( - interpreter.get_rlp_memory(), - vec![ - 0x80 + 3, // prefix - (2 + 1) * 16 + 0xA, - 0xBC, - 0xDE, - ] - ); - - Ok(()) -} - -#[test] -fn hex_prefix_odd_terminated_tiny() -> Result<()> { - let hex_prefix = KERNEL.global_labels["hex_prefix_rlp"]; - - let retdest = 0xDEADBEEFu32.into(); - let terminated = 1.into(); - let packed_nibbles = 0xA.into(); - let num_nibbles = 1.into(); - let rlp_pos = 2.into(); - let initial_stack = vec![retdest, terminated, packed_nibbles, num_nibbles, rlp_pos]; - let mut interpreter = Interpreter::new_with_kernel(hex_prefix, initial_stack); - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![3.into()]); - - assert_eq!( - interpreter.get_rlp_memory(), - vec![ - // Since rlp_pos = 2, we skipped over the first two bytes. - 0, - 0, - // No length prefix; this tiny string is its own RLP encoding. - (2 + 1) * 16 + 0xA, - ] - ); - - Ok(()) -} diff --git a/evm/src/cpu/kernel/tests/mpt/insert.rs b/evm/src/cpu/kernel/tests/mpt/insert.rs deleted file mode 100644 index 6fd95a30b9..0000000000 --- a/evm/src/cpu/kernel/tests/mpt/insert.rs +++ /dev/null @@ -1,230 +0,0 @@ -use anyhow::{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, nibbles_count, 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_insert_empty() -> Result<()> { - test_state_trie(Default::default(), nibbles_64(0xABC), test_account_2()) -} - -#[test] -fn mpt_insert_leaf_identical_keys() -> Result<()> { - let key = nibbles_64(0xABC); - let state_trie = Node::Leaf { - nibbles: key, - value: test_account_1_rlp(), - } - .into(); - test_state_trie(state_trie, key, test_account_2()) -} - -#[test] -fn mpt_insert_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_insert_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()) -} - -#[test] -#[ignore] // TODO: Not valid for state trie, all keys have same len. -fn mpt_insert_leaf_insert_key_extends_leaf_key() -> Result<()> { - let state_trie = Node::Leaf { - nibbles: 0xABC_u64.into(), - value: test_account_1_rlp(), - } - .into(); - test_state_trie(state_trie, nibbles_64(0xABCDE), test_account_2()) -} - -#[test] -#[ignore] // TODO: Not valid for state trie, all keys have same len. -fn mpt_insert_leaf_leaf_key_extends_insert_key() -> Result<()> { - let state_trie = Node::Leaf { - nibbles: 0xABCDE_u64.into(), - value: test_account_1_rlp(), - } - .into(); - test_state_trie(state_trie, nibbles_64(0xABC), test_account_2()) -} - -#[test] -fn mpt_insert_branch_replacing_empty_child() -> Result<()> { - let children = core::array::from_fn(|_| Node::Empty.into()); - let state_trie = Node::Branch { - children, - value: vec![], - } - .into(); - - test_state_trie(state_trie, nibbles_64(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 = core::array::from_fn(|_| Node::Empty.into()); - children[0xD] = Node::Leaf { - nibbles: 0xEF_u64.into(), - value: test_account_1_rlp(), - } - .into(); - let state_trie = Node::Extension { - nibbles: 0xABC_u64.into(), - child: Node::Branch { - children, - value: test_account_1_rlp(), - } - .into(), - } - .into(); - test_state_trie(state_trie, nibbles_64(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 = core::array::from_fn(|_| Node::Empty.into()); - children[0xB] = Node::Leaf { - nibbles: 0xCD_u64.into(), - value: test_account_1_rlp(), - } - .into(); - let state_trie = Node::Extension { - nibbles: 0xA_u64.into(), - child: Node::Branch { - children, - value: test_account_1_rlp(), - } - .into(), - } - .into(); - test_state_trie(state_trie, nibbles_64(0xABCDEF), test_account_2()) -} - -#[test] -fn mpt_insert_branch_to_leaf_same_key() -> Result<()> { - let leaf = Node::Leaf { - nibbles: nibbles_count(0xBCD, 63), - value: test_account_1_rlp(), - } - .into(); - - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[0] = leaf; - let state_trie = Node::Branch { - children, - value: vec![], - } - .into(); - - test_state_trie(state_trie, nibbles_64(0xABCD), 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( - mut 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_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).map_err(|_| anyhow!("Invalid MPT data"))?; - 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.try_into_u256().unwrap()); // key - - interpreter.run()?; - assert_eq!( - interpreter.stack().len(), - 0, - "Expected empty stack after insert, found {:?}", - interpreter.stack() - ); - - // Now, execute mpt_hash_state_trie. - interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; - interpreter.push(0xDEADBEEFu32.into()); - interpreter.run()?; - - assert_eq!( - interpreter.stack().len(), - 1, - "Expected 1 item on stack after hashing, found {:?}", - interpreter.stack() - ); - let hash = H256::from_uint(&interpreter.stack()[0]); - - state_trie.insert(k, rlp::encode(&account).to_vec()); - let expected_state_trie_hash = state_trie.hash(); - assert_eq!(hash, expected_state_trie_hash); - - Ok(()) -} diff --git a/evm/src/cpu/kernel/tests/mpt/load.rs b/evm/src/cpu/kernel/tests/mpt/load.rs deleted file mode 100644 index ae0bfa3bc8..0000000000 --- a/evm/src/cpu/kernel/tests/mpt/load.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::str::FromStr; - -use anyhow::{anyhow, Result}; -use eth_trie_utils::nibbles::Nibbles; -use eth_trie_utils::partial_trie::HashedPartialTrie; -use ethereum_types::{BigEndianHash, H256, U256}; -use hex_literal::hex; - -use crate::cpu::kernel::aggregator::KERNEL; -use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; -use crate::cpu::kernel::constants::trie_type::PartialTrieType; -use crate::cpu::kernel::interpreter::Interpreter; -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; -use crate::Node; - -#[test] -fn load_all_mpts_empty() -> Result<()> { - let trie_inputs = TrieInputs { - state_trie: Default::default(), - 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) - .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); - - assert_eq!(interpreter.get_trie_data(), vec![]); - - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot), - 0.into() - ); - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot), - 0.into() - ); - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot), - 0.into() - ); - - Ok(()) -} - -#[test] -fn load_all_mpts_leaf() -> Result<()> { - let trie_inputs = TrieInputs { - state_trie: Node::Leaf { - nibbles: 0xABC_u64.into(), - value: test_account_1_rlp(), - } - .into(), - 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) - .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); - - let type_leaf = U256::from(PartialTrieType::Leaf as u32); - assert_eq!( - interpreter.get_trie_data(), - vec![ - 0.into(), - type_leaf, - 3.into(), - 0xABC.into(), - 5.into(), // value ptr - test_account_1().nonce, - test_account_1().balance, - 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(), - ] - ); - - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot), - 0.into() - ); - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot), - 0.into() - ); - - Ok(()) -} - -#[test] -fn load_all_mpts_hash() -> Result<()> { - let hash = H256::random(); - let trie_inputs = TrieInputs { - state_trie: Node::Hash(hash).into(), - 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) - .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); - - let type_hash = U256::from(PartialTrieType::Hash as u32); - assert_eq!( - 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(()) -} - -#[test] -fn load_all_mpts_empty_branch() -> Result<()> { - let children = core::array::from_fn(|_| Node::Empty.into()); - let state_trie = Node::Branch { - children, - value: vec![], - } - .into(); - let trie_inputs = TrieInputs { - state_trie, - 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) - .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); - - let type_branch = U256::from(PartialTrieType::Branch as u32); - assert_eq!( - interpreter.get_trie_data(), - vec![ - 0.into(), // First address is unused, so that 0 can be treated as a null pointer. - type_branch, - 0.into(), // child 0 - 0.into(), // ... - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), - 0.into(), // child 16 - 0.into(), // value_ptr - ] - ); - - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::TransactionTrieRoot), - 0.into() - ); - assert_eq!( - interpreter.get_global_metadata_field(GlobalMetadata::ReceiptTrieRoot), - 0.into() - ); - - Ok(()) -} - -#[test] -fn load_all_mpts_ext_to_leaf() -> Result<()> { - let trie_inputs = TrieInputs { - state_trie: extension_to_leaf(test_account_1_rlp()), - 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) - .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); - - let type_extension = U256::from(PartialTrieType::Extension as u32); - let type_leaf = U256::from(PartialTrieType::Leaf as u32); - assert_eq!( - interpreter.get_trie_data(), - vec![ - 0.into(), // First address is unused, so that 0 can be treated as a null pointer. - type_extension, - 3.into(), // 3 nibbles - 0xABC.into(), // key part - 5.into(), // Pointer to the leaf node immediately below. - type_leaf, - 3.into(), // 3 nibbles - 0xDEF.into(), // key part - 9.into(), // value pointer - test_account_1().nonce, - test_account_1().balance, - 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(), - ] - ); - - Ok(()) -} - -#[test] -fn load_mpt_txn_trie() -> Result<()> { - let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; - - let txn = hex!("f860010a830186a094095e7baea6a6c7c4c2dfeb977efac326af552e89808025a04a223955b0bd3827e3740a9a427d0ea43beb5bafa44a0204bf0a3306c8219f7ba0502c32d78f233e9e7ce9f5df3b576556d5d49731e0678fd5a068cdf359557b5b").to_vec(); - - let trie_inputs = TrieInputs { - state_trie: Default::default(), - transactions_trie: HashedPartialTrie::from(Node::Leaf { - nibbles: Nibbles::from_str("0x80").unwrap(), - value: txn.clone(), - }), - receipts_trie: Default::default(), - storage_tries: vec![], - }; - - 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) - .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); - - let mut expected_trie_data = vec![ - 0.into(), - U256::from(PartialTrieType::Leaf as u32), - 2.into(), - 128.into(), // Nibble - 5.into(), // value_ptr - txn.len().into(), - ]; - expected_trie_data.extend(txn.into_iter().map(U256::from)); - let trie_data = interpreter.get_trie_data(); - - assert_eq!(trie_data, expected_trie_data); - - Ok(()) -} diff --git a/evm/src/cpu/kernel/tests/mpt/mod.rs b/evm/src/cpu/kernel/tests/mpt/mod.rs deleted file mode 100644 index 292d064af1..0000000000 --- a/evm/src/cpu/kernel/tests/mpt/mod.rs +++ /dev/null @@ -1,71 +0,0 @@ -use eth_trie_utils::nibbles::Nibbles; -use eth_trie_utils::partial_trie::HashedPartialTrie; -use ethereum_types::{BigEndianHash, H256, U256}; - -use crate::generation::mpt::AccountRlp; -use crate::Node; - -mod delete; -mod hash; -mod hex_prefix; -mod insert; -mod load; -mod read; - -pub(crate) fn nibbles_64>(v: T) -> Nibbles { - let packed: U256 = v.into(); - Nibbles { - count: 64, - packed: packed.into(), - } -} - -pub(crate) fn nibbles_count>(v: T, count: usize) -> Nibbles { - let packed: U256 = v.into(); - Nibbles { - count, - packed: packed.into(), - } -} - -pub(crate) fn test_account_1() -> AccountRlp { - 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)), - } -} - -pub(crate) fn test_account_1_rlp() -> Vec { - rlp::encode(&test_account_1()).to_vec() -} - -pub(crate) fn test_account_2() -> AccountRlp { - AccountRlp { - nonce: U256::from(5555), - balance: U256::from(6666), - storage_root: H256::from_uint(&U256::from(7777)), - code_hash: H256::from_uint(&U256::from(8888)), - } -} - -pub(crate) fn test_account_2_rlp() -> Vec { - rlp::encode(&test_account_2()).to_vec() -} - -/// A `PartialTrie` where an extension node leads to a leaf node containing an account. -pub(crate) fn extension_to_leaf(value: Vec) -> HashedPartialTrie { - Node::Extension { - nibbles: 0xABC_u64.into(), - child: Node::Leaf { - nibbles: Nibbles { - count: 3, - packed: 0xDEF.into(), - }, - value, - } - .into(), - } - .into() -} diff --git a/evm/src/cpu/kernel/tests/mpt/read.rs b/evm/src/cpu/kernel/tests/mpt/read.rs deleted file mode 100644 index f9ae94f03b..0000000000 --- a/evm/src/cpu/kernel/tests/mpt/read.rs +++ /dev/null @@ -1,49 +0,0 @@ -use anyhow::{anyhow, Result}; -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, 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 trie_inputs = TrieInputs { - state_trie: extension_to_leaf(test_account_1_rlp()), - transactions_trie: Default::default(), - receipts_trie: Default::default(), - storage_tries: vec![], - }; - - let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; - let mpt_read = KERNEL.global_labels["mpt_read"]; - - 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) - .map_err(|err| anyhow!("Invalid MPT data: {:?}", err))?; - interpreter.run()?; - assert_eq!(interpreter.stack(), vec![]); - - // Now, execute mpt_read on the state trie. - interpreter.generation_state.registers.program_counter = mpt_read; - interpreter.push(0xdeadbeefu32.into()); - interpreter.push(0xABCDEFu64.into()); - interpreter.push(6.into()); - interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); - interpreter.run()?; - - 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], 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(()) -} diff --git a/evm/src/cpu/kernel/tests/smt/hash.rs b/evm/src/cpu/kernel/tests/smt/hash.rs new file mode 100644 index 0000000000..b38caecc59 --- /dev/null +++ b/evm/src/cpu/kernel/tests/smt/hash.rs @@ -0,0 +1,65 @@ +use anyhow::{anyhow, Result}; +use ethereum_types::{BigEndianHash, H256, U256}; +use rand::{thread_rng, Rng}; +use smt_utils::account::Account; +use smt_utils::smt::Smt; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::generation::mpt::{all_mpt_prover_inputs_reversed, state_smt_prover_inputs_reversed}; +use crate::generation::TrieInputs; + +// TODO: Test with short leaf. Might need to be a storage trie. + +#[test] +fn smt_hash_empty() -> Result<()> { + let smt = Smt::empty(); + test_state_smt(smt) +} + +#[test] +fn smt_hash() -> Result<()> { + let n = 100; + let mut rng = thread_rng(); + let rand_node = |_| (U256(rng.gen()).into(), Account::rand(10).into()); + let smt = Smt::new((0..n).map(rand_node)).unwrap(); + + test_state_smt(smt) +} + +fn test_state_smt(state_smt: Smt) -> Result<()> { + let trie_inputs = TrieInputs { + state_smt: state_smt.serialize(), + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + }; + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + let smt_hash_state = KERNEL.global_labels["smt_hash_state"]; + + 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).map_err(|_| anyhow!("Invalid MPT data"))?; + interpreter.generation_state.state_smt_prover_inputs = + state_smt_prover_inputs_reversed(&trie_inputs); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![]); + + // Now, execute smt_hash_state. + interpreter.generation_state.registers.program_counter = smt_hash_state; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.run()?; + + assert_eq!( + interpreter.stack().len(), + 1, + "Expected 1 item on stack, found {:?}", + interpreter.stack() + ); + let hash = H256::from_uint(&interpreter.stack()[0]); + let expected_state_trie_hash = state_smt.root; + assert_eq!(hash, expected_state_trie_hash); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/smt/insert.rs b/evm/src/cpu/kernel/tests/smt/insert.rs new file mode 100644 index 0000000000..a785e2c08c --- /dev/null +++ b/evm/src/cpu/kernel/tests/smt/insert.rs @@ -0,0 +1,181 @@ +use anyhow::{anyhow, Result}; +use ethereum_types::{BigEndianHash, U256}; +use rand::{thread_rng, Rng}; +use smt_utils::account::Account; +use smt_utils::smt::{AccountOrValue, Smt, ValOrHash}; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::evm_constants; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::generation::mpt::{all_mpt_prover_inputs_reversed, state_smt_prover_inputs_reversed}; +use crate::generation::TrieInputs; +use crate::memory::segments::Segment; + +#[test] +fn smt_insert_state() -> Result<()> { + let n = 100; + let mut rng = thread_rng(); + let rand_node = |_| { + ( + U256(rng.gen()).into(), + ValOrHash::Val(AccountOrValue::Account(Account::rand(10))), + ) + }; + let smt = Smt::new((0..n).map(rand_node)).unwrap(); + let new_key = U256(rng.gen()); + let new_account = Account::rand(0); + + test_state_smt(smt, new_key, new_account) +} + +fn test_state_smt(mut state_smt: Smt, new_key: U256, new_account: Account) -> Result<()> { + let trie_inputs = TrieInputs { + state_smt: state_smt.serialize(), + 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).map_err(|_| anyhow!("Invalid MPT data"))?; + interpreter.generation_state.state_smt_prover_inputs = + state_smt_prover_inputs_reversed(&trie_inputs); + interpreter.run()?; + assert_eq!(interpreter.stack(), vec![]); + + let state_root = interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot); + let trie_data_size = interpreter.get_global_metadata_field(GlobalMetadata::TrieDataSize); + let trie_data = interpreter.get_trie_data_mut(); + trie_data.push(U256::zero()); // For key + let mut packed_account = new_account.pack_u256(); + packed_account[2] = U256::zero(); // No storage SMT. + for (i, x) in (trie_data_size.as_usize() + 1..).zip(packed_account) { + if i < trie_data.len() { + trie_data[i] = x; + } else { + trie_data.push(x); + } + } + let len = trie_data.len(); + interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, len.into()); + + let smt_insert = KERNEL.global_labels["smt_insert"]; + // Now, execute smt_insert. + interpreter.generation_state.registers.program_counter = smt_insert; + interpreter.push(0xDEADBEEFu32.into()); + interpreter.push(trie_data_size); + interpreter.push(new_key); + interpreter.push(state_root); + interpreter.run()?; + + assert_eq!( + interpreter.stack().len(), + 1, + "Expected 1 item on stack, found {:?}", + interpreter.stack() + ); + let smt_hash = KERNEL.global_labels["smt_hash"]; + interpreter.generation_state.registers.program_counter = smt_hash; + let ptr = interpreter.stack()[0]; + interpreter.pop(); + interpreter.push(0xDEADBEEFu32.into()); + interpreter.push(ptr); + interpreter.run()?; + let hash = interpreter.pop(); + + state_smt + .insert( + new_key.into(), + ValOrHash::Val(AccountOrValue::Account(new_account)), + ) + .unwrap(); + let expected_hash = state_smt.root; + + assert_eq!(hash, expected_hash.into_uint()); + + Ok(()) +} + +#[test] +fn smt_insert_storage() -> Result<()> { + let n = 100; + let mut rng = thread_rng(); + let rand_node = |_| { + ( + U256(rng.gen()).into(), + ValOrHash::Val(AccountOrValue::Value(U256(rng.gen()))), + ) + }; + let smt = Smt::new((0..n).map(rand_node)).unwrap(); + let new_key = U256(rng.gen()); + let new_val = U256(rng.gen()); + + test_storage_smt(smt, new_key, new_val) +} + +fn test_storage_smt(mut storage_smt: Smt, new_key: U256, new_val: U256) -> Result<()> { + let initial_stack = vec![0xDEADBEEFu32.into()]; + let smt_insert = KERNEL.global_labels["smt_insert"]; + let mut interpreter = Interpreter::new_with_kernel(smt_insert, initial_stack); + let trie_data = storage_smt.serialize(); + let len = trie_data.len(); + interpreter.generation_state.memory.contexts[0].segments[Segment::TrieData as usize].content = + trie_data; + interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, len.into()); + interpreter.set_global_metadata_field(GlobalMetadata::StateTrieRoot, 2.into()); + + let state_root = interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot); + let trie_data_size = interpreter.get_global_metadata_field(GlobalMetadata::TrieDataSize); + let trie_data = &mut interpreter.generation_state.memory.contexts[0].segments + [Segment::TrieData as usize] + .content; + trie_data.push(U256::zero()); // For key + trie_data.push(new_val); + let len = trie_data.len(); + interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, len.into()); + + let smt_insert = KERNEL.global_labels["smt_insert"]; + // Now, execute smt_insert. + interpreter.generation_state.registers.program_counter = smt_insert; + interpreter.push(trie_data_size); + interpreter.push(new_key); + interpreter.push(state_root); + interpreter.run()?; + + assert_eq!( + interpreter.stack().len(), + 1, + "Expected 1 item on stack, found {:?}", + interpreter.stack() + ); + + let smt_hash = KERNEL.global_labels["smt_hash"]; + interpreter.generation_state.registers.program_counter = smt_hash; + interpreter.generation_state.memory.contexts[0].segments[Segment::KernelGeneral as usize] + .content + .resize(13371338, U256::zero()); + interpreter.generation_state.memory.contexts[0].segments[Segment::KernelGeneral as usize] + .content[evm_constants()["SMT_IS_STORAGE"].as_usize()] = U256::one(); // To hash storage trie. + let ptr = interpreter.stack()[0]; + interpreter.pop(); + interpreter.push(0xDEADBEEFu32.into()); + interpreter.push(ptr); + interpreter.run()?; + let hash = interpreter.pop(); + + storage_smt + .insert( + new_key.into(), + ValOrHash::Val(AccountOrValue::Value(new_val)), + ) + .unwrap(); + let expected_hash = storage_smt.root; + + assert_eq!(hash, expected_hash.into_uint()); + + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/smt/mod.rs b/evm/src/cpu/kernel/tests/smt/mod.rs new file mode 100644 index 0000000000..2d7504cdc5 --- /dev/null +++ b/evm/src/cpu/kernel/tests/smt/mod.rs @@ -0,0 +1,2 @@ +mod hash; +mod insert; diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 62182cd254..c4d19dca01 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -9,6 +9,7 @@ use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; use serde::{Deserialize, Serialize}; +use smt_utils::smt::hash_serialize_state; use GlobalMetadata::{ ReceiptTrieRootDigestAfter, ReceiptTrieRootDigestBefore, StateTrieRootDigestAfter, StateTrieRootDigestBefore, TransactionTrieRootDigestAfter, TransactionTrieRootDigestBefore, @@ -69,11 +70,11 @@ pub struct GenerationInputs { pub addresses: Vec
, } -#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct TrieInputs { - /// A partial version of the state trie prior to these transactions. It should include all nodes + /// A serialized partial version of the state SMT prior to these transactions. It should include all nodes /// that will be accessed by these transactions. - pub state_trie: HashedPartialTrie, + pub state_smt: Vec, /// A partial version of the transaction trie prior to these transactions. It should include all /// nodes that will be accessed by these transactions. @@ -88,6 +89,18 @@ pub struct TrieInputs { pub storage_tries: Vec<(H256, HashedPartialTrie)>, } +impl Default for TrieInputs { + fn default() -> Self { + Self { + // First 2 zeros are for the default empty node. The next 2 are for the current empty state trie root. + state_smt: vec![U256::zero(); 4], + transactions_trie: Default::default(), + receipts_trie: Default::default(), + storage_tries: vec![], + } + } +} + fn apply_metadata_and_tries_memops, const D: usize>( state: &mut GenerationState, inputs: &GenerationInputs, @@ -124,7 +137,7 @@ fn apply_metadata_and_tries_memops, const D: usize> ), ( GlobalMetadata::StateTrieRootDigestBefore, - h2u(tries.state_trie.hash()), + h2u(hash_serialize_state(&tries.state_smt)), ), ( GlobalMetadata::TransactionTrieRootDigestBefore, diff --git a/evm/src/generation/mpt.rs b/evm/src/generation/mpt.rs index 20e8b30b60..726c20225b 100644 --- a/evm/src/generation/mpt.rs +++ b/evm/src/generation/mpt.rs @@ -1,17 +1,15 @@ -use std::collections::HashMap; use std::ops::Deref; use bytes::Bytes; -use eth_trie_utils::nibbles::Nibbles; use eth_trie_utils::partial_trie::{HashedPartialTrie, PartialTrie}; -use ethereum_types::{Address, BigEndianHash, H256, U256, U512}; +use ethereum_types::{Address, H256, U256}; use keccak_hash::keccak; use rlp::{Decodable, DecoderError, Encodable, PayloadInfo, Rlp, RlpStream}; use rlp_derive::{RlpDecodable, RlpEncodable}; use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::generation::TrieInputs; -use crate::witness::errors::{ProgramError, ProverInputError}; +use crate::witness::errors::ProgramError; use crate::Node; #[derive(RlpEncodable, RlpDecodable, Debug)] @@ -48,6 +46,12 @@ pub struct LegacyReceiptRlp { pub logs: Vec, } +pub(crate) fn state_smt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec { + let mut inputs = state_smt_prover_inputs(trie_inputs); + inputs.reverse(); + inputs +} + pub(crate) fn all_mpt_prover_inputs_reversed( trie_inputs: &TrieInputs, ) -> Result, ProgramError> { @@ -86,26 +90,18 @@ pub(crate) fn parse_receipts(rlp: &[u8]) -> Result, ProgramError> { Ok(parsed_receipt) } + +pub(crate) fn state_smt_prover_inputs(trie_inputs: &TrieInputs) -> Vec { + let len = trie_inputs.state_smt.len(); + let mut v = vec![len.into()]; + v.extend(trie_inputs.state_smt.iter()); + v +} + /// Generate prover inputs for the initial MPT data, in the format expected by `mpt/load.asm`. pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Result, ProgramError> { let mut prover_inputs = vec![]; - let storage_tries_by_state_key = trie_inputs - .storage_tries - .iter() - .map(|(hashed_address, storage_trie)| { - let key = Nibbles::from_bytes_be(hashed_address.as_bytes()).unwrap(); - (key, 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| { let mut parsed_txn = vec![U256::from(rlp.len())]; parsed_txn.extend(rlp.iter().copied().map(U256::from)); @@ -179,106 +175,6 @@ where } } -/// 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: &HashedPartialTrie, - key: Nibbles, - prover_inputs: &mut Vec, - storage_tries_by_state_key: &HashMap, -) -> Result<(), ProgramError> { - prover_inputs.push((PartialTrieType::of(trie) as u32).into()); - match trie.deref() { - Node::Empty => Ok(()), - Node::Hash(h) => { - prover_inputs.push(U256::from_big_endian(h.as_bytes())); - Ok(()) - } - Node::Branch { children, value } => { - if !value.is_empty() { - return Err(ProgramError::ProverInputError( - ProverInputError::InvalidMptInput, - )); - } - prover_inputs.push(U256::zero()); // value_present = 0 - - for (i, child) in children.iter().enumerate() { - let extended_key = key.merge_nibbles(&Nibbles { - count: 1, - packed: i.into(), - }); - mpt_prover_inputs_state_trie( - child, - extended_key, - prover_inputs, - storage_tries_by_state_key, - )?; - } - - Ok(()) - } - Node::Extension { nibbles, child } => { - prover_inputs.push(nibbles.count.into()); - prover_inputs.push( - nibbles - .try_into_u256() - .map_err(|_| ProgramError::IntegerTooLarge)?, - ); - let extended_key = key.merge_nibbles(nibbles); - mpt_prover_inputs_state_trie( - child, - extended_key, - prover_inputs, - storage_tries_by_state_key, - ) - } - Node::Leaf { nibbles, value } => { - let account: AccountRlp = rlp::decode(value).map_err(|_| ProgramError::InvalidRlp)?; - let AccountRlp { - nonce, - balance, - storage_root, - code_hash, - } = account; - - let storage_hash_only = HashedPartialTrie::new(Node::Hash(storage_root)); - let merged_key = key.merge_nibbles(nibbles); - let storage_trie: &HashedPartialTrie = storage_tries_by_state_key - .get(&merged_key) - .copied() - .unwrap_or(&storage_hash_only); - - assert_eq!(storage_trie.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 - .try_into_u256() - .map_err(|_| ProgramError::IntegerTooLarge)?, - ); - 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()); - - Ok(()) - } - } -} - -fn parse_storage_value(value_rlp: &[u8]) -> Result, ProgramError> { - let value: U256 = rlp::decode(value_rlp).map_err(|_| ProgramError::InvalidRlp)?; - Ok(vec![value]) -} - -fn empty_nibbles() -> Nibbles { - Nibbles { - count: 0, - packed: U512::zero(), - } -} - pub mod transaction_testing { use super::*; diff --git a/evm/src/generation/prover_input.rs b/evm/src/generation/prover_input.rs index 205dff7c66..cf618d1717 100644 --- a/evm/src/generation/prover_input.rs +++ b/evm/src/generation/prover_input.rs @@ -40,6 +40,7 @@ impl GenerationState { "sf" => self.run_sf(input_fn), "ffe" => self.run_ffe(input_fn), "mpt" => self.run_mpt(), + "smt" => self.run_smt(input_fn), "rlp" => self.run_rlp(), "current_hash" => self.run_current_hash(), "account_code" => self.run_account_code(input_fn), @@ -120,6 +121,19 @@ impl GenerationState { .ok_or(ProgramError::ProverInputError(OutOfMptData)) } + /// SMT data. + fn run_smt(&mut self, input_fn: &ProverInputFn) -> Result { + match input_fn.0[1].as_str() { + "state" => self + .state_smt_prover_inputs + .pop() + .ok_or(ProgramError::ProverInputError(OutOfSmtData)), + "transactions" => todo!(), + "receipts" => todo!(), + _ => panic!("Invalid SMT"), + } + } + /// RLP data. fn run_rlp(&mut self) -> Result { self.rlp_prover_inputs diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index aec01e1b71..79e0cfdb85 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -6,7 +6,7 @@ use plonky2::field::types::Field; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; -use crate::generation::mpt::all_mpt_prover_inputs_reversed; +use crate::generation::mpt::{all_mpt_prover_inputs_reversed, state_smt_prover_inputs_reversed}; use crate::generation::rlp::all_rlp_prover_inputs_reversed; use crate::generation::GenerationInputs; use crate::memory::segments::Segment; @@ -35,6 +35,10 @@ pub(crate) struct GenerationState { /// via `pop()`. pub(crate) mpt_prover_inputs: Vec, + /// Prover inputs containing SMT data, in reverse order so that the next input can be obtained + /// via `pop()`. + pub(crate) state_smt_prover_inputs: Vec, + /// Prover inputs containing RLP data, in reverse order so that the next input can be obtained /// via `pop()`. pub(crate) rlp_prover_inputs: Vec, @@ -53,7 +57,7 @@ pub(crate) struct GenerationState { impl GenerationState { pub(crate) fn new(inputs: GenerationInputs, kernel_code: &[u8]) -> Result { log::debug!("Input signed_txns: {:?}", &inputs.signed_txns); - log::debug!("Input state_trie: {:?}", &inputs.tries.state_trie); + log::debug!("Input state_trie: {:?}", &inputs.tries.state_smt); log::debug!( "Input transactions_trie: {:?}", &inputs.tries.transactions_trie @@ -61,6 +65,7 @@ impl GenerationState { log::debug!("Input receipts_trie: {:?}", &inputs.tries.receipts_trie); log::debug!("Input storage_tries: {:?}", &inputs.tries.storage_tries); log::debug!("Input contract_code: {:?}", &inputs.contract_code); + let state_smt_prover_inputs = state_smt_prover_inputs_reversed(&inputs.tries); let mpt_prover_inputs = all_mpt_prover_inputs_reversed(&inputs.tries)?; let rlp_prover_inputs = all_rlp_prover_inputs_reversed(&inputs.signed_txns); let bignum_modmul_result_limbs = Vec::new(); @@ -72,6 +77,7 @@ impl GenerationState { traces: Traces::default(), next_txn_index: 0, mpt_prover_inputs, + state_smt_prover_inputs, rlp_prover_inputs, state_key_to_address: HashMap::new(), bignum_modmul_result_limbs, diff --git a/evm/src/witness/errors.rs b/evm/src/witness/errors.rs index 8186246035..f883f8a4bd 100644 --- a/evm/src/witness/errors.rs +++ b/evm/src/witness/errors.rs @@ -30,9 +30,9 @@ pub enum MemoryError { #[derive(Debug)] pub enum ProverInputError { OutOfMptData, + OutOfSmtData, OutOfRlpData, CodeHashNotFound, - InvalidMptInput, InvalidInput, InvalidFunction, } diff --git a/evm/tests/add11_yml.rs b/evm/tests/add11_yml.rs index cb0212a388..ef38fb5c68 100644 --- a/evm/tests/add11_yml.rs +++ b/evm/tests/add11_yml.rs @@ -13,12 +13,14 @@ use plonky2::plonk::config::KeccakGoldilocksConfig; use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; -use plonky2_evm::generation::mpt::{AccountRlp, LegacyReceiptRlp}; +use plonky2_evm::generation::mpt::LegacyReceiptRlp; use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::{BlockHashes, BlockMetadata, TrieRoots}; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; use plonky2_evm::Node; +use smt_utils::account::Account; +use smt_utils::smt::Smt; type F = GoldilocksField; const D: usize = 2; @@ -40,37 +42,40 @@ fn add11_yml() -> anyhow::Result<()> { let sender_state_key = keccak(sender); let to_hashed = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); + let beneficiary_bits = beneficiary_state_key.into(); + let sender_bits = sender_state_key.into(); + let to_bits = to_hashed.into(); let code = [0x60, 0x01, 0x60, 0x01, 0x01, 0x60, 0x00, 0x55, 0x00]; let code_hash = keccak(code); - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_before = Account { + nonce: 1, + ..Account::default() }; - let sender_account_before = AccountRlp { + let sender_account_before = Account { balance: 0x0de0b6b3a7640000u64.into(), - ..AccountRlp::default() + ..Account::default() }; - let to_account_before = AccountRlp { + let to_account_before = Account { balance: 0x0de0b6b3a7640000u64.into(), code_hash, - ..AccountRlp::default() + ..Account::default() }; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); - state_trie_before.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_before).to_vec(), - ); - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + let mut state_smt_before = Smt::empty(); + state_smt_before + .insert(beneficiary_bits, beneficiary_account_before.into()) + .unwrap(); + state_smt_before + .insert(sender_bits, sender_account_before.into()) + .unwrap(); + state_smt_before + .insert(to_bits, to_account_before.into()) + .unwrap(); let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_smt_before.serialize(), transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), storage_tries: vec![(to_hashed, Node::Empty.into())], @@ -96,36 +101,34 @@ fn add11_yml() -> anyhow::Result<()> { contract_code.insert(code_hash, code.to_vec()); let expected_state_trie_after = { - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_after = Account { + nonce: 1, + ..Account::default() }; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: 0xde0b6b3a75be550u64.into(), - nonce: 1.into(), - ..AccountRlp::default() + nonce: 1, + ..Account::default() }; - let to_account_after = AccountRlp { + let to_account_after = Account { balance: 0xde0b6b3a76586a0u64.into(), code_hash, // Storage map: { 0 => 2 } - storage_root: HashedPartialTrie::from(Node::Leaf { - nibbles: Nibbles::from_h256_be(keccak([0u8; 32])), - value: vec![2], - }) - .hash(), - ..AccountRlp::default() + storage_smt: Smt::new([(keccak([0u8; 32]).into(), 2.into())]).unwrap(), + ..Account::default() }; - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - ); - expected_state_trie_after - .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); - expected_state_trie_after + let mut expected_state_smt_after = Smt::empty(); + expected_state_smt_after + .insert(beneficiary_bits, beneficiary_account_after.into()) + .unwrap(); + expected_state_smt_after + .insert(sender_bits, sender_account_after.into()) + .unwrap(); + expected_state_smt_after + .insert(to_bits, to_account_after.into()) + .unwrap(); + expected_state_smt_after }; let receipt_0 = LegacyReceiptRlp { @@ -146,7 +149,7 @@ fn add11_yml() -> anyhow::Result<()> { .into(); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_trie_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; diff --git a/evm/tests/basic_smart_contract.rs b/evm/tests/basic_smart_contract.rs index 687328dcb9..a4a58901ff 100644 --- a/evm/tests/basic_smart_contract.rs +++ b/evm/tests/basic_smart_contract.rs @@ -14,12 +14,14 @@ use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; use plonky2_evm::cpu::kernel::opcodes::{get_opcode, get_push_opcode}; -use plonky2_evm::generation::mpt::{AccountRlp, LegacyReceiptRlp}; +use plonky2_evm::generation::mpt::LegacyReceiptRlp; use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::{BlockHashes, BlockMetadata, TrieRoots}; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; use plonky2_evm::Node; +use smt_utils::account::Account; +use smt_utils::smt::Smt; type F = GoldilocksField; const D: usize = 2; @@ -42,9 +44,9 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { let sender_state_key = keccak(sender); let to_state_key = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_state_key.as_bytes()).unwrap(); + let beneficiary_bits = beneficiary_state_key.into(); + let sender_bits = sender_state_key.into(); + let to_bits = to_state_key.into(); let push1 = get_push_opcode(1); let add = get_opcode("ADD"); @@ -53,46 +55,29 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { let code_gas = 3 + 3 + 3; let code_hash = keccak(code); - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_before = Account { + nonce: 1, + ..Account::default() }; - let sender_account_before = AccountRlp { - nonce: 5.into(), + let sender_account_before = Account { + nonce: 5, balance: eth_to_wei(100_000.into()), - ..AccountRlp::default() + ..Account::default() }; - let to_account_before = AccountRlp { + let to_account_before = Account { code_hash, - ..AccountRlp::default() + ..Account::default() }; - let state_trie_before = { - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[beneficiary_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: beneficiary_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&beneficiary_account_before).to_vec(), - } - .into(); - children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: sender_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&sender_account_before).to_vec(), - } - .into(); - children[to_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: to_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&to_account_before).to_vec(), - } - .into(); - Node::Branch { - children, - value: vec![], - } - } - .into(); + let state_smt_before = Smt::new([ + (beneficiary_bits, beneficiary_account_before.clone().into()), + (sender_bits, sender_account_before.clone().into()), + (to_bits, to_account_before.clone().into()), + ]) + .unwrap(); let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_smt_before.serialize(), transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), storage_tries: vec![], @@ -122,43 +107,27 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { contract_code.insert(keccak(vec![]), vec![]); contract_code.insert(code_hash, code.to_vec()); - let expected_state_trie_after: HashedPartialTrie = { - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let expected_state_smt_after = { + let beneficiary_account_after = Account { + nonce: 1, + ..Account::default() }; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: sender_account_before.balance - value - gas_used * 10, nonce: sender_account_before.nonce + 1, ..sender_account_before }; - let to_account_after = AccountRlp { + let to_account_after = Account { balance: to_account_before.balance + value, ..to_account_before }; - - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[beneficiary_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: beneficiary_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&beneficiary_account_after).to_vec(), - } - .into(); - children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: sender_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&sender_account_after).to_vec(), - } - .into(); - children[to_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: to_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&to_account_after).to_vec(), - } - .into(); - Node::Branch { - children, - value: vec![], - } - } - .into(); + Smt::new([ + (beneficiary_bits, beneficiary_account_after.into()), + (sender_bits, sender_account_after.into()), + (to_bits, to_account_after.into()), + ]) + .unwrap() + }; let receipt_0 = LegacyReceiptRlp { status: true, @@ -178,7 +147,7 @@ fn test_basic_smart_contract() -> anyhow::Result<()> { .into(); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_smt_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs index dd4e624b04..228aaea222 100644 --- a/evm/tests/empty_txn_list.rs +++ b/evm/tests/empty_txn_list.rs @@ -17,6 +17,7 @@ use plonky2_evm::fixed_recursive_verifier::AllRecursiveCircuits; use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::{BlockHashes, BlockMetadata, TrieRoots}; use plonky2_evm::Node; +use smt_utils::smt::Smt; type F = GoldilocksField; const D: usize = 2; @@ -33,7 +34,7 @@ fn test_empty_txn_list() -> anyhow::Result<()> { let block_metadata = BlockMetadata::default(); - let state_trie = HashedPartialTrie::from(Node::Empty); + let state_smt = Smt::empty(); let transactions_trie = HashedPartialTrie::from(Node::Empty); let receipts_trie = HashedPartialTrie::from(Node::Empty); let storage_tries = vec![]; @@ -43,21 +44,21 @@ fn test_empty_txn_list() -> anyhow::Result<()> { // No transactions, so no trie roots change. let trie_roots_after = TrieRoots { - state_root: state_trie.hash(), + state_root: state_smt.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; let inputs = GenerationInputs { signed_txns: vec![], tries: TrieInputs { - state_trie, + state_smt: state_smt.serialize(), transactions_trie, receipts_trie, storage_tries, }, trie_roots_after, contract_code, - genesis_state_trie_root: HashedPartialTrie::from(Node::Empty).hash(), + genesis_state_trie_root: Smt::empty().root, block_metadata, txn_number_before: 0.into(), gas_used_before: 0.into(), diff --git a/evm/tests/log_opcode.rs b/evm/tests/log_opcode.rs index dd7ea223e4..8a41046417 100644 --- a/evm/tests/log_opcode.rs +++ b/evm/tests/log_opcode.rs @@ -18,12 +18,14 @@ use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; use plonky2_evm::fixed_recursive_verifier::AllRecursiveCircuits; use plonky2_evm::generation::mpt::transaction_testing::{AddressOption, LegacyTransactionRlp}; -use plonky2_evm::generation::mpt::{AccountRlp, LegacyReceiptRlp, LogRlp}; +use plonky2_evm::generation::mpt::{LegacyReceiptRlp, LogRlp}; use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::{BlockHashes, BlockMetadata, ExtraBlockData, PublicValues, TrieRoots}; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; use plonky2_evm::Node; +use smt_utils::account::Account; +use smt_utils::smt::Smt; type F = GoldilocksField; const D: usize = 2; @@ -47,9 +49,9 @@ fn test_log_opcodes() -> anyhow::Result<()> { let sender_state_key = keccak(sender); let to_hashed = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); + let beneficiary_bits = beneficiary_state_key.into(); + let sender_bits = sender_state_key.into(); + let to_bits = to_hashed.into(); // For the first code transaction code, we consider two LOG opcodes. The first deals with 0 topics and empty data. The second deals with two topics, and data of length 5, stored in memory. let code = [ @@ -68,30 +70,29 @@ fn test_log_opcodes() -> anyhow::Result<()> { let code_hash = keccak(code); // Set accounts before the transaction. - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_before = Account { + nonce: 1, + ..Account::default() }; let sender_balance_before = 5000000000000000u64; - let sender_account_before = AccountRlp { + let sender_account_before = Account { balance: sender_balance_before.into(), - ..AccountRlp::default() + ..Account::default() }; - let to_account_before = AccountRlp { + let to_account_before = Account { balance: 9000000000u64.into(), code_hash, - ..AccountRlp::default() + ..Account::default() }; // Initialize the state trie with three accounts. - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); - state_trie_before.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_before).to_vec(), - ); - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + let state_smt_before = Smt::new([ + (beneficiary_bits, beneficiary_account_before.clone().into()), + (sender_bits, sender_account_before.clone().into()), + (to_bits, to_account_before.clone().into()), + ]) + .unwrap(); // We now add two receipts with logs and data. This updates the receipt trie as well. let log_0 = LogRlp { @@ -121,7 +122,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { ); let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_smt_before.serialize(), transactions_trie: Node::Empty.into(), receipts_trie: receipts_trie.clone(), storage_tries: vec![(to_hashed, Node::Empty.into())], @@ -150,21 +151,21 @@ fn test_log_opcodes() -> anyhow::Result<()> { // Update the state and receipt tries after the transaction, so that we have the correct expected tries: // Update accounts - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_after = Account { + nonce: 1, + ..Account::default() }; let sender_balance_after = sender_balance_before - gas_used * txn_gas_price; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: sender_balance_after.into(), - nonce: 1.into(), - ..AccountRlp::default() + nonce: 1, + ..Account::default() }; - let to_account_after = AccountRlp { + let to_account_after = Account { balance: 9000000000u64.into(), code_hash, - ..AccountRlp::default() + ..Account::default() }; // Update the receipt trie. @@ -195,13 +196,12 @@ fn test_log_opcodes() -> anyhow::Result<()> { receipts_trie.insert(receipt_nibbles, rlp::encode(&receipt).to_vec()); // Update the state trie. - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - ); - expected_state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + let expected_state_smt_after = Smt::new([ + (beneficiary_bits, beneficiary_account_after.into()), + (sender_bits, sender_account_after.into()), + (to_bits, to_account_after.into()), + ]) + .unwrap(); let transactions_trie: HashedPartialTrie = Node::Leaf { nibbles: Nibbles::from_str("0x80").unwrap(), @@ -210,7 +210,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { .into(); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_smt_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; @@ -255,7 +255,7 @@ fn test_log_opcodes() -> anyhow::Result<()> { // Assert that the proof leads to the correct state and receipt roots. assert_eq!( proof.public_values.trie_roots_after.state_root, - expected_state_trie_after.hash() + expected_state_smt_after.root, ); assert_eq!( @@ -303,46 +303,42 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { let to_hashed = keccak(to_first); let to_hashed_2 = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); - let to_second_nibbles = Nibbles::from_bytes_be(to_hashed_2.as_bytes()).unwrap(); + let beneficiary_bits = beneficiary_state_key.into(); + let sender_bits = sender_state_key.into(); + let to_bits = to_hashed.into(); + let to_second_bits = to_hashed_2.into(); - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_before = Account { + nonce: 1, + ..Account::default() }; let sender_balance_before = 1000000000000000000u64.into(); - let sender_account_before = AccountRlp { + let sender_account_before = Account { balance: sender_balance_before, - ..AccountRlp::default() + ..Account::default() }; - let to_account_before = AccountRlp { - ..AccountRlp::default() + let to_account_before = Account { + ..Account::default() }; - let to_account_second_before = AccountRlp { + let to_account_second_before = Account { code_hash, - ..AccountRlp::default() + ..Account::default() }; // In the first transaction, the sender account sends `txn_value` to `to_account`. let gas_price = 10; let txn_value = 0xau64; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); - state_trie_before.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_before).to_vec(), - ); - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); - state_trie_before.insert( - to_second_nibbles, - rlp::encode(&to_account_second_before).to_vec(), - ); - let genesis_state_trie_root = state_trie_before.hash(); + let state_smt_before = Smt::new([ + (beneficiary_bits, beneficiary_account_before.clone().into()), + (sender_bits, sender_account_before.clone().into()), + (to_bits, to_account_before.clone().into()), + (to_second_bits, to_account_second_before.clone().into()), + ]) + .unwrap(); + let genesis_state_trie_root = state_smt_before.root; let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_smt_before.serialize(), transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), storage_tries: vec![], @@ -378,37 +374,33 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { block_random: Default::default(), }; - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_after = Account { + nonce: 1, + ..Account::default() }; let sender_balance_after = sender_balance_before - gas_price * 21000 - txn_value; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: sender_balance_after, - nonce: 1.into(), - ..AccountRlp::default() + nonce: 1, + ..Account::default() }; - let to_account_after = AccountRlp { + let to_account_after = Account { balance: txn_value.into(), - ..AccountRlp::default() + ..Account::default() }; let mut contract_code = HashMap::new(); contract_code.insert(keccak(vec![]), vec![]); contract_code.insert(code_hash, code.to_vec()); - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - ); - expected_state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); - expected_state_trie_after.insert( - to_second_nibbles, - rlp::encode(&to_account_second_before).to_vec(), - ); + let expected_state_smt_after = Smt::new([ + (beneficiary_bits, beneficiary_account_after.into()), + (sender_bits, sender_account_after.into()), + (to_bits, to_account_after.clone().into()), + (to_second_bits, to_account_second_before.clone().into()), + ]) + .unwrap(); // Compute new receipt trie. let mut receipts_trie = HashedPartialTrie::from(Node::Empty); @@ -430,7 +422,7 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { .into(); let tries_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_smt_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.clone().hash(), }; @@ -474,10 +466,10 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { // Prove second transaction. In this second transaction, the code with logs is executed. - let state_trie_before = expected_state_trie_after; + let state_trie_before = expected_state_smt_after; let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_trie_before.serialize(), transactions_trie: transactions_trie.clone(), receipts_trie: receipts_trie.clone(), storage_tries: vec![], @@ -493,26 +485,26 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { // Update the state and receipt tries after the transaction, so that we have the correct expected tries: // Update accounts. - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_after = Account { + nonce: 1, + ..Account::default() }; let sender_balance_after = sender_balance_after - gas_used * txn_gas_price; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: sender_balance_after, - nonce: 2.into(), - ..AccountRlp::default() + nonce: 2, + ..Account::default() }; let balance_after = to_account_after.balance; - let to_account_after = AccountRlp { + let to_account_after = Account { balance: balance_after, - ..AccountRlp::default() + ..Account::default() }; - let to_account_second_after = AccountRlp { + let to_account_second_after = Account { balance: to_account_second_before.balance, code_hash, - ..AccountRlp::default() + ..Account::default() }; // Update the receipt trie. @@ -542,23 +534,19 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { receipts_trie.insert(receipt_nibbles, rlp::encode(&receipt).to_vec()); - // Update the state trie. - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - ); - expected_state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); - expected_state_trie_after.insert( - to_second_nibbles, - rlp::encode(&to_account_second_after).to_vec(), - ); + // Update the state SMT. + let expected_state_trie_after = Smt::new([ + (beneficiary_bits, beneficiary_account_after.into()), + (sender_bits, sender_account_after.into()), + (to_bits, to_account_after.into()), + (to_second_bits, to_account_second_after.into()), + ]) + .unwrap(); transactions_trie.insert(Nibbles::from_str("0x01").unwrap(), txn_2.to_vec()); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_trie_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; @@ -767,36 +755,35 @@ fn test_two_txn() -> anyhow::Result<()> { let sender_state_key = keccak(sender); let to_hashed = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); + let beneficiary_bits = beneficiary_state_key.into(); + let sender_bits = sender_state_key.into(); + let to_bits = to_hashed.into(); // Set accounts before the transaction. - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_before = Account { + nonce: 1, + ..Account::default() }; let sender_balance_before = 50000000000000000u64; - let sender_account_before = AccountRlp { + let sender_account_before = Account { balance: sender_balance_before.into(), - ..AccountRlp::default() + ..Account::default() }; - let to_account_before = AccountRlp { - ..AccountRlp::default() + let to_account_before = Account { + ..Account::default() }; // Initialize the state trie with three accounts. - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); - state_trie_before.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_before).to_vec(), - ); - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + let state_smt_before = Smt::new([ + (beneficiary_bits, beneficiary_account_before.clone().into()), + (sender_bits, sender_account_before.clone().into()), + (to_bits, to_account_before.clone().into()), + ]) + .unwrap(); let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_smt_before.serialize(), transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), storage_tries: vec![(to_hashed, Node::Empty.into())], @@ -825,30 +812,29 @@ fn test_two_txn() -> anyhow::Result<()> { contract_code.insert(keccak(vec![]), vec![]); // Update accounts - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_after = Account { + nonce: 1, + ..Account::default() }; let sender_balance_after = sender_balance_before - gas_price * 21000 * 2 - txn_value * 2; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: sender_balance_after.into(), - nonce: 2.into(), - ..AccountRlp::default() + nonce: 2, + ..Account::default() }; - let to_account_after = AccountRlp { + let to_account_after = Account { balance: (2 * txn_value).into(), - ..AccountRlp::default() + ..Account::default() }; // Update the state trie. - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - ); - expected_state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + let expected_state_smt_after = Smt::new([ + (beneficiary_bits, beneficiary_account_after.into()), + (sender_bits, sender_account_after.into()), + (to_bits, to_account_after.into()), + ]) + .unwrap(); // Compute new receipt trie. let mut receipts_trie = HashedPartialTrie::from(Node::Empty); @@ -886,7 +872,7 @@ fn test_two_txn() -> anyhow::Result<()> { transactions_trie.insert(Nibbles::from_str("0x01").unwrap(), txn_1.to_vec()); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_smt_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; @@ -916,7 +902,7 @@ fn test_two_txn() -> anyhow::Result<()> { // Assert trie roots. assert_eq!( proof.public_values.trie_roots_after.state_root, - expected_state_trie_after.hash() + expected_state_smt_after.root ); assert_eq!( diff --git a/evm/tests/many_transactions.rs b/evm/tests/many_transactions.rs index 9678d652d3..7d9bfae572 100644 --- a/evm/tests/many_transactions.rs +++ b/evm/tests/many_transactions.rs @@ -16,12 +16,14 @@ use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; use plonky2_evm::cpu::kernel::opcodes::{get_opcode, get_push_opcode}; -use plonky2_evm::generation::mpt::{AccountRlp, LegacyReceiptRlp}; +use plonky2_evm::generation::mpt::LegacyReceiptRlp; use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::{BlockHashes, BlockMetadata, TrieRoots}; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; use plonky2_evm::Node; +use smt_utils::account::Account; +use smt_utils::smt::Smt; type F = GoldilocksField; const D: usize = 2; @@ -43,9 +45,9 @@ fn test_four_transactions() -> anyhow::Result<()> { let sender_state_key = keccak(sender); let to_state_key = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_state_key.as_bytes()).unwrap(); + let beneficiary_bits = beneficiary_state_key.into(); + let sender_bits = sender_state_key.into(); + let to_bits = to_state_key.into(); let push1 = get_push_opcode(1); let add = get_opcode("ADD"); @@ -54,42 +56,26 @@ fn test_four_transactions() -> anyhow::Result<()> { let code_gas = 3 + 3 + 3; let code_hash = keccak(code); - let beneficiary_account_before = AccountRlp::default(); - let sender_account_before = AccountRlp { - nonce: 5.into(), - + let beneficiary_account_before = Account::default(); + let sender_account_before = Account { + nonce: 5, balance: eth_to_wei(100_000.into()), - - ..AccountRlp::default() + ..Account::default() }; - let to_account_before = AccountRlp { + let to_account_before = Account { code_hash, - ..AccountRlp::default() + ..Account::default() }; - let state_trie_before = { - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: sender_nibbles.truncate_n_nibbles_front(1), - - value: rlp::encode(&sender_account_before).to_vec(), - } - .into(); - children[to_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: to_nibbles.truncate_n_nibbles_front(1), - - value: rlp::encode(&to_account_before).to_vec(), - } - .into(); - Node::Branch { - children, - value: vec![], - } - } - .into(); + let state_smt_before = Smt::new([ + (beneficiary_bits, beneficiary_account_before.clone().into()), + (sender_bits, sender_account_before.clone().into()), + (to_bits, to_account_before.clone().into()), + ]) + .unwrap(); let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_smt_before.serialize(), transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), storage_tries: vec![], @@ -123,46 +109,27 @@ fn test_four_transactions() -> anyhow::Result<()> { // Update trie roots after the 4 transactions. // State trie. - let expected_state_trie_after: HashedPartialTrie = { - let beneficiary_account_after = AccountRlp { + let expected_state_trie_after = { + let beneficiary_account_after = Account { balance: beneficiary_account_before.balance + gas_used * 10, ..beneficiary_account_before }; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: sender_account_before.balance - value - gas_used * 10, nonce: sender_account_before.nonce + 1, ..sender_account_before }; - let to_account_after = AccountRlp { + let to_account_after = Account { balance: to_account_before.balance + value, ..to_account_before }; - - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[beneficiary_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: beneficiary_nibbles.truncate_n_nibbles_front(1), - - value: rlp::encode(&beneficiary_account_after).to_vec(), - } - .into(); - children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: sender_nibbles.truncate_n_nibbles_front(1), - - value: rlp::encode(&sender_account_after).to_vec(), - } - .into(); - children[to_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: to_nibbles.truncate_n_nibbles_front(1), - - value: rlp::encode(&to_account_after).to_vec(), - } - .into(); - Node::Branch { - children, - value: vec![], - } - } - .into(); + Smt::new([ + (beneficiary_bits, beneficiary_account_after.into()), + (sender_bits, sender_account_after.into()), + (to_bits, to_account_after.into()), + ]) + .unwrap() + }; // Transactions trie. let mut transactions_trie: HashedPartialTrie = Node::Leaf { @@ -206,7 +173,7 @@ fn test_four_transactions() -> anyhow::Result<()> { ); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_trie_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; diff --git a/evm/tests/self_balance_gas_cost.rs b/evm/tests/self_balance_gas_cost.rs index 4492ba9af4..fdcd04b05d 100644 --- a/evm/tests/self_balance_gas_cost.rs +++ b/evm/tests/self_balance_gas_cost.rs @@ -13,12 +13,14 @@ use plonky2::plonk::config::KeccakGoldilocksConfig; use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; -use plonky2_evm::generation::mpt::{AccountRlp, LegacyReceiptRlp}; +use plonky2_evm::generation::mpt::LegacyReceiptRlp; use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::{BlockHashes, BlockMetadata, TrieRoots}; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; use plonky2_evm::Node; +use smt_utils::account::Account; +use smt_utils::smt::Smt; type F = GoldilocksField; const D: usize = 2; @@ -41,9 +43,9 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { let sender_state_key = keccak(sender); let to_hashed = keccak(to); - let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); + let beneficiary_bits = beneficiary_state_key.into(); + let sender_bits = sender_state_key.into(); + let to_bits = to_hashed.into(); let code = [ 0x5a, 0x47, 0x5a, 0x90, 0x50, 0x90, 0x03, 0x60, 0x02, 0x90, 0x03, 0x60, 0x01, 0x55, 0x00, @@ -62,29 +64,28 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { + 22100; // SSTORE let code_hash = keccak(code); - let beneficiary_account_before = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_before = Account { + nonce: 1, + ..Account::default() }; - let sender_account_before = AccountRlp { + let sender_account_before = Account { balance: 0x3635c9adc5dea00000u128.into(), - ..AccountRlp::default() + ..Account::default() }; - let to_account_before = AccountRlp { + let to_account_before = Account { code_hash, - ..AccountRlp::default() + ..Account::default() }; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); - state_trie_before.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_before).to_vec(), - ); - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + let state_smt_before = Smt::new([ + (beneficiary_bits, beneficiary_account_before.clone().into()), + (sender_bits, sender_account_before.clone().into()), + (to_bits, to_account_before.clone().into()), + ]) + .unwrap(); let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_smt_before.serialize(), transactions_trie: Node::Empty.into(), receipts_trie: Node::Empty.into(), storage_tries: vec![(to_hashed, Node::Empty.into())], @@ -112,39 +113,36 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { contract_code.insert(code_hash, code.to_vec()); let expected_state_trie_after = { - let beneficiary_account_after = AccountRlp { - nonce: 1.into(), - ..AccountRlp::default() + let beneficiary_account_after = Account { + nonce: 1, + ..Account::default() }; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: sender_account_before.balance - U256::from(gas_used) * U256::from(10), - nonce: 1.into(), - ..AccountRlp::default() + nonce: 1, + ..Account::default() }; - let to_account_after = AccountRlp { + let to_account_after = Account { code_hash, // Storage map: { 1 => 5 } - storage_root: HashedPartialTrie::from(Node::Leaf { - // TODO: Could do keccak(pad32(1)) - nibbles: Nibbles::from_str( - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", + storage_smt: Smt::new([( + U256::from_str( + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", // keccak(pad(1)) ) - .unwrap(), - value: vec![5], - }) - .hash(), - ..AccountRlp::default() + .unwrap() + .into(), + U256::from(5).into(), + )]) + .unwrap(), + ..Account::default() }; - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert( - beneficiary_nibbles, - rlp::encode(&beneficiary_account_after).to_vec(), - ); - expected_state_trie_after - .insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); - expected_state_trie_after + Smt::new([ + (beneficiary_bits, beneficiary_account_after.into()), + (sender_bits, sender_account_after.into()), + (to_bits, to_account_after.into()), + ]) + .unwrap() }; let receipt_0 = LegacyReceiptRlp { @@ -165,7 +163,7 @@ fn self_balance_gas_cost() -> anyhow::Result<()> { .into(); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_trie_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; diff --git a/evm/tests/selfdestruct.rs b/evm/tests/selfdestruct.rs index a3e467b012..88af15aca7 100644 --- a/evm/tests/selfdestruct.rs +++ b/evm/tests/selfdestruct.rs @@ -12,18 +12,22 @@ use plonky2::plonk::config::KeccakGoldilocksConfig; use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; -use plonky2_evm::generation::mpt::{AccountRlp, LegacyReceiptRlp}; +use plonky2_evm::generation::mpt::LegacyReceiptRlp; use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::{BlockHashes, BlockMetadata, TrieRoots}; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; use plonky2_evm::Node; +use smt_utils::account::Account; +use smt_utils::bits::Bits; +use smt_utils::smt::Smt; type F = GoldilocksField; const D: usize = 2; type C = KeccakGoldilocksConfig; /// Test a simple selfdestruct. +#[ignore] #[test] fn test_selfdestruct() -> anyhow::Result<()> { init_logger(); @@ -38,32 +42,34 @@ fn test_selfdestruct() -> anyhow::Result<()> { let sender_state_key = keccak(sender); let to_state_key = keccak(to); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_state_key.as_bytes()).unwrap(); + let sender_bits = Bits::from(sender_state_key); + let to_bits = Bits::from(to_state_key); - let sender_account_before = AccountRlp { - nonce: 5.into(), + let sender_account_before = Account { + nonce: 5, balance: eth_to_wei(100_000.into()), - storage_root: HashedPartialTrie::from(Node::Empty).hash(), code_hash: keccak([]), + storage_smt: Smt::empty(), }; let code = vec![ 0x32, // ORIGIN 0xFF, // SELFDESTRUCT ]; - let to_account_before = AccountRlp { - nonce: 12.into(), + let to_account_before = Account { + nonce: 12, balance: eth_to_wei(10_000.into()), - storage_root: HashedPartialTrie::from(Node::Empty).hash(), code_hash: keccak(&code), + storage_smt: Smt::empty(), }; - let mut state_trie_before = HashedPartialTrie::from(Node::Empty); - state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); - state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + let state_trie_before = Smt::new([ + (sender_bits, sender_account_before.into()), + (to_bits, to_account_before.into()), + ]) + .unwrap(); let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_trie_before.serialize(), transactions_trie: HashedPartialTrie::from(Node::Empty), receipts_trie: HashedPartialTrie::from(Node::Empty), storage_tries: vec![], @@ -87,16 +93,14 @@ fn test_selfdestruct() -> anyhow::Result<()> { let contract_code = [(keccak(&code), code), (keccak([]), vec![])].into(); - let expected_state_trie_after: HashedPartialTrie = { - let mut state_trie_after = HashedPartialTrie::from(Node::Empty); - let sender_account_after = AccountRlp { - nonce: 6.into(), + let expected_state_trie_after = { + let sender_account_after = Account { + nonce: 6, balance: eth_to_wei(110_000.into()) - 26_002 * 0xa, - storage_root: HashedPartialTrie::from(Node::Empty).hash(), code_hash: keccak([]), + storage_smt: Smt::empty(), }; - state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); - state_trie_after + Smt::new([(sender_bits, sender_account_after.into())]).unwrap() }; let receipt_0 = LegacyReceiptRlp { @@ -117,7 +121,7 @@ fn test_selfdestruct() -> anyhow::Result<()> { .into(); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_trie_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), }; diff --git a/evm/tests/simple_transfer.rs b/evm/tests/simple_transfer.rs index 80bee8afeb..b4fc49166e 100644 --- a/evm/tests/simple_transfer.rs +++ b/evm/tests/simple_transfer.rs @@ -13,12 +13,14 @@ use plonky2::plonk::config::KeccakGoldilocksConfig; use plonky2::util::timing::TimingTree; use plonky2_evm::all_stark::AllStark; use plonky2_evm::config::StarkConfig; -use plonky2_evm::generation::mpt::{AccountRlp, LegacyReceiptRlp}; +use plonky2_evm::generation::mpt::LegacyReceiptRlp; use plonky2_evm::generation::{GenerationInputs, TrieInputs}; use plonky2_evm::proof::{BlockHashes, BlockMetadata, TrieRoots}; use plonky2_evm::prover::prove; use plonky2_evm::verifier::verify_proof; use plonky2_evm::Node; +use smt_utils::account::Account; +use smt_utils::smt::Smt; type F = GoldilocksField; const D: usize = 2; @@ -39,25 +41,25 @@ fn test_simple_transfer() -> anyhow::Result<()> { let sender_state_key = keccak(sender); let to_state_key = keccak(to); - let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); - let to_nibbles = Nibbles::from_bytes_be(to_state_key.as_bytes()).unwrap(); + let sender_bits = sender_state_key.into(); + let to_bits = to_state_key.into(); - let sender_account_before = AccountRlp { - nonce: 5.into(), + let sender_account_before = Account { + nonce: 5, balance: eth_to_wei(100_000.into()), - storage_root: HashedPartialTrie::from(Node::Empty).hash(), + storage_smt: Smt::empty(), code_hash: keccak([]), }; - let to_account_before = AccountRlp::default(); + let to_account_before = Account::default(); - let state_trie_before = Node::Leaf { - nibbles: sender_nibbles, - value: rlp::encode(&sender_account_before).to_vec(), - } - .into(); + let state_smt_before = Smt::new([ + (sender_bits, sender_account_before.clone().into()), + (to_bits, to_account_before.clone().into()), + ]) + .unwrap(); let tries_before = TrieInputs { - state_trie: state_trie_before, + state_smt: state_smt_before.serialize(), transactions_trie: HashedPartialTrie::from(Node::Empty), receipts_trie: HashedPartialTrie::from(Node::Empty), storage_tries: vec![], @@ -83,36 +85,25 @@ fn test_simple_transfer() -> anyhow::Result<()> { let mut contract_code = HashMap::new(); contract_code.insert(keccak(vec![]), vec![]); - let expected_state_trie_after: HashedPartialTrie = { + let expected_state_smt_after: Smt = { let txdata_gas = 2 * 16; let gas_used = 21_000 + txdata_gas; - let sender_account_after = AccountRlp { + let sender_account_after = Account { balance: sender_account_before.balance - value - gas_used * 10, nonce: sender_account_before.nonce + 1, ..sender_account_before }; - let to_account_after = AccountRlp { + let to_account_after = Account { balance: value, ..to_account_before }; - let mut children = core::array::from_fn(|_| Node::Empty.into()); - children[sender_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: sender_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&sender_account_after).to_vec(), - } - .into(); - children[to_nibbles.get_nibble(0) as usize] = Node::Leaf { - nibbles: to_nibbles.truncate_n_nibbles_front(1), - value: rlp::encode(&to_account_after).to_vec(), - } - .into(); - Node::Branch { - children, - value: vec![], - } - .into() + Smt::new([ + (sender_bits, sender_account_after.clone().into()), + (to_bits, to_account_after.clone().into()), + ]) + .unwrap() }; let receipt_0 = LegacyReceiptRlp { @@ -133,7 +124,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { .into(); let trie_roots_after = TrieRoots { - state_root: expected_state_trie_after.hash(), + state_root: expected_state_smt_after.root, transactions_root: transactions_trie.hash(), receipts_root: receipts_trie.hash(), };