diff --git a/evm_arithmetization/src/cpu/kernel/aggregator.rs b/evm_arithmetization/src/cpu/kernel/aggregator.rs index b6b6b293c..fe25d3020 100644 --- a/evm_arithmetization/src/cpu/kernel/aggregator.rs +++ b/evm_arithmetization/src/cpu/kernel/aggregator.rs @@ -125,6 +125,9 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/mpt/insert/insert_extension.asm"), include_str!("asm/mpt/insert/insert_leaf.asm"), include_str!("asm/mpt/insert/insert_trie_specific.asm"), + include_str!("asm/mpt/linked_list/linked_list.asm"), + include_str!("asm/mpt/linked_list/initial_tries.asm"), + include_str!("asm/mpt/linked_list/final_tries.asm"), include_str!("asm/mpt/read.asm"), include_str!("asm/mpt/storage/storage_read.asm"), include_str!("asm/mpt/storage/storage_write.asm"), diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/access_lists.asm b/evm_arithmetization/src/cpu/kernel/asm/core/access_lists.asm index 552cbc7d8..d54d63a39 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/access_lists.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/access_lists.asm @@ -21,8 +21,7 @@ global init_access_lists: // Store @U256_MAX at the beginning of the segment PUSH @SEGMENT_ACCESSED_ADDRESSES // ctx == virt == 0 DUP1 - PUSH @U256_MAX - MSTORE_GENERAL + %mstore_u256_max // Store @SEGMENT_ACCESSED_ADDRESSES at address 1 %increment DUP1 @@ -38,8 +37,7 @@ global init_access_lists: // Store @U256_MAX at the beginning of the segment PUSH @SEGMENT_ACCESSED_STORAGE_KEYS // ctx == virt == 0 DUP1 - PUSH @U256_MAX - MSTORE_GENERAL + %mstore_u256_max // Store @SEGMENT_ACCESSED_STORAGE_KEYS at address 3 %add_const(3) DUP1 @@ -201,8 +199,7 @@ global remove_accessed_addresses: MLOAD_GENERAL // stack: next_next_ptr, next_next_ptr_ptr, next_ptr_ptr, addr, retdest SWAP1 - PUSH @U256_MAX - MSTORE_GENERAL + %mstore_u256_max // stack: next_next_ptr, next_ptr_ptr, addr, retdest MSTORE_GENERAL POP @@ -385,8 +382,7 @@ global remove_accessed_storage_keys: MLOAD_GENERAL // stack: next_next_ptr, next_next_ptr_ptr, next_ptr_ptr, addr, key, retdest SWAP1 - PUSH @U256_MAX - MSTORE_GENERAL + %mstore_u256_max // stack: next_next_ptr, next_ptr_ptr, addr, key, retdest MSTORE_GENERAL %pop2 diff --git a/evm_arithmetization/src/cpu/kernel/asm/core/util.asm b/evm_arithmetization/src/cpu/kernel/asm/core/util.asm index df1c53396..871784301 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/core/util.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/core/util.asm @@ -93,3 +93,9 @@ SET_CONTEXT // stack: (empty) %endmacro + +%macro mstore_u256_max + // stack: addr + PUSH @U256_MAX + MSTORE_GENERAL +%endmacro \ No newline at end of file diff --git a/evm_arithmetization/src/cpu/kernel/asm/journal/account_destroyed.asm b/evm_arithmetization/src/cpu/kernel/asm/journal/account_destroyed.asm index 3806a891d..15fd87625 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/journal/account_destroyed.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/journal/account_destroyed.asm @@ -16,7 +16,10 @@ revert_account_destroyed_contd: SWAP1 // Remove `prev_balance` from `target`'s balance. // stack: target, address, prev_balance, retdest - %mpt_read_state_trie + %read_accounts_linked_list + // stack: target_payload_ptr, address, prev_balance, retdest + DUP1 + %assert_nonzero %add_const(1) // stack: target_balance_ptr, address, prev_balance, retdest DUP3 @@ -25,8 +28,12 @@ revert_account_destroyed_contd: SUB SWAP1 %mstore_trie_data // Set `address`'s balance to `prev_balance`. // stack: address, prev_balance, retdest - %mpt_read_state_trie - %add_const(1) + %read_accounts_linked_list + // stack: account_payload_ptr, prev_balance, retdest + DUP1 + %assert_nonzero + %increment + // stack: account_balance_payload_ptr, prev_balance, retdest %mstore_trie_data JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/journal/code_change.asm b/evm_arithmetization/src/cpu/kernel/asm/journal/code_change.asm index 5bb637c72..0fc33f9dd 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/journal/code_change.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/journal/code_change.asm @@ -9,7 +9,9 @@ global revert_code_change: POP %journal_load_2 // stack: address, prev_codehash, retdest - %mpt_read_state_trie + %read_accounts_linked_list + // stack: account_ptr, prev_codehash, retdest + DUP1 %assert_nonzero // stack: account_ptr, prev_codehash, retdest %add_const(3) // stack: codehash_ptr, prev_codehash, retdest diff --git a/evm_arithmetization/src/cpu/kernel/asm/journal/nonce_change.asm b/evm_arithmetization/src/cpu/kernel/asm/journal/nonce_change.asm index 3ab8f1367..397957e0b 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/journal/nonce_change.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/journal/nonce_change.asm @@ -9,8 +9,10 @@ global revert_nonce_change: POP %journal_load_2 // stack: address, prev_nonce, retdest - %mpt_read_state_trie - // stack: nonce_ptr, prev_nonce retdest + %read_accounts_linked_list + // stack: payload_ptr, prev_nonce, retdest + DUP1 %assert_nonzero + // stack: nonce_ptr, prev_nonce, retdest %mstore_trie_data // stack: retdest JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/journal/storage_change.asm b/evm_arithmetization/src/cpu/kernel/asm/journal/storage_change.asm index 752674d1e..afe2ca130 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/journal/storage_change.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/journal/storage_change.asm @@ -11,47 +11,16 @@ global revert_storage_change: // stack: address, slot, prev_value, retdest DUP3 ISZERO %jumpi(delete) // stack: address, slot, prev_value, retdest - SWAP1 %slot_to_storage_key - // stack: storage_key, address, prev_value, retdest - PUSH 64 // storage_key has 64 nibbles - // stack: 64, storage_key, address, prev_value, retdest - DUP3 %mpt_read_state_trie - DUP1 ISZERO %jumpi(panic) - // stack: account_ptr, 64, storage_key, address, prev_value, retdest - %add_const(2) - // stack: storage_root_ptr_ptr, 64, storage_key, address, prev_value, retdest - %mload_trie_data - %get_trie_data_size - DUP6 %append_to_trie_data - %stack (prev_value_ptr, storage_root_ptr, num_nibbles, storage_key, address, prev_value, retdest) -> - (storage_root_ptr, num_nibbles, storage_key, prev_value_ptr, new_storage_root, address, retdest) - %jump(mpt_insert) + %insert_slot_with_value + // stack: value_ptr + POP + JUMP delete: // stack: address, slot, prev_value, retdest SWAP2 POP - %stack (slot, address, retdest) -> (slot, new_storage_root, address, retdest) + // stack: slot, address, retdest %slot_to_storage_key - // stack: storage_key, new_storage_root, address, retdest - PUSH 64 // storage_key has 64 nibbles - // stack: 64, storage_key, new_storage_root, address, retdest - DUP4 %mpt_read_state_trie - DUP1 ISZERO %jumpi(panic) - // stack: account_ptr, 64, storage_key, new_storage_root, address, retdest - %add_const(2) - // stack: storage_root_ptr_ptr, 64, storage_key, new_storage_root, address, retdest - %mload_trie_data - // stack: storage_root_ptr, 64, storage_key, new_storage_root, address, retdest - %jump(mpt_delete) - -new_storage_root: - // stack: new_storage_root_ptr, address, retdest - DUP2 %mpt_read_state_trie - // stack: account_ptr, new_storage_root_ptr, address, retdest - - // Update account with our new storage root pointer. - %add_const(2) - // stack: account_storage_root_ptr_ptr, new_storage_root_ptr, address, retdest - %mstore_trie_data - // stack: address, retdest - POP JUMP + SWAP1 %addr_to_state_key + // stack: addr_key, slot_key, retdest + %jump(remove_slot) diff --git a/evm_arithmetization/src/cpu/kernel/asm/main.asm b/evm_arithmetization/src/cpu/kernel/asm/main.asm index 849eed9ae..3ecc23de3 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/main.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/main.asm @@ -67,14 +67,23 @@ global main: // Initialize the RLP DATA pointer to its initial position, // skipping over the preinitialized empty node. PUSH @INITIAL_TXN_RLP_ADDR + %add_const(@MAX_RLP_BLOB_SIZE) %mstore_global_metadata(@GLOBAL_METADATA_RLP_DATA_SIZE) // Encode constant nodes %initialize_rlp_segment + + // Initialize trie data size. + PROVER_INPUT(trie_ptr::trie_data_size) + %mstore_global_metadata(@GLOBAL_METADATA_TRIE_DATA_SIZE) + +global store_initial: + // Store the initial accounts and slots for hashing later + %store_initial_accounts + %store_initial_slots - // Initialize the state, transaction and receipt trie root pointers. - PROVER_INPUT(trie_ptr::state) - %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) +global after_store_initial: + // Initialize the transaction and receipt trie root pointers. PROVER_INPUT(trie_ptr::txn) %mstore_global_metadata(@GLOBAL_METADATA_TXN_TRIE_ROOT) PROVER_INPUT(trie_ptr::receipt) @@ -83,16 +92,16 @@ global main: global hash_initial_tries: // We compute the length of the trie data segment in `mpt_hash` so that we // can check the value provided by the prover. - // We initialize the segment length with 1 because the segment contains - // the null pointer `0` when the tries are empty. - PUSH 1 - %mpt_hash_state_trie %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) %assert_eq + // The trie data segment is already written by the linked lists + %get_trie_data_size + // stack: trie_data_len %mpt_hash_txn_trie %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE) %assert_eq // stack: trie_data_len %mpt_hash_receipt_trie %mload_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_BEFORE) %assert_eq // stack: trie_data_full_len - %mstore_global_metadata(@GLOBAL_METADATA_TRIE_DATA_SIZE) + + %set_trie_data_size global start_txns: // stack: (empty) @@ -144,10 +153,25 @@ global perform_final_checks: // stack: cum_gas, txn_counter, num_nibbles, txn_nb // Check that we end up with the correct `cum_gas`, `txn_nb` and bloom filter. %mload_global_metadata(@GLOBAL_METADATA_BLOCK_GAS_USED_AFTER) %assert_eq - DUP3 %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_AFTER) %assert_eq + DUP3 + %mload_global_metadata(@GLOBAL_METADATA_TXN_NUMBER_AFTER) %assert_eq %pop3 + + PROVER_INPUT(trie_ptr::state) + + %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %set_initial_tries + %get_trie_data_size + %mpt_hash_state_trie + + SWAP1 %set_trie_data_size + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_BEFORE) + %assert_eq + PUSH 1 // initial trie data length + global check_state_trie: + %set_final_tries %mpt_hash_state_trie %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER) %assert_eq global check_txn_trie: %mpt_hash_txn_trie %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_AFTER) %assert_eq diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm index 0ee987b4c..15584872a 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/accounts.asm @@ -19,3 +19,44 @@ %mload_trie_data // stack: storage_root_ptr %endmacro + +%macro clone_account + // stack: account_ptr + %get_trie_data_size + // stack: cloned_account_ptr, account_ptr + SWAP1 + DUP1 + // stack: nonce_ptr, account_ptr, cloned_account_ptr + // Nonce + %mload_trie_data + %append_to_trie_data + // stack: account_ptr, cloned_account_ptr + %increment + // Balance + DUP1 + %mload_trie_data + %append_to_trie_data + // Storage trie root + %increment + DUP1 + %mload_trie_data + %append_to_trie_data + // Codehash + %increment + %mload_trie_data + %append_to_trie_data + // stack: cloned_account_ptr +%endmacro + +// The slot_ptr cannot be 0, because `insert_slot` +// is only called in `revert_storage_change` (where the case `slot_ptr = 0` +// is dealt with differently), and in `storage_write`, +// where writing 0 actually corresponds to a `delete`. +%macro clone_slot + // stack: slot_ptr + %get_trie_data_size + // stack: cloned_slot_ptr, slot_ptr + SWAP1 + %mload_trie_data + %append_to_trie_data +%endmacro diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/delete/delete.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/delete/delete.asm index 913ba1fcf..ffef18bbf 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/delete/delete.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/delete/delete.asm @@ -24,17 +24,13 @@ mpt_delete_leaf: SWAP1 JUMP global delete_account: - %stack (address, retdest) -> (address, delete_account_save, retdest) %addr_to_state_key - // stack: key, delete_account_save, retdest - PUSH 64 - // stack: 64, key, delete_account_save, retdest - %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) - // stack: state_root_prt, 64, key, delete_account_save, retdest - %jump(mpt_delete) -delete_account_save: - // stack: updated_state_root_ptr, retdest - %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + DUP1 + %remove_account_from_linked_list + // stack: addr_to_state_key, retdest + + // Now we also need to remove all the storage nodes associated with the deleted account. + %remove_all_account_slots JUMP %macro delete_account diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm index cd07c01fd..b9bed9ae2 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm @@ -101,10 +101,6 @@ global encode_account: DUP3 %add_const(2) %mload_trie_data // storage_root_ptr = value[2] // stack: storage_root_ptr, cur_len, rlp_pos_5, value_ptr, cur_len, retdest - - PUSH debug_after_hash_storage_trie - POP - // Hash storage trie. %mpt_hash_storage_trie // stack: storage_root_digest, new_len, rlp_pos_5, value_ptr, cur_len, retdest diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm index 71f78ec5b..e1e82b562 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm @@ -6,15 +6,7 @@ // TODO: Have this take an address and do %mpt_insert_state_trie? To match mpt_read_state_trie. global mpt_insert_state_trie: // stack: key, value_ptr, retdest - %stack (key, value_ptr) - -> (key, value_ptr, mpt_insert_state_trie_save) - PUSH 64 // num_nibbles - %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) - // stack: state_root_ptr, num_nibbles, key, value_ptr, mpt_insert_state_trie_save, retdest - %jump(mpt_insert) -mpt_insert_state_trie_save: - // stack: updated_node_ptr, retdest - %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %insert_account_with_overwrite JUMP %macro mpt_insert_state_trie diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/final_tries.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/final_tries.asm new file mode 100644 index 000000000..4211baec3 --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/final_tries.asm @@ -0,0 +1,233 @@ +// Given a pointer `root_ptr` to the root of a trie, insert all accounts in +// the accounts_linked_list starting at `account_ptr_ptr` as well as the +// respective storage slots in `storage_ptr_ptr`. +// Pre stack: account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest +// Post stack: new_root_ptr. +global insert_all_accounts: + // stack: account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest + SWAP2 + DUP3 + MLOAD_GENERAL + // stack: key, storage_ptr_ptr, root_ptr, account_ptr_ptr, retdest + DUP1 + %eq_const(@U256_MAX) + %jumpi(no_more_accounts) + // stack: key, storage_ptr_ptr, root_ptr, account_ptr_ptr, retdest + DUP4 + %increment + MLOAD_GENERAL + // stack: account_ptr, key, storage_ptr_ptr, root_ptr, account_ptr_ptr, retdest + %add_const(2) + DUP1 + %mload_trie_data + // stack: storage_root_ptr, storage_root_ptr_ptr, key, storage_ptr_ptr, root_ptr, account_ptr_ptr, retdest + %stack + (storage_root_ptr, storage_root_ptr_ptr, key, storage_ptr_ptr) -> + (key, storage_ptr_ptr, storage_root_ptr, after_insert_all_slots, storage_root_ptr_ptr, key) + %jump(insert_all_slots) + +after_insert_all_slots: + // stack: storage_ptr_ptr', storage_root_ptr', storage_root_ptr_ptr, key, root_ptr, account_ptr_ptr, retdest + SWAP2 + %mstore_trie_data + // stack: storage_ptr_ptr', key, root_ptr, account_ptr_ptr, retdest + DUP4 + %increment + MLOAD_GENERAL + %stack + (payload_ptr, storage_ptr_ptr_p, key, root_ptr, account_ptr_ptr) -> + (root_ptr, 64, key, payload_ptr, after_insert_account, account_ptr_ptr, storage_ptr_ptr_p) + %jump(mpt_insert) +after_insert_account: + // stack: root_ptr', account_ptr_ptr, storage_ptr_ptr', retdest + SWAP1 + %next_account + // stack: account_ptr_ptr', root_ptr', storage_ptr_ptr', retdest + %jump(insert_all_accounts) + +no_more_accounts: + // stack: key, storage_ptr_ptr, root_ptr, account_ptr_ptr, retdest + %stack (key, storage_ptr_ptr, root_ptr, account_ptr_ptr, retdest) ->(retdest, root_ptr) + JUMP + +// Insert all slots before the account key changes +// Pre stack: addr, storage_ptr_ptr, root_ptr, retdest +// Post stack: storage_ptr_ptr', root_ptr' +global insert_all_slots: + DUP2 + MLOAD_GENERAL + DUP2 + EQ // Check that the node addres is the same as `addr` + %jumpi(insert_next_slot) + // The addr has changed, meaning that we've inserted all slots for addr + // stack: addr, storage_ptr_ptr, root_ptr, retdest + %stack (addr, storage_ptr_ptr, root_ptr, retdest) -> (retdest, storage_ptr_ptr, root_ptr) + JUMP + +insert_next_slot: + // stack: addr, storage_ptr_ptr, root_ptr, retdest + DUP2 + %increment + MLOAD_GENERAL + // key, addr, storage_ptr_ptr, root_ptr, retdest + DUP3 + %add_const(2) + MLOAD_GENERAL + // stack: payload_ptr, key, addr, storage_ptr_ptr, root_ptr, retdest + %stack (payload_ptr, key, addr, storage_ptr_ptr, root_ptr) -> (root_ptr, 64, key, payload_ptr, after_insert_slot, storage_ptr_ptr, addr) + %jump(mpt_insert) +after_insert_slot: + // stack: root_ptr', storage_ptr_ptr, addr, retdest + SWAP1 + %next_slot + // stack: storage_ptr_ptr', root_ptr', addr + %stack (storage_ptr_ptr_p, root_ptr_p, addr) -> (addr, storage_ptr_ptr_p, root_ptr_p) + %jump(insert_all_slots) + +// Delete all the accounts, referenced by the respective nodes in the linked list starting at +// `account_ptr_ptr`, which where deleted from the initial state. Delete also all slots of non-deleted accounts +// deleted from the storage trie. +// Pre stack: account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest +// Post stack: new_root_ptr. +global delete_removed_accounts: + // stack: account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest + DUP1 + // We assume that the size of the initial accounts linked list, containing the accounts + // of the initial state, was stored at `@GLOBAL_METADATA_INITIAL_ACCOUNTS_LINKED_LIST_LEN`. + %mload_global_metadata(@GLOBAL_METADATA_INITIAL_ACCOUNTS_LINKED_LIST_LEN) + // The initial accounts linked list was stored at addresses smaller than `@GLOBAL_METADATA_INITIAL_ACCOUNTS_LINKED_LIST_LEN`. + // If we also know that `@SEGMENT_ACCOUNT_LINKED_LIST <= account_ptr_ptr`, for deleting node at `addr_ptr_ptr` it + // suffices to check that `account_ptr_ptr` != `@GLOBAL_METADATA_INITIAL_ACCOUNTS_LINKED_LIST_LEN` + EQ + %jumpi(delete_removed_accounts_end) + // stack: account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest + DUP1 + %next_account + %eq_const(@U256_MAX) // If the next node pointer is @U256_MAX, the node was deleted + %jumpi(delete_account) + // The account is still there so we need to delete any removed slot. + // stack: account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest + DUP1 + MLOAD_GENERAL + // stack: key, account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest + DUP2 + %add_const(2) + MLOAD_GENERAL // get intitial payload_ptr + %add_const(2) // storage_root_ptr_ptr = payload_ptr + 2 + %mload_trie_data + // stack: storage_root_ptr, key, account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest + DUP3 + %increment + MLOAD_GENERAL // get dynamic payload_ptr + %add_const(2) // storage_root_ptr_ptr = dyn_payload_ptr + 2 + %stack + (storage_root_ptr_ptr, storage_root_ptr, key, account_ptr_ptr, root_ptr, storage_ptr_ptr) -> + (key, storage_root_ptr, storage_ptr_ptr, after_delete_removed_slots, storage_root_ptr_ptr, account_ptr_ptr, root_ptr) + %jump(delete_removed_slots) +after_delete_removed_slots: + // stack: storage_root_ptr', storage_ptr_ptr', storage_root_ptr_ptr, account_ptr_ptr, root_ptr, retdest + SWAP1 SWAP2 + // stack: storage_root_ptr_ptr, storage_root_ptr', storage_ptr_ptr', account_ptr_ptr, root_ptr, retdest + %mstore_trie_data + // stack: storage_ptr_ptr', account_ptr_ptr, root_ptr, retdest + SWAP1 + %add_const(@ACCOUNTS_LINKED_LISTS_NODE_SIZE) // The next account in memory + // stack: account_ptr_ptr', storage_ptr_ptr', root_ptr, retdest + SWAP1 SWAP2 SWAP1 + %jump(delete_removed_accounts) + +delete_removed_accounts_end: + // stack: account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest + %stack (account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest) -> (retdest, root_ptr) + JUMP +delete_account: + // stack: account_ptr_ptr, root_ptr, storage_ptr_ptr, retdest + DUP1 + MLOAD_GENERAL + %stack (key, account_ptr_ptr, root_ptr) -> (root_ptr, 64, key, after_mpt_delete, account_ptr_ptr) + // Pre stack: node_ptr, num_nibbles, key, retdest + // Post stack: updated_node_ptr + %jump(mpt_delete) +after_mpt_delete: + // stack: root_ptr', account_ptr_ptr, storage_ptr_ptr, retdest + SWAP1 + %add_const(@ACCOUNTS_LINKED_LISTS_NODE_SIZE) + %jump(delete_removed_accounts) + +// Delete all slots in `storage_ptr_ptr` with address == `addr` and +// `storage_ptr_ptr` < `@GLOBAL_METADATA_INITIAL_STORAGE_LINKED_LIST_LEN`. +// Pre stack: addr, root_ptr, storage_ptr_ptr, retdest +// Post stack: new_root_ptr, storage_ptr_ptr'. +delete_removed_slots: + // stack: addr, root_ptr, storage_ptr_ptr, retdest + DUP3 + MLOAD_GENERAL + // stack: address, addr, root_ptr, storage_ptr_ptr, retdest + DUP2 + EQ + // stack: loaded_address == addr, addr, root_ptr, storage_ptr_ptr, retdest + %mload_global_metadata(@GLOBAL_METADATA_INITIAL_STORAGE_LINKED_LIST_LEN) + DUP5 + LT + MUL // AND + // stack: loaded_address == addr AND storage_ptr_ptr < GLOBAL_METADATA_INITIAL_STORAGE_LINKED_LIST_LEN, addr, root_ptr, storage_ptr_ptr, retdest + // jump if we either change the address or reach the end of the initial linked list + %jumpi(maybe_delete_this_slot) + // If we are here we have deleted all the slots for this key + %stack (addr, root_ptr, storage_ptr_ptr, retdest) -> (retdest, root_ptr, storage_ptr_ptr) + JUMP +maybe_delete_this_slot: + // stack: addr, root_ptr, storage_ptr_ptr, retdest + DUP3 + %next_slot + %eq_const(@U256_MAX) // Check if the node was deleted + %jumpi(delete_this_slot) + // The slot was not deleted, so we skip it. + // stack: addr, root_ptr, storage_ptr_ptr, retdest + SWAP2 + %add_const(@STORAGE_LINKED_LISTS_NODE_SIZE) + SWAP2 + %jump(delete_removed_slots) +delete_this_slot: + // stack: addr, root_ptr, storage_ptr_ptr, retdest + DUP3 + %increment + MLOAD_GENERAL + %stack (key, addr, root_ptr, storage_ptr_ptr) -> (root_ptr, 64, key, after_mpt_delete_slot, addr, storage_ptr_ptr) + %jump(mpt_delete) +after_mpt_delete_slot: + // stack: root_ptr', addr, storage_ptr_ptr + SWAP2 + %add_const(@STORAGE_LINKED_LISTS_NODE_SIZE) + %stack (storage_ptr_ptr_p, addr, root_ptr_p) -> (addr, root_ptr_p, storage_ptr_ptr_p) + %jump(delete_removed_slots) + +global set_final_tries: + PUSH set_final_tries_after + PUSH @SEGMENT_STORAGE_LINKED_LIST + %add_const(@STORAGE_LINKED_LISTS_NODE_SIZE) // Skip the first node. + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + PUSH @SEGMENT_ACCOUNTS_LINKED_LIST + %add_const(@ACCOUNTS_LINKED_LISTS_NODE_SIZE) // Skip the first node. + %jump(delete_removed_accounts) +set_final_tries_after: + // stack: new_state_root + PUSH set_final_tries_after_after SWAP1 + // stack: new_state_root, set_final_tries_after_after + PUSH @SEGMENT_STORAGE_LINKED_LIST + %next_slot + SWAP1 + PUSH @SEGMENT_ACCOUNTS_LINKED_LIST + %next_account + %jump(insert_all_accounts) +set_final_tries_after_after: + //stack: new_state_root + %mstore_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + JUMP + +%macro set_final_tries + // stack: (empty) + PUSH %%after + %jump(set_final_tries) +%%after: +%endmacro diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/initial_tries.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/initial_tries.asm new file mode 100644 index 000000000..e87b9dcea --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/initial_tries.asm @@ -0,0 +1,188 @@ +// Set the payload pointers of the leaves in the trie with root at `node_ptr` +// to mem[payload_ptr_ptr] + step*i, +// for i =0..n_leaves. This is used to constraint the +// initial state and account tries payload pointers such that they are exactly +// those of the initial accounts and linked lists. +// Pre stack: node_ptr, account_ptr_ptr, storage_ptr_ptr, retdest +// Post stack: account_ptr_ptr, storage_ptr_ptr +global mpt_set_payload: + // stack: node_ptr, account_ptr_ptr, storage_ptr_ptr, retdest + DUP1 %mload_trie_data + // stack: node_type, node_ptr, account_ptr_ptr, storage_ptr_ptr, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %increment SWAP1 + // stack: node_type, after_node_type, account_ptr_ptr, storage_ptr_ptr, retdest + + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(skip) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(set_payload_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(set_payload_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(set_payload_leaf) + DUP1 %eq_const(@MPT_NODE_HASH) %jumpi(skip) + PANIC + +skip: + // stack: node_type, after_node_type, account_ptr_ptr, storage_ptr_ptr, retdest + %stack (node_type, after_node_type, account_ptr_ptr, storage_ptr_ptr, retdest) -> (retdest, account_ptr_ptr, storage_ptr_ptr) + JUMP + +%macro mpt_set_payload + %stack(node_ptr, account_ptr_ptr, storage_ptr_ptr) -> (node_ptr, account_ptr_ptr, storage_ptr_ptr, %%after) + %jump(mpt_set_payload) +%%after: +%endmacro + +%macro set_initial_tries + PUSH %%after + PUSH @SEGMENT_STORAGE_LINKED_LIST + %add_const(8) // The first node is the special node, of size 5, so the first payload is at position 5 + 3. + PUSH @SEGMENT_ACCOUNTS_LINKED_LIST + %add_const(6) // The first node is the special node, of size 4, so the first payload is at position 4 + 2. + %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) + %jump(mpt_set_payload) +%%after: + // We store account_ptr_ptr - 2, i.e. a pointer to the first node not in the initial state. + %sub_const(2) + %mstore_global_metadata(@GLOBAL_METADATA_INITIAL_ACCOUNTS_LINKED_LIST_LEN) + // We store storage_ptr_ptr - 3, i.e. a pointer to the first node not in the initial state. + %sub_const(3) + %mstore_global_metadata(@GLOBAL_METADATA_INITIAL_STORAGE_LINKED_LIST_LEN) +%endmacro + +// Pre stack: node_ptr, storage_ptr_ptr, retdest +// Post stack: storage_ptr_ptr +global mpt_set_storage_payload: + // stack: node_ptr, storage_ptr_ptr, retdest + DUP1 %mload_trie_data + // stack: node_type, node_ptr, storage_ptr_ptr, retdest + // Increment node_ptr, so it points to the node payload instead of its type. + SWAP1 %increment SWAP1 + // stack: node_type, after_node_type, storage_ptr_ptr, retdest + + DUP1 %eq_const(@MPT_NODE_EMPTY) %jumpi(storage_skip) + DUP1 %eq_const(@MPT_NODE_BRANCH) %jumpi(set_payload_storage_branch) + DUP1 %eq_const(@MPT_NODE_EXTENSION) %jumpi(set_payload_storage_extension) + DUP1 %eq_const(@MPT_NODE_LEAF) %jumpi(set_payload_storage_leaf) + +storage_skip: + // stack: node_type, after_node_type, storage_ptr_ptr, retdest + %stack (node_type, after_node_type, storage_ptr_ptr, retdest) -> (retdest, storage_ptr_ptr) + JUMP + +%macro mpt_set_storage_payload + %stack(node_ptr, storage_ptr_ptr) -> (node_ptr, storage_ptr_ptr, %%after) + %jump(mpt_set_storage_payload) +%%after: +%endmacro + +set_payload_branch: + // stack: node_type, after_node_type, account_ptr_ptr, storage_ptr_ptr, retdest + POP + + // Call mpt_set_payload on each child + %rep 16 + %stack + (child_ptr_ptr, account_ptr_ptr, storage_ptr_ptr) -> + (child_ptr_ptr, account_ptr_ptr, storage_ptr_ptr, child_ptr_ptr) + // stack: child_ptr_ptr, account_ptr_ptr, storage_ptr_ptr, child_ptr_ptr, retdest + %mload_trie_data + // stack: child_ptr, account_ptr_ptr, storage_ptr_ptr, child_ptr_ptr, retdest + %mpt_set_payload + // stack: account_ptr_ptr', storage_ptr_ptr', child_ptr_ptr, retdest + SWAP1 + SWAP2 + %increment + %endrep + // stack: child_ptr_ptr', account_ptr_ptr', storage_ptr_ptr', retdest + %stack (child_ptr_ptr, account_ptr_ptr, storage_ptr_ptr, retdest) -> (retdest, account_ptr_ptr, storage_ptr_ptr) + JUMP + +set_payload_storage_branch: + // stack: node_type, child_ptr_ptr, storage_ptr_ptr, retdest + POP + + // Call mpt_set_storage_payload on each child + %rep 16 + %stack + (child_ptr_ptr, storage_ptr_ptr) -> + (child_ptr_ptr, storage_ptr_ptr, child_ptr_ptr) + // stack: child_ptr_ptr, storage_ptr_ptr, child_ptr_ptr, retdest + %mload_trie_data + // stack: child_ptr, storage_ptr_ptr, child_ptr_ptr, retdest + %mpt_set_storage_payload + // stack: storage_ptr_ptr', child_ptr_ptr, retdest + SWAP1 + %increment + %endrep + // stack: child_ptr_ptr', storage_ptr_ptr', retdest + %stack (child_ptr_ptr, storage_ptr_ptr, retdest) -> (retdest, storage_ptr_ptr) + JUMP + +set_payload_extension: + // stack: node_type, after_node_type, account_ptr_ptr, storage_ptr_ptr, retdest + POP + // stack: after_node_type, account_ptr_ptr, storage_ptr_ptr, retdest + %add_const(2) %mload_trie_data + // stack: child_ptr, after_node_type, account_ptr_ptr, storage_ptr_ptr, retdest + %jump(mpt_set_payload) + +set_payload_storage_extension: + // stack: node_type, after_node_type, storage_ptr_ptr, retdest + POP + // stack: after_node_type, storage_ptr_ptr, retdest + %add_const(2) %mload_trie_data + // stack: child_ptr, storage_ptr_ptr, retdest + %jump(mpt_set_storage_payload) + +set_payload_leaf: + // stack: node_type, after_node_type, account_ptr_ptr, storage_ptr_ptr, retdest + POP + %add_const(2) // The payload pointer starts at index 3, after num_nibbles and packed_nibbles. + DUP1 + // stack: payload_ptr_ptr, payload_ptr_ptr, account_ptr_ptr, storage_ptr_ptr, retdest + %mload_trie_data + // stack: account_ptr, payload_ptr_ptr, account_ptr_ptr, storage_ptr_ptr, retdest + %add_const(2) + %mload_trie_data // storage_root_ptr = account[2] + + // stack: storage_root_ptr, payload_ptr_ptr, account_ptr_ptr, storage_ptr_ptr, retdest + %stack + (storage_root_ptr, payload_ptr_ptr, account_ptr_ptr, storage_ptr_ptr) -> + (storage_root_ptr, storage_ptr_ptr, after_set_storage_payload, storage_root_ptr, payload_ptr_ptr, account_ptr_ptr) + %jump(mpt_set_storage_payload) +after_set_storage_payload: + // stack: storage_ptr_ptr', storage_root_ptr, payload_ptr_ptr, account_ptr_ptr, retdest + DUP4 + MLOAD_GENERAL // load the next payload pointer in the linked list + DUP1 %add_const(2) // new_storage_root_ptr_ptr = payload_ptr[2] + // stack: new_storage_root_ptr_ptr, new_payload_ptr, storage_root_ptr, storage_ptr_ptr', payload_ptr_ptr, account_ptr_ptr, retdest + // Load also the old "dynamic" payload for storing the storage_root_ptr + DUP6 %decrement + MLOAD_GENERAL + %add_const(2) // dyn_storage_root_ptr_ptr = dyn_paylod_ptr[2] + %stack + (dyn_storage_root_ptr_ptr, new_storage_root_ptr_ptr, new_payload_ptr, storage_ptr_ptr_p, storage_root_ptr, payload_ptr_ptr, account_ptr_ptr) -> + (new_storage_root_ptr_ptr, storage_root_ptr, dyn_storage_root_ptr_ptr, storage_root_ptr, payload_ptr_ptr, new_payload_ptr, account_ptr_ptr, storage_ptr_ptr_p) + %mstore_trie_data // The initial account pointer in the linked list has no storage root so we need to manually set it. + %mstore_trie_data // The dynamic account pointer in the linked list has no storage root so we need to manually set it. + %mstore_trie_data // Set the leaf payload pointing to next account in the linked list. + // stack: account_ptr_ptr, storage_ptr_ptr', retdest + %add_const(@ACCOUNTS_LINKED_LISTS_NODE_SIZE) // The next pointer is at distance `ACCOUNTS_LINKED_LISTS_NODE_SIZE` + // stack: payload_ptr_ptr', storage_ptr_ptr', retdest + SWAP1 + SWAP2 + JUMP + +set_payload_storage_leaf: + // stack: node_type, after_node_type, storage_ptr_ptr, retdest + POP + // stack: after_node_type, storage_ptr_ptr, retdest + %add_const(2) // The value pointer starts at index 3, after num_nibbles and packed_nibbles. + DUP2 + MLOAD_GENERAL + SWAP1 + %mstore_trie_data + // stack: storage_ptr_ptr, retdest + %add_const(@STORAGE_LINKED_LISTS_NODE_SIZE) // The next pointer is at distance `STORAGE_LINKED_LISTS_NODE_SIZE` + // stack: storage_ptr_ptr', retdest + SWAP1 + JUMP diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm new file mode 100644 index 000000000..9d7026dac --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm @@ -0,0 +1,916 @@ +/// Linked lists for accounts and storage slots. +/// The accounts linked list is stored in SEGMENT_ACCOUNTS_LINKED_LIST while the slots +/// are stored in SEGMENT_STORAGE_LINKED_LIST. The length of +/// the segments is stored in the associated global metadata. +/// Both arrays are stored in the kernel memory (context=0). +/// Searching and inserting is done by guessing the predecessor in the list. +/// If the address/storage key isn't found in the array, it is inserted +/// at the correct location. These linked lists are used to keep track of +/// inserted and deleted accounts/slots during the execution, so that the +/// initial and final MPT state tries can be reconstructed at the end of the execution. +/// An empty account linked list is written as +/// [@U256_MAX, _, _, @SEGMENT_ACCOUNTS_LINKED_LIST] in SEGMENT_ACCOUNTS_LINKED_LIST. +/// The linked list is preinitialized by appending accounts to the segment. Each account is encoded +/// using 4 values. +/// The values at the respective positions are: +/// - 0: The account key +/// - 1: A ptr to the payload (the account values) +/// - 2: A ptr to the initial payload. +/// - 3: A ptr (in segment @SEGMENT_ACCOUNTS_LINKED_LIST) to the next node in the list. +/// Similarly, an empty storage linked list is written as +/// [@U256_MAX, _, _, _, @SEGMENT_ACCOUNTS_LINKED_LIST] in SEGMENT_ACCOUNTS_LINKED_LIST. +/// The linked list is preinitialized by appending storage slots to the segment. +/// Each slot is encoded using 5 values. +/// The values at the respective positions are: +/// - 0: The account key +/// - 1: The slot key +/// - 2: A ptr to the payload (the stored value) +/// - 3: A ptr to the initial payload. +/// - 4: A ptr (in segment @SEGMENT_ACCOUNTS_LINKED_LIST) to the next node in the list. + +%macro store_initial_accounts + PUSH %%after + %jump(store_initial_accounts) +%%after: +%endmacro + +/// Iterates over the initial account linked list and shallow copies +/// the accounts, storing a pointer to the copied account in the node. +/// Computes the length of `SEGMENT_ACCOUNTS_LINKED_LIST` and +/// stores it in `GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE`. +global store_initial_accounts: + // stack: retdest + PUSH @ACCOUNTS_LINKED_LISTS_NODE_SIZE + PUSH @SEGMENT_ACCOUNTS_LINKED_LIST + ADD + // stack: cur_len, retdest + PUSH @SEGMENT_ACCOUNTS_LINKED_LIST + %next_account +loop_store_initial_accounts: + // stack: current_node_ptr, cur_len, retdest + %get_trie_data_size + DUP2 + MLOAD_GENERAL + // stack: current_addr_key, cpy_ptr, current_node_ptr, cur_len, retdest + %eq_const(@U256_MAX) + %jumpi(store_initial_accounts_end) + DUP2 + %increment + MLOAD_GENERAL + // stack: nonce_ptr, cpy_ptr, current_node_ptr, cur_len, retdest + DUP1 + %mload_trie_data // nonce + %append_to_trie_data + %increment + // stack: balance_ptr, cpy_ptr, current_node_ptr, cur_len, retdest + DUP1 + %mload_trie_data // balance + %append_to_trie_data + %increment // The storage_root_ptr is not really necessary + // stack: storage_root_ptr_ptr, cpy_ptr, current_node_ptr, cur_len, retdest + DUP1 + %mload_trie_data // storage_root_ptr + %append_to_trie_data + %increment + // stack: code_hash_ptr, cpy_ptr, current_node_ptr, cur_len, retdest + %mload_trie_data // code_hash + %append_to_trie_data + // stack: cpy_ptr, current_node_ptr, cur_len, retdest + DUP2 + %add_const(2) + SWAP1 + MSTORE_GENERAL // Store cpy_ptr + // stack: current_node_ptr, cur_len, retdest + SWAP1 PUSH @ACCOUNTS_LINKED_LISTS_NODE_SIZE + ADD + SWAP1 + // stack: current_node_ptr, cur_len', retdest + %next_account + %jump(loop_store_initial_accounts) + +store_initial_accounts_end: + %pop2 + // stack: cur_len, retdest + %mstore_global_metadata(@GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE) + JUMP + +%macro insert_account_with_overwrite + %stack (addr_key, ptr) -> (addr_key, ptr, %%after) + %jump(insert_account_with_overwrite) +%%after: +%endmacro + +// Multiplies the value at the top of the stack, denoted by ptr/4, by 4 +// and aborts if ptr/4 <= mem[@GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE]/4. +// Also checks that ptr >= @SEGMENT_ACCOUNTS_LINKED_LIST. +// This way, 4*ptr/4 must be pointing to the beginning of a node. +// TODO: Maybe we should check here if the node has been deleted. +%macro get_valid_account_ptr + // stack: ptr/4 + // Check that the pointer is greater than the segment. + PUSH @SEGMENT_ACCOUNTS_LINKED_LIST + DUP2 + %mul_const(4) + // stack: ptr, @SEGMENT_ACCOUNTS_LINKED_LIST, ptr/4 + %increment %assert_gt + // stack: ptr/4 + DUP1 + PUSH 4 + %mload_global_metadata(@GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE) + // By construction, both @SEGMENT_ACCOUNTS_LINKED_LIST and the unscaled list len + // must be multiples of 4 + DIV + // stack: @SEGMENT_ACCOUNTS_LINKED_LIST/4 + accounts_linked_list_len/4, ptr/4, ptr/4 + %assert_gt + %mul_const(4) +%endmacro + +global insert_account_with_overwrite: + // stack: addr_key, payload_ptr, retdest + PROVER_INPUT(linked_list::insert_account) + // stack: pred_ptr/4, addr_key, payload_ptr, retdest + %get_valid_account_ptr + // stack: pred_ptr, addr_key, payload_ptr, retdest + DUP1 + MLOAD_GENERAL + DUP1 + // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, payload_ptr, retdest + DUP4 GT + DUP3 %eq_const(@SEGMENT_ACCOUNTS_LINKED_LIST) + ADD // OR + // If the predesessor is strictly smaller or the predecessor is the special + // node with key @U256_MAX (and hence we're inserting a new minimum), then + // we need to insert a new node. + %jumpi(insert_new_account) + // stack: pred_addr_key, pred_ptr, addr_key, payload_ptr, retdest + // If we are here we know that addr <= pred_addr. But this is only possible if pred_addr == addr. + DUP3 + %assert_eq + + // stack: pred_ptr, addr_key, payload_ptr, retdest + // Check that this is not a deleted node + DUP1 + %add_const(@ACCOUNTS_NEXT_NODE_PTR) + MLOAD_GENERAL + %jump_neq_const(@U256_MAX, account_found_with_overwrite) + // The storage key is not in the list. + PANIC + +account_found_with_overwrite: + // The address was already in the list + // stack: pred_ptr, addr_key, payload_ptr, retdest + // Load the payload pointer + %increment + // stack: payload_ptr_ptr, addr_key, payload_ptr, retdest + DUP3 MSTORE_GENERAL + %pop2 + JUMP + +insert_new_account: + // stack: pred_addr_key, pred_ptr, addr_key, payload_ptr, retdest + POP + // get the value of the next address + %add_const(@ACCOUNTS_NEXT_NODE_PTR) + // stack: next_ptr_ptr, addr_key, payload_ptr, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE) + DUP2 + MLOAD_GENERAL + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, payload_ptr, retdest + // Check that this is not a deleted node + DUP1 + %eq_const(@U256_MAX) + %assert_zero + DUP1 + MLOAD_GENERAL + // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, payload_ptr, retdest + DUP5 + // Here, (addr_key > pred_addr_key) || (pred_ptr == @SEGMENT_ACCOUNTS_LINKED_LIST). + // We should have (addr_key < next_addr_key), meaning the new value can be inserted between pred_ptr and next_ptr. + %assert_lt + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, payload_ptr, retdest + SWAP2 + DUP2 + // stack: new_ptr, next_ptr_ptr, new_ptr, next_ptr, addr_key, payload_ptr, retdest + MSTORE_GENERAL + // stack: new_ptr, next_ptr, addr_key, payload_ptr, retdest + DUP1 + DUP4 + MSTORE_GENERAL + // stack: new_ptr, next_ptr, addr_key, payload_ptr, retdest + %increment + DUP1 + DUP5 + MSTORE_GENERAL + // stack: new_ptr + 1, next_ptr, addr_key, payload_ptr, retdest + %increment + DUP1 + DUP5 + %clone_account + MSTORE_GENERAL + %increment + DUP1 + // stack: new_next_ptr, new_next_ptr, next_ptr, addr_key, payload_ptr, retdest + SWAP2 + MSTORE_GENERAL + // stack: new_next_ptr, addr_key, payload_ptr, retdest + %increment + %mstore_global_metadata(@GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE) + // stack: addr_key, payload_ptr, retdest + %pop2 + JUMP + + +/// Searches the account addr in the linked list. +/// Returns 0 if the account was not found or `original_ptr` if it was already present. +global search_account: + // stack: addr_key, retdest + PROVER_INPUT(linked_list::insert_account) + // stack: pred_ptr/4, addr_key, retdest + %get_valid_account_ptr + // stack: pred_ptr, addr_key, retdest + DUP1 + MLOAD_GENERAL + DUP1 + // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, retdest + DUP4 GT + DUP3 %eq_const(@SEGMENT_ACCOUNTS_LINKED_LIST) + ADD // OR + // If the predesessor is strictly smaller or the predecessor is the special + // node with key @U256_MAX (and hence we're inserting a new minimum), then + // we need to insert a new node. + %jumpi(account_not_found) + // stack: pred_addr_key, pred_ptr, addr_key, retdest + // If we are here we know that addr_key <= pred_addr_key. But this is only possible if pred_addr == addr. + DUP3 + %assert_eq + + // stack: pred_ptr, addr_key, retdest + // Check that this is not a deleted node + DUP1 + %add_const(@ACCOUNTS_NEXT_NODE_PTR) + MLOAD_GENERAL + %jump_neq_const(@U256_MAX, account_found) + // The storage key is not in the list. + PANIC + +account_found: + // The address was already in the list + // stack: pred_ptr, addr_key, retdest + // Load the payload pointer + %increment + MLOAD_GENERAL + // stack: orig_payload_ptr, addr_key, retdest + %stack (orig_payload_ptr, addr_key, retdest) -> (retdest, orig_payload_ptr) + JUMP + +account_not_found: + // stack: pred_addr_key, pred_ptr, addr_key, retdest + %stack (pred_addr_key, pred_ptr, addr_key, retdest) -> (retdest, 0) + JUMP + +%macro remove_account_from_linked_list + PUSH %%after + SWAP1 + %jump(remove_account) +%%after: +%endmacro + +/// Removes the address and its value from the access list. +/// Panics if the key is not in the list. +global remove_account: + // stack: addr_key, retdest + PROVER_INPUT(linked_list::remove_account) + // stack: pred_ptr/4, addr_key, retdest + %get_valid_account_ptr + // stack: pred_ptr, addr_key, retdest + %add_const(@ACCOUNTS_NEXT_NODE_PTR) + // stack: next_ptr_ptr, addr_key, retdest + DUP1 + MLOAD_GENERAL + // stack: next_ptr, next_ptr_ptr, addr_key, retdest + DUP1 + MLOAD_GENERAL + // stack: next_addr_key, next_ptr, next_ptr_ptr, addr_key, retdest + DUP4 + %assert_eq + // stack: next_ptr, next_ptr_ptr, addr_key, retdest + %add_const(@ACCOUNTS_NEXT_NODE_PTR) + // stack: next_next_ptr_ptr, next_ptr_ptr, addr_key, key, retdest + DUP1 + MLOAD_GENERAL + // stack: next_next_ptr, next_next_ptr_ptr, next_ptr_ptr, addr_key, retdest + SWAP1 + %mstore_u256_max + // stack: next_next_ptr, next_ptr_ptr, addr_key, retdest + MSTORE_GENERAL + POP + JUMP + + +// +// +// STORAGE linked list +// +// + +%macro store_initial_slots + PUSH %%after + %jump(store_initial_slots) +%%after: +%endmacro + + +/// Iterates over the initial account linked list and shallow copies +/// the accounts, storing a pointer to the copied account in the node. +/// Computes the length of `SEGMENT_STORAGE_LINKED_LIST` and +/// checks against `GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE`. +global store_initial_slots: + // stack: retdest + PUSH @STORAGE_LINKED_LISTS_NODE_SIZE + PUSH @SEGMENT_STORAGE_LINKED_LIST + ADD + // stack: cur_len, retdest + PUSH @SEGMENT_STORAGE_LINKED_LIST + %next_slot + +loop_store_initial_slots: + // stack: current_node_ptr, cur_len, retdest + %get_trie_data_size + DUP2 + MLOAD_GENERAL + // stack: current_addr_key, cpy_ptr, current_node_ptr, cur_len, retdest + %eq_const(@U256_MAX) + %jumpi(store_initial_slots_end) + DUP2 + %add_const(2) + MLOAD_GENERAL + // stack: payload_ptr, cpy_ptr, current_node_ptr, cur_len, retdest + %mload_trie_data + %append_to_trie_data + // stack: cpy_ptr, current_node_ptr, cur_len, retdest + DUP2 + %add_const(@STORAGE_COPY_PAYLOAD_PTR) + SWAP1 + MSTORE_GENERAL // Store cpy_ptr + // stack: current_node_ptr, cur_len, retdest + SWAP1 PUSH @STORAGE_LINKED_LISTS_NODE_SIZE + ADD + SWAP1 + // stack: current_node_ptr, cur_len', retdest + %next_slot + %jump(loop_store_initial_slots) + +store_initial_slots_end: + %pop2 + // stack: cur_len, retdest + %mstore_global_metadata(@GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE) + JUMP + + +%macro insert_slot + %stack (addr_key, key, ptr) -> (addr_key, key, ptr, %%after) + %jump(insert_slot) +%%after: + // stack: value_ptr +%endmacro + +%macro insert_slot_no_return + %insert_slot + POP +%endmacro + +// Multiplies the value at the top of the stack, denoted by ptr/5, by 5 +// and aborts if ptr/5 >= (mem[@GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE] - @SEGMENT_STORAGE_LINKED_LIST)/5. +// This way, @SEGMENT_STORAGE_LINKED_LIST + 5*ptr/5 must be pointing to the beginning of a node. +// TODO: Maybe we should check here if the node has been deleted. +%macro get_valid_slot_ptr + // stack: ptr/5 + DUP1 + PUSH 5 + PUSH @SEGMENT_STORAGE_LINKED_LIST + // stack: segment, 5, ptr/5, ptr/5 + %mload_global_metadata(@GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE) + SUB + // stack: accessed_strg_keys_len, 5, ptr/5, ptr/5 + // By construction, the unscaled list len must be multiple of 5 + DIV + // stack: accessed_strg_keys_len/5, ptr/5, ptr/5 + %assert_gt + %mul_const(5) + %add_const(@SEGMENT_STORAGE_LINKED_LIST) +%endmacro + +/// Inserts the pair (address_key, storage_key) and a new payload pointer into the linked list if it is not already present, +/// or modifies its payload if it was already present. +/// Returns `new_payload_ptr` if the storage key was inserted, `original_ptr` if it was already present. +global insert_slot_with_value: + // stack: addr_key, key, value, retdest + PROVER_INPUT(linked_list::insert_slot) + // stack: pred_ptr/5, addr_key, key, value, retdest + %get_valid_slot_ptr + + // stack: pred_ptr, addr_key, key, value, retdest + DUP1 + MLOAD_GENERAL + DUP1 + // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, key, value, retdest + DUP4 + GT + DUP3 %eq_const(@SEGMENT_STORAGE_LINKED_LIST) + ADD // OR + // If the predesessor is strictly smaller or the predecessor is the special + // node with key @U256_MAX (and hence we're inserting a new minimum), then + // we need to insert a new node. + %jumpi(insert_new_slot_with_value) + // stack: pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + // If we are here we know that addr <= pred_addr. But this is only possible if pred_addr == addr. + DUP3 + %assert_eq + // stack: pred_ptr, addr_key, key, value, retdest + DUP1 + %increment + MLOAD_GENERAL + // stack: pred_key, pred_ptr, addr_key, key, value, retdest + DUP1 DUP5 + GT + %jumpi(insert_new_slot_with_value) + // stack: pred_key, pred_ptr, addr_key, key, value, retdest + DUP4 + // We know that key <= pred_key. It must hold that pred_key == key. + %assert_eq + + // stack: pred_ptr, addr_key, key, value, retdest + // Check that this is not a deleted node + DUP1 + %add_const(@STORAGE_NEXT_NODE_PTR) + MLOAD_GENERAL + %jump_neq_const(@U256_MAX, slot_found_write_value) + // The storage key is not in the list. + PANIC + +insert_new_slot_with_value: + // stack: pred_addr or pred_key, pred_ptr, addr_key, key, value, retdest + POP + // get the value of the next address + %add_const(@STORAGE_NEXT_NODE_PTR) + // stack: next_ptr_ptr, addr_key, key, value, retdest + %mload_global_metadata(@GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE) + DUP2 + MLOAD_GENERAL + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest + // Check that this is not a deleted node + DUP1 + %eq_const(@U256_MAX) + %assert_zero + DUP1 + MLOAD_GENERAL + // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest + DUP1 + DUP6 + // Here, (addr_key > pred_addr_key) || (pred_ptr == @SEGMENT_ACCOUNTS_LINKED_LIST). + // We should have (addr_key < next_addr_key), meaning the new value can be inserted between pred_ptr and next_ptr. + LT + %jumpi(next_node_ok_with_value) + // If addr_key <= next_addr_key, then it addr must be equal to next_addr + // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest + DUP5 + %assert_eq + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest + DUP1 + %increment + MLOAD_GENERAL + // stack: next_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest + DUP1 // This is added just to have the correct stack in next_node_ok + DUP7 + // The next key must be strictly larger + %assert_lt + +next_node_ok_with_value: + // stack: next_addr or next_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest + POP + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, value, retdest + SWAP2 + DUP2 + // stack: new_ptr, next_ptr_ptr, new_ptr, next_ptr, addr_key, key, value, retdest + MSTORE_GENERAL + // stack: new_ptr, next_ptr, addr_key, key, value, retdest + // Write the address in the new node + DUP1 + DUP4 + MSTORE_GENERAL + // stack: new_ptr, next_ptr, addr_key, key, value, retdest + // Write the key in the new node + %increment + DUP1 + DUP5 + MSTORE_GENERAL + // stack: new_ptr + 1, next_ptr, addr_key, key, value, retdest + // Append the value to `TrieDataSegment` and write the resulting payload_ptr. + %increment + DUP1 + %get_trie_data_size + // stack: new_payload_ptr, new_ptr+2, new_ptr+2, next_ptr, addr_key, key, value, retdest + %stack (new_payload_ptr, new_payload_ptr_ptr, new_payload_ptr_ptr, next_ptr, addr_key, key, value, retdest) + -> (value, new_payload_ptr, new_payload_ptr_ptr, new_payload_ptr_ptr, next_ptr, new_payload_ptr, retdest) + %append_to_trie_data + MSTORE_GENERAL + + // stack: new_ptr + 2, next_ptr, new_payload_ptr, retdest + // Store the payload ptr copy + %increment + DUP1 + DUP4 + %clone_slot + MSTORE_GENERAL + // stack: new_ptr + 3, next_ptr, new_payload_ptr, retdest + %increment + DUP1 + // stack: new_next_ptr, new_next_ptr, next_ptr, new_payload_ptr, retdest + SWAP2 + MSTORE_GENERAL + // stack: new_next_ptr, new_payload_ptr, retdest + %increment + %mstore_global_metadata(@GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE) + // stack: new_payload_ptr, retdest + SWAP1 + JUMP + +slot_found_write_value: + // stack: pred_ptr, addr_key, key, value, retdest + %add_const(2) MLOAD_GENERAL + %stack (payload_ptr, addr_key, key, value) -> (payload_ptr, value, payload_ptr) + %mstore_trie_data + // stack: payload_ptr, retdest + %stack (payload_ptr, retdest) -> (retdest, payload_ptr) + JUMP + +%macro insert_slot_with_value + // stack: addr, slot, value + %addr_to_state_key + SWAP1 + %slot_to_storage_key + %stack (slot_key, addr_key, value) -> (addr_key, slot_key, value, %%after) + %jump(insert_slot_with_value) +%%after: + // stack: value_ptr +%endmacro + +/// Inserts the pair (address_key, storage_key) and payload pointer into the linked list if it is not already present, +/// or modifies its payload if it was already present. +/// Returns `payload_ptr` if the storage key was inserted, `original_ptr` if it was already present. +global insert_slot: + // stack: addr_key, key, payload_ptr, retdest + PROVER_INPUT(linked_list::insert_slot) + // stack: pred_ptr/5, addr_key, key, payload_ptr, retdest + %get_valid_slot_ptr + + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + DUP1 + MLOAD_GENERAL + DUP1 + // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + DUP4 + GT + DUP3 %eq_const(@SEGMENT_STORAGE_LINKED_LIST) + ADD // OR + // If the predesessor is strictly smaller or the predecessor is the special + // node with key @U256_MAX (and hence we're inserting a new minimum), then + // we need to insert a new node. + %jumpi(insert_new_slot) + // stack: pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + // If we are here we know that addr <= pred_addr. But this is only possible if pred_addr == addr. + DUP3 + %assert_eq + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + DUP1 + %increment + MLOAD_GENERAL + // stack: pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + DUP1 DUP5 + GT + %jumpi(insert_new_slot) + // stack: pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + DUP4 + // We know that key <= pred_key. It must hold that pred_key == key. + %assert_eq + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // Check that this is not a deleted node + DUP1 + %add_const(@STORAGE_NEXT_NODE_PTR) + MLOAD_GENERAL + %jump_neq_const(@U256_MAX, slot_found_write) + // The storage key is not in the list. + PANIC + +slot_found_write: + // The slot was already in the list + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // Load the the payload pointer and access counter + %add_const(2) + DUP1 + MLOAD_GENERAL + // stack: orig_payload_ptr, pred_ptr + 2, addr_key, key, payload_ptr, retdest + SWAP1 + DUP5 + MSTORE_GENERAL // Store the new payload + %stack (orig_payload_ptr, addr_key, key, payload_ptr, retdest) -> (retdest, orig_payload_ptr) + JUMP +insert_new_slot: + // stack: pred_addr or pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + POP + // get the value of the next address + %add_const(@STORAGE_NEXT_NODE_PTR) + // stack: next_ptr_ptr, addr_key, key, payload_ptr, retdest + %mload_global_metadata(@GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE) + DUP2 + MLOAD_GENERAL + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + // Check that this is not a deleted node + DUP1 + %eq_const(@U256_MAX) + %assert_zero + DUP1 + MLOAD_GENERAL + // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + DUP1 + DUP6 + // Here, (addr_key > pred_addr_key) || (pred_ptr == @SEGMENT_ACCOUNTS_LINKED_LIST). + // We should have (addr_key < next_addr_key), meaning the new value can be inserted between pred_ptr and next_ptr. + LT + %jumpi(next_node_ok) + // If addr_key <= next_addr_key, then it addr must be equal to next_addr + // stack: next_addr_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + DUP5 + %assert_eq + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + DUP1 + %increment + MLOAD_GENERAL + // stack: next_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + DUP1 // This is added just to have the correct stack in next_node_ok + DUP7 + // The next key must be strictly larger + %assert_lt +next_node_ok: + // stack: next_addr or next_key, next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + POP + // stack: next_ptr, new_ptr, next_ptr_ptr, addr_key, key, payload_ptr, retdest + SWAP2 + DUP2 + // stack: new_ptr, next_ptr_ptr, new_ptr, next_ptr, addr_key, key, payload_ptr, retdest + MSTORE_GENERAL + // stack: new_ptr, next_ptr, addr_key, key, payload_ptr, retdest + // Write the address in the new node + DUP1 + DUP4 + MSTORE_GENERAL + // stack: new_ptr, next_ptr, addr_key, key, payload_ptr, retdest + // Write the key in the new node + %increment + DUP1 + DUP5 + MSTORE_GENERAL + // stack: new_ptr + 1, next_ptr, addr_key, key, payload_ptr, retdest + // Store payload_ptr + %increment + DUP1 + DUP6 + MSTORE_GENERAL + + // stack: new_ptr + 2, next_ptr, addr_key, key, payload_ptr, retdest + // Store the copy of payload_ptr + %increment + DUP1 + DUP6 + %clone_slot + MSTORE_GENERAL + // stack: new_ptr + 3, next_ptr, addr_key, key, payload_ptr, retdest + %increment + DUP1 + // stack: new_next_ptr, new_next_ptr, next_ptr, addr_key, key, payload_ptr, retdest + SWAP2 + MSTORE_GENERAL + // stack: new_next_ptr, addr_key, key, payload_ptr, retdest + %increment + %mstore_global_metadata(@GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE) + // stack: addr_key, key, payload_ptr, retdest + %stack (addr_key, key, payload_ptr, retdest) -> (retdest, payload_ptr) + JUMP + +/// Searches the pair (address_key, storage_key) in the storage the linked list. +/// Returns `payload_ptr` if the storage key was inserted, `original_ptr` if it was already present. +global search_slot: + // stack: addr_key, key, payload_ptr, retdest + PROVER_INPUT(linked_list::insert_slot) + // stack: pred_ptr/5, addr_key, key, payload_ptr, retdest + %get_valid_slot_ptr + + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + DUP1 + MLOAD_GENERAL + DUP1 + // stack: pred_addr_key, pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + DUP4 + GT + DUP3 %eq_const(@SEGMENT_STORAGE_LINKED_LIST) + ADD // OR + // If the predesessor is strictly smaller or the predecessor is the special + // node with key @U256_MAX (and hence we're inserting a new minimum), then + // the slot was not found + %jumpi(slot_not_found) + // stack: pred_addr_key, pred_ptr, addr_key, key, payload_ptr, retdest + // If we are here we know that addr <= pred_addr. But this is only possible if pred_addr == addr. + DUP3 + %assert_eq + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + DUP1 + %increment + MLOAD_GENERAL + // stack: pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + DUP1 DUP5 + GT + %jumpi(slot_not_found) + // stack: pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + DUP4 + // We know that key <= pred_key. It must hold that pred_key == key. + %assert_eq + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // Check that this is not a deleted node + DUP1 + %add_const(@STORAGE_NEXT_NODE_PTR) + MLOAD_GENERAL + %jump_neq_const(@U256_MAX, slot_found_no_write) + // The storage key is not in the list. + PANIC +slot_not_found: + // stack: pred_addr_or_pred_key, pred_ptr, addr_key, key, payload_ptr, retdest + %stack (pred_addr_or_pred_key, pred_ptr, addr_key, key, payload_ptr, retdest) + -> (retdest, payload_ptr) + JUMP + +slot_found_no_write: + // The slot was already in the list + // stack: pred_ptr, addr_key, key, payload_ptr, retdest + // Load the the payload pointer and access counter + %add_const(2) + MLOAD_GENERAL + // stack: orig_payload_ptr, addr_key, key, payload_ptr, retdest + %stack (orig_payload_ptr, addr_key, key, payload_ptr, retdest) -> (retdest, orig_payload_ptr) + JUMP + + +%macro remove_slot + %stack (key, addr_key) -> (addr_key, key, %%after) + %jump(remove_slot) +%%after: +%endmacro + +/// Removes the storage key and its value from the list. +/// Panics if the key is not in the list. +global remove_slot: + // stack: addr_key, key, retdest + PROVER_INPUT(linked_list::remove_slot) + // stack: pred_ptr/5, addr_key, key, retdest + %get_valid_slot_ptr + // stack: pred_ptr, addr_key, key, retdest + %add_const(@STORAGE_NEXT_NODE_PTR) + // stack: next_ptr_ptr, addr_key, key, retdest + DUP1 + MLOAD_GENERAL + // stack: next_ptr, next_ptr_ptr, addr_key, key, retdest + DUP1 + MLOAD_GENERAL + // stack: next_addr_key, next_ptr, next_ptr_ptr, addr_key, key, retdest + DUP4 + %assert_eq + // stack: next_ptr, next_ptr_ptr, addr_key, key, retdest + DUP1 + %increment + MLOAD_GENERAL + // stack: next_key, next_ptr, next_ptr_ptr, addr_key, key, retdest + DUP5 + %assert_eq + // stack: next_ptr, next_ptr_ptr, addr_key, key, retdest + %add_const(@STORAGE_NEXT_NODE_PTR) + // stack: next_next_ptr_ptr, next_ptr_ptr, addr_key, key, retdest + DUP1 + MLOAD_GENERAL + // stack: next_next_ptr, next_next_ptr_ptr, next_ptr_ptr, addr_key, key, retdest + // Mark the next node as deleted + SWAP1 + %mstore_u256_max + // stack: next_next_ptr, next_ptr_ptr, addr_key, key, retdest + MSTORE_GENERAL + %pop2 + JUMP + +/// Called when an account is deleted: it deletes all slots associated with the account. +global remove_all_account_slots: + // stack: addr_key, retdest + PROVER_INPUT(linked_list::remove_address_slots) + // pred_ptr/5, retdest + %get_valid_slot_ptr + // stack: pred_ptr, addr_key, retdest + // First, check that the previous address is not `addr` + DUP1 MLOAD_GENERAL + // stack: pred_addr_key, pred_ptr, addr_key, retdest + DUP3 EQ %jumpi(panic) + // stack: pred_ptr, addr_key, retdest + DUP1 + +// Now, while the next address is `addr`, remove the next slot. +remove_all_slots_loop: + // stack: pred_ptr, pred_ptr, addr_key, retdest + %add_const(@STORAGE_NEXT_NODE_PTR) DUP1 MLOAD_GENERAL + // stack: cur_ptr, cur_ptr_ptr, pred_ptr, addr_key, retdest + DUP1 %eq_const(@U256_MAX) %jumpi(remove_all_slots_end) + DUP1 %add_const(@STORAGE_NEXT_NODE_PTR) MLOAD_GENERAL + // stack: next_ptr, cur_ptr, cur_ptr_ptr, pred_ptr, addr_key, retdest + SWAP1 DUP1 + // stack: cur_ptr, cur_ptr, next_ptr, cur_ptr_ptr, pred_ptr, addr_key, retdest + MLOAD_GENERAL + DUP6 EQ ISZERO %jumpi(remove_all_slots_pop_and_end) + + // Remove slot: update the value in cur_ptr_ptr, and set cur_ptr+4 to @U256_MAX. + // stack: cur_ptr, next_ptr, cur_ptr_ptr, pred_ptr, addr_key, retdest + SWAP2 SWAP1 + // stack: next_ptr, cur_ptr_ptr, cur_ptr, pred_ptr, addr_key, retdest + MSTORE_GENERAL + // stack: cur_ptr, pred_ptr, addr_key, retdest + %add_const(@STORAGE_NEXT_NODE_PTR) + %mstore_u256_max + // stack: pred_ptr, addr_key, retdest + DUP1 + %jump(remove_all_slots_loop) + +remove_all_slots_pop_and_end: + POP +remove_all_slots_end: + // stack: next_ptr, cur_ptr_ptr, pred_ptr, addr_key, retdest + %pop4 JUMP + +%macro remove_all_account_slots + %stack (addr_key) -> (addr_key, %%after) + %jump(remove_all_account_slots) +%%after: +%endmacro + +%macro read_accounts_linked_list + %stack (addr) -> (addr, %%after) + %addr_to_state_key + %jump(search_account) +%%after: + // stack: account_ptr +%endmacro + +%macro read_storage_linked_list + // stack: slot + %slot_to_storage_key + %address + %addr_to_state_key + %stack (addr_key, key) -> (addr_key, key, 0, %%after) + %jump(search_slot) +%%after: + // stack: slot_ptr +%endmacro + +%macro read_storage_linked_list_w_addr + // stack: slot, address + %slot_to_storage_key + SWAP1 + %addr_to_state_key + %stack (addr_key, key) -> (addr_key, key, 0, %%after) + %jump(search_slot) +%%after: + // stack: slot_ptr +%endmacro + +%macro first_account + // stack: empty + PUSH @SEGMENT_ACCOUNTS_LINKED_LIST + %next_account +%endmacro + +%macro next_account + // stack: node_ptr + %add_const(@ACCOUNTS_NEXT_NODE_PTR) + MLOAD_GENERAL + // stack: next_node_ptr +%endmacro + +%macro first_slot + // stack: empty + PUSH @SEGMENT_STORAGE_LINKED_LIST + %next_slot +%endmacro + +%macro next_slot + // stack: node_ptr + %add_const(@STORAGE_NEXT_NODE_PTR) + MLOAD_GENERAL + // stack: next_node_ptr +%endmacro diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm index eaad20641..7f078931c 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/read.asm @@ -1,14 +1,14 @@ // Given an address, return a pointer to the associated account data, which // consists of four words (nonce, balance, storage_root, code_hash), in the -// state trie. Returns null if the address is not found. +// trie_data segment. Return null if the address is not found. global mpt_read_state_trie: // stack: addr, retdest - %addr_to_state_key - // stack: key, retdest - PUSH 64 // num_nibbles - %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_ROOT) // node_ptr - // stack: node_ptr, num_nibbles, key, retdest - %jump(mpt_read) + %read_accounts_linked_list + // stack: account_ptr, retdest + SWAP1 + JUMP + + // Convenience macro to call mpt_read_state_trie and return where we left off. %macro mpt_read_state_trie diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_read.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_read.asm index db9fe4222..d4a7ca36a 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_read.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_read.asm @@ -5,15 +5,7 @@ %endmacro 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) - -global after_storage_read: + %read_storage_linked_list // stack: value_ptr, retdest DUP1 %jumpi(storage_key_exists) @@ -22,13 +14,6 @@ global after_storage_read: %stack (value_ptr, retdest) -> (retdest, 0) JUMP -global storage_key_exists: - // stack: value_ptr, retdest - %mload_trie_data - // stack: value, retdest - SWAP1 - JUMP - // Read a word from the current account's storage trie. // // Pre stack: kexit_info, slot diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_write.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_write.asm index 22c5d29de..a188661d3 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_write.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/storage/storage_write.asm @@ -119,25 +119,11 @@ sstore_after_refund: %append_to_trie_data // stack: slot, value_ptr, kexit_info - // Next, call mpt_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) - -after_storage_insert: - // stack: new_storage_root_ptr, kexit_info - %current_account_data - // stack: account_ptr, new_storage_root_ptr, kexit_info - - // Update the copied account with our new storage root pointer. - %add_const(2) - // stack: account_storage_root_ptr_ptr, new_storage_root_ptr, kexit_info - %mstore_trie_data - // stack: kexit_info + %address + %addr_to_state_key + %insert_slot_no_return + EXIT_KERNEL sstore_noop: @@ -148,12 +134,11 @@ sstore_noop: // Delete the slot from the storage trie. sstore_delete: // stack: slot, value, kexit_info - SWAP1 POP - PUSH after_storage_insert SWAP1 - // stack: slot, after_storage_insert, kexit_info + %address + %addr_to_state_key + // stack: addr_key, slot, value, kexit_info + SWAP2 POP + // stack: slot, addr_key, kexit_info %slot_to_storage_key - // stack: storage_key, after_storage_insert, kexit_info - PUSH 64 // storage_key has 64 nibbles - %current_storage_trie - // stack: storage_root_ptr, 64, storage_key, after_storage_insert, kexit_info - %jump(mpt_delete) + %remove_slot + EXIT_KERNEL diff --git a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm index d89293998..49caffe87 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/transactions/common_decoding.asm @@ -244,22 +244,20 @@ after_read: sload_with_addr: - %stack (slot, addr) -> (slot, addr, after_storage_read) - %slot_to_storage_key - // stack: storage_key, addr, after_storage_read - PUSH 64 // storage_key has 64 nibbles - %stack (n64, storage_key, addr, after_storage_read) -> (addr, n64, storage_key, after_storage_read) - %mpt_read_state_trie - // stack: account_ptr, 64, storage_key, after_storage_read - DUP1 ISZERO %jumpi(ret_zero) // TODO: Fix this. This should never happen. - // stack: account_ptr, 64, storage_key, after_storage_read - %add_const(2) - // stack: storage_root_ptr_ptr - %mload_trie_data - // stack: storage_root_ptr, 64, storage_key, after_storage_read - %jump(mpt_read) -ret_zero: - // stack: account_ptr, 64, storage_key, after_storage_read, retdest - %pop4 - PUSH 0 SWAP1 JUMP + %read_storage_linked_list_w_addr + + // stack: value_ptr, retdest + DUP1 %jumpi(storage_key_exists) + + // Storage key not found. Return default value_ptr = 0, + // which derefs to 0 since @SEGMENT_TRIE_DATA[0] = 0. + %stack (value_ptr, retdest) -> (retdest, 0) + JUMP + +global storage_key_exists: + // stack: value_ptr, retdest + %mload_trie_data + // stack: value, retdest + SWAP1 + JUMP diff --git a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs index 4baa18f48..4af27c6ce 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/global_metadata.rs @@ -94,10 +94,23 @@ pub(crate) enum GlobalMetadata { KernelHash, KernelLen, + + /// The address of the next available address in + /// Segment::AccountsLinkedList + AccountsLinkedListNextAvailable, + /// The address of the next available address in + /// Segment::StorageLinkedList + StorageLinkedListNextAvailable, + /// Length of the `AccountsLinkedList` segment after insertion of the + /// initial accounts. + InitialAccountsLinkedListLen, + /// Length of the `StorageLinkedList` segment after insertion of the + /// initial storage slots. + InitialStorageLinkedListLen, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 47; + pub(crate) const COUNT: usize = 51; /// Unscales this virtual offset by their respective `Segment` value. pub(crate) const fn unscale(&self) -> usize { @@ -153,6 +166,10 @@ impl GlobalMetadata { Self::TxnNumberAfter, Self::KernelHash, Self::KernelLen, + Self::AccountsLinkedListNextAvailable, + Self::StorageLinkedListNextAvailable, + Self::InitialAccountsLinkedListLen, + Self::InitialStorageLinkedListLen, ] } @@ -206,6 +223,16 @@ impl GlobalMetadata { Self::TxnNumberAfter => "GLOBAL_METADATA_TXN_NUMBER_AFTER", Self::KernelHash => "GLOBAL_METADATA_KERNEL_HASH", Self::KernelLen => "GLOBAL_METADATA_KERNEL_LEN", + Self::AccountsLinkedListNextAvailable => { + "GLOBAL_METADATA_ACCOUNTS_LINKED_LIST_NEXT_AVAILABLE" + } + Self::StorageLinkedListNextAvailable => { + "GLOBAL_METADATA_STORAGE_LINKED_LIST_NEXT_AVAILABLE" + } + Self::InitialAccountsLinkedListLen => { + "GLOBAL_METADATA_INITIAL_ACCOUNTS_LINKED_LIST_LEN" + } + Self::InitialStorageLinkedListLen => "GLOBAL_METADATA_INITIAL_STORAGE_LINKED_LIST_LEN", } } } diff --git a/evm_arithmetization/src/cpu/kernel/constants/mod.rs b/evm_arithmetization/src/cpu/kernel/constants/mod.rs index 610b28dc9..d24d3dff0 100644 --- a/evm_arithmetization/src/cpu/kernel/constants/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/constants/mod.rs @@ -54,6 +54,10 @@ pub(crate) fn evm_constants() -> HashMap { c.insert(name.into(), U256::from(value)); } + for (name, value) in LINKED_LISTS_CONSTANTS { + c.insert(name.into(), U256::from(value)); + } + c.insert(MAX_NONCE.0.into(), U256::from(MAX_NONCE.1)); c.insert(CALL_STACK_LIMIT.0.into(), U256::from(CALL_STACK_LIMIT.1)); @@ -321,3 +325,11 @@ 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); + +const LINKED_LISTS_CONSTANTS: [(&str, u16); 5] = [ + ("ACCOUNTS_LINKED_LISTS_NODE_SIZE", 4), + ("STORAGE_LINKED_LISTS_NODE_SIZE", 5), + ("ACCOUNTS_NEXT_NODE_PTR", 3), + ("STORAGE_NEXT_NODE_PTR", 4), + ("STORAGE_COPY_PAYLOAD_PTR", 3), +]; diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 886093b29..6d4f42235 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -20,7 +20,7 @@ use crate::cpu::columns::CpuColumnsView; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField; -use crate::generation::mpt::{load_all_mpts, TrieRootPtrs}; +use crate::generation::mpt::{load_linked_lists_and_txn_and_receipt_mpts, TrieRootPtrs}; use crate::generation::rlp::all_rlp_prover_inputs_reversed; use crate::generation::state::{ all_withdrawals_prover_inputs_reversed, GenerationState, GenerationStateCheckpoint, @@ -143,16 +143,10 @@ pub(crate) fn set_registers_and_run( .iter() .enumerate() .for_each(|(i, reg_content)| { - let (addr, val) = ( - MemoryAddress::new_u256s( - 0.into(), - (Segment::RegistersStates.unscale()).into(), - i.into(), - ) - .expect("All input values are known to be valid for MemoryAddress"), + interpreter.generation_state.memory.set( + MemoryAddress::new(0, Segment::RegistersStates, i), *reg_content, - ); - interpreter.generation_state.memory.set(addr, val); + ) }); interpreter.run() @@ -259,16 +253,27 @@ impl Interpreter { self.generation_state.inputs = inputs.trim(); // Initialize the MPT's pointers. - let (trie_root_ptrs, trie_data) = - load_all_mpts(tries).expect("Invalid MPT data for preinitialization"); + let (trie_root_ptrs, state_leaves, storage_leaves, trie_data) = + load_linked_lists_and_txn_and_receipt_mpts(&inputs.tries) + .expect("Invalid MPT data for preinitialization"); + let trie_roots_after = &inputs.trie_roots_after; self.generation_state.trie_root_ptrs = trie_root_ptrs; // Initialize the `TrieData` segment. - let preinit_trie_data_segment = MemorySegmentState { - content: trie_data.iter().map(|&elt| Some(elt)).collect::>(), + let preinit_trie_data_segment = MemorySegmentState { content: trie_data }; + let preinit_accounts_ll_segment = MemorySegmentState { + content: state_leaves, + }; + let preinit_storage_ll_segment = MemorySegmentState { + content: storage_leaves, }; self.insert_preinitialized_segment(Segment::TrieData, preinit_trie_data_segment); + self.insert_preinitialized_segment( + Segment::AccountsLinkedList, + preinit_accounts_ll_segment, + ); + self.insert_preinitialized_segment(Segment::StorageLinkedList, preinit_storage_ll_segment); // Update the RLP and withdrawal prover inputs. let rlp_prover_inputs = all_rlp_prover_inputs_reversed(&inputs.signed_txns); @@ -339,12 +344,7 @@ impl Interpreter { let final_block_bloom_fields = (0..8) .map(|i| { ( - MemoryAddress::new_u256s( - U256::zero(), - (Segment::GlobalBlockBloom.unscale()).into(), - i.into(), - ) - .expect("This cannot panic as `virt` fits in a `u32`"), + MemoryAddress::new(0, Segment::GlobalBlockBloom, i), metadata.block_bloom[i], ) }) @@ -356,12 +356,7 @@ impl Interpreter { let block_hashes_fields = (0..256) .map(|i| { ( - MemoryAddress::new_u256s( - U256::zero(), - (Segment::BlockHashes.unscale()).into(), - i.into(), - ) - .expect("This cannot panic as `virt` fits in a `u32`"), + MemoryAddress::new(0, Segment::BlockHashes, i), h2u(inputs.block_hashes.prev_hashes[i]), ) }) @@ -381,12 +376,7 @@ impl Interpreter { let registers_before_fields = (0..registers_before.len()) .map(|i| { ( - MemoryAddress::new_u256s( - 0.into(), - (Segment::RegistersStates.unscale()).into(), - i.into(), - ) - .unwrap(), + MemoryAddress::new(0, Segment::RegistersStates, i), registers_before[i], ) }) @@ -651,6 +641,10 @@ impl State for Interpreter { memory_state.contexts[ctx_idx] = ctx.clone(); } } + if self.generation_state.set_preinit { + memory_state.preinitialized_segments = + self.generation_state.memory.preinitialized_segments.clone(); + } Some(memory_state) } diff --git a/evm_arithmetization/src/cpu/kernel/tests/account_code.rs b/evm_arithmetization/src/cpu/kernel/tests/account_code.rs index a6beab6f0..b72eaf7ca 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/account_code.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/account_code.rs @@ -5,7 +5,7 @@ use ethereum_types::{Address, BigEndianHash, H256, U256}; use hex_literal::hex; use keccak_hash::keccak; use mpt_trie::nibbles::Nibbles; -use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; +use mpt_trie::partial_trie::{HashedPartialTrie, Node, PartialTrie}; use plonky2::field::goldilocks_field::GoldilocksField as F; use plonky2::field::types::Field; use rand::{thread_rng, Rng}; @@ -15,20 +15,92 @@ use crate::cpu::kernel::constants::context_metadata::ContextMetadata::{self, Gas 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::{load_all_mpts, AccountRlp}; +use crate::generation::mpt::{ + load_linked_lists_and_txn_and_receipt_mpts, load_state_mpt, AccountRlp, +}; use crate::generation::TrieInputs; use crate::memory::segments::Segment; -use crate::witness::memory::MemoryAddress; +use crate::util::h2u; +use crate::witness::memory::{MemoryAddress, MemorySegmentState}; use crate::witness::operation::CONTEXT_SCALING_FACTOR; -use crate::Node; pub(crate) fn initialize_mpts( interpreter: &mut Interpreter, trie_inputs: &TrieInputs, ) { // Load all MPTs. - let (trie_root_ptrs, trie_data) = - load_all_mpts(trie_inputs).expect("Invalid MPT data for preinitialization"); + let (mut trie_root_ptrs, state_leaves, storage_leaves, trie_data) = + load_linked_lists_and_txn_and_receipt_mpts(trie_inputs) + .expect("Invalid MPT data for preinitialization"); + + interpreter.generation_state.memory.contexts[0].segments + [Segment::AccountsLinkedList.unscale()] + .content = state_leaves; + interpreter.generation_state.memory.contexts[0].segments + [Segment::StorageLinkedList.unscale()] + .content = storage_leaves; + interpreter.generation_state.memory.contexts[0].segments[Segment::TrieData.unscale()].content = + trie_data.clone(); + interpreter.generation_state.trie_root_ptrs = trie_root_ptrs.clone(); + + if trie_root_ptrs.state_root_ptr.is_none() { + trie_root_ptrs.state_root_ptr = Some( + load_state_mpt( + &trie_inputs.trim(), + &mut interpreter.generation_state.memory.contexts[0].segments + [Segment::TrieData.unscale()] + .content, + ) + .expect("Invalid MPT data for preinitialization"), + ); + } + + let accounts_len = Segment::AccountsLinkedList as usize + + interpreter.generation_state.memory.contexts[0].segments + [Segment::AccountsLinkedList.unscale()] + .content + .len(); + let storage_len = Segment::StorageLinkedList as usize + + interpreter.generation_state.memory.contexts[0].segments + [Segment::StorageLinkedList.unscale()] + .content + .len(); + let accounts_len_addr = MemoryAddress { + context: 0, + segment: Segment::GlobalMetadata.unscale(), + virt: GlobalMetadata::AccountsLinkedListNextAvailable.unscale(), + }; + let storage_len_addr = MemoryAddress { + context: 0, + segment: Segment::GlobalMetadata.unscale(), + virt: GlobalMetadata::StorageLinkedListNextAvailable.unscale(), + }; + let initial_accounts_len_addr = MemoryAddress { + context: 0, + segment: Segment::GlobalMetadata.unscale(), + virt: GlobalMetadata::InitialAccountsLinkedListLen.unscale(), + }; + let initial_storage_len_addr = MemoryAddress { + context: 0, + segment: Segment::GlobalMetadata.unscale(), + virt: GlobalMetadata::InitialStorageLinkedListLen.unscale(), + }; + let trie_data_len_addr = MemoryAddress { + context: 0, + segment: Segment::GlobalMetadata.unscale(), + virt: GlobalMetadata::TrieDataSize.unscale(), + }; + let trie_data_len = interpreter.generation_state.memory.contexts[0].segments + [Segment::TrieData.unscale()] + .content + .len(); + interpreter.set_memory_multi_addresses(&[ + (accounts_len_addr, accounts_len.into()), + (storage_len_addr, storage_len.into()), + (trie_data_len_addr, trie_data_len.into()), + (initial_accounts_len_addr, accounts_len.into()), + (initial_storage_len_addr, storage_len.into()), + ]); let state_addr = MemoryAddress::new_bundle((GlobalMetadata::StateTrieRoot as usize).into()).unwrap(); @@ -39,12 +111,14 @@ pub(crate) fn initialize_mpts( let len_addr = MemoryAddress::new_bundle((GlobalMetadata::TrieDataSize as usize).into()).unwrap(); - let to_set = [ - (state_addr, trie_root_ptrs.state_root_ptr.into()), + let mut to_set = vec![]; + if let Some(state_root_ptr) = trie_root_ptrs.state_root_ptr { + to_set.push((state_addr, state_root_ptr.into())); + } + to_set.extend([ (txn_addr, trie_root_ptrs.txn_root_ptr.into()), (receipts_addr, trie_root_ptrs.receipt_root_ptr.into()), - (len_addr, trie_data.len().into()), - ]; + ]); interpreter.set_memory_multi_addresses(&to_set); @@ -53,39 +127,29 @@ pub(crate) fn initialize_mpts( interpreter .generation_state .memory - .set(trie_addr, data.into()); - } -} - -// Test account with a given code hash. -fn test_account(code: &[u8]) -> AccountRlp { - AccountRlp { - nonce: U256::from(1111), - balance: U256::from(2222), - storage_root: HashedPartialTrie::from(Node::Empty).hash(), - code_hash: keccak(code), + .set(trie_addr, data.unwrap_or_default()); } } -fn random_code() -> Vec { - let mut rng = thread_rng(); - let num_bytes = rng.gen_range(0..1000); - (0..num_bytes).map(|_| rng.gen()).collect() -} - // Stolen from `tests/mpt/insert.rs` // Prepare the interpreter by inserting the account in the state trie. -fn prepare_interpreter( +pub(crate) fn prepare_interpreter( interpreter: &mut Interpreter, address: Address, account: &AccountRlp, ) -> Result<()> { 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 trie_inputs = Default::default(); + let check_state_trie = KERNEL.global_labels["check_state_trie"]; + let mut state_trie: HashedPartialTrie = HashedPartialTrie::from(Node::Empty); + let trie_inputs = TrieInputs { + state_trie: HashedPartialTrie::from(Node::Empty), + transactions_trie: HashedPartialTrie::from(Node::Empty), + receipts_trie: HashedPartialTrie::from(Node::Empty), + storage_tries: vec![], + }; initialize_mpts(interpreter, &trie_inputs); + assert_eq!(interpreter.stack(), vec![]); let k = nibbles_64(U256::from_big_endian( keccak(address.to_fixed_bytes()).as_bytes(), @@ -119,6 +183,7 @@ fn prepare_interpreter( .expect("The stack should not overflow"); // key interpreter.run()?; + assert_eq!( interpreter.stack().len(), 0, @@ -126,13 +191,46 @@ fn prepare_interpreter( interpreter.stack() ); - // Now, execute mpt_hash_state_trie. - interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; + // Set initial tries. interpreter .push(0xDEADBEEFu32.into()) .expect("The stack should not overflow"); interpreter - .push(1.into()) // Initial length of the trie data segment, unused. + .push((Segment::StorageLinkedList as usize + 8).into()) + .expect("The stack should not overflow"); + interpreter + .push((Segment::AccountsLinkedList as usize + 6).into()) + .expect("The stack should not overflow"); + interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); + + // Now, set the payload. + interpreter.generation_state.registers.program_counter = + KERNEL.global_labels["mpt_set_payload"]; + + interpreter.run()?; + + let acc_ptr = interpreter.pop().expect("The stack should not be empty") - 2; + let storage_ptr = interpreter.pop().expect("The stack should not be empty") - 3; + interpreter.set_global_metadata_field(GlobalMetadata::InitialAccountsLinkedListLen, acc_ptr); + interpreter.set_global_metadata_field(GlobalMetadata::InitialStorageLinkedListLen, storage_ptr); + + // Now, execute `mpt_hash_state_trie`. + state_trie.insert(k, rlp::encode(account).to_vec()); + let expected_state_trie_hash = state_trie.hash(); + interpreter.set_global_metadata_field( + GlobalMetadata::StateTrieRootDigestAfter, + h2u(expected_state_trie_hash), + ); + + interpreter.generation_state.registers.program_counter = check_state_trie; + interpreter + .halt_offsets + .push(KERNEL.global_labels["check_txn_trie"]); + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter + .push(interpreter.get_global_metadata_field(GlobalMetadata::TrieDataSize)) // Initial trie data segment size, unused. .expect("The stack should not overflow"); interpreter.run()?; @@ -142,15 +240,26 @@ fn prepare_interpreter( "Expected 2 items on stack after hashing, found {:?}", interpreter.stack() ); - let hash = H256::from_uint(&interpreter.stack()[1]); - - 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(()) } +// Test account with a given code hash. +fn test_account(code: &[u8]) -> AccountRlp { + AccountRlp { + nonce: U256::from(1111), + balance: U256::from(2222), + storage_root: HashedPartialTrie::from(Node::Empty).hash(), + code_hash: keccak(code), + } +} + +fn random_code() -> Vec { + let mut rng = thread_rng(); + let num_bytes = rng.gen_range(0..1000); + (0..num_bytes).map(|_| rng.gen()).collect() +} + #[test] fn test_extcodesize() -> Result<()> { let code = random_code(); @@ -278,6 +387,50 @@ fn prepare_interpreter_all_accounts( initialize_mpts(interpreter, &trie_inputs); assert_eq!(interpreter.stack(), vec![]); + // Copy the initial account and storage pointers + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter.generation_state.registers.program_counter = + KERNEL.global_labels["store_initial_accounts"]; + interpreter.run()?; + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter.generation_state.registers.program_counter = + KERNEL.global_labels["store_initial_slots"]; + interpreter.run()?; + + // Set the pointers to the initial payloads. + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter + .push((Segment::StorageLinkedList as usize + 8).into()) + .expect("The stack should not overflow"); + interpreter + .push((Segment::AccountsLinkedList as usize + 6).into()) + .expect("The stack should not overflow"); + interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); + + // Now, set the payloads in the state trie leaves. + interpreter.generation_state.registers.program_counter = + KERNEL.global_labels["mpt_set_payload"]; + + interpreter.run()?; + + assert_eq!( + interpreter.stack().len(), + 2, + "Expected 2 items on stack after setting the initial trie payloads, found {:?}", + interpreter.stack() + ); + + let acc_ptr = interpreter.pop().expect("The stack should not be empty") - 2; + let storage_ptr = interpreter.pop().expect("The stack should not be empty") - 3; + interpreter.set_global_metadata_field(GlobalMetadata::InitialAccountsLinkedListLen, acc_ptr); + interpreter.set_global_metadata_field(GlobalMetadata::InitialStorageLinkedListLen, storage_ptr); + // Switch context and initialize memory with the data we need for the tests. interpreter.generation_state.registers.program_counter = 0; interpreter.set_code(1, code.to_vec()); @@ -364,8 +517,21 @@ fn sstore() -> Result<()> { .hash(), ..AccountRlp::default() }; - // Now, execute mpt_hash_state_trie. - let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; + + let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); + expected_state_trie_after.insert(addr_nibbles, rlp::encode(&account_after).to_vec()); + + let expected_state_trie_hash = expected_state_trie_after.hash(); + + interpreter.set_global_metadata_field( + GlobalMetadata::StateTrieRootDigestAfter, + h2u(expected_state_trie_hash), + ); + interpreter + .halt_offsets + .push(KERNEL.global_labels["check_txn_trie"]); + // Now, execute `mpt_hash_state_trie` and check that the hash is correct. + let mpt_hash_state_trie = KERNEL.global_labels["check_state_trie"]; interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; interpreter.set_is_kernel(true); interpreter.set_context(0); @@ -377,27 +543,12 @@ fn sstore() -> Result<()> { .expect("The stack should not overflow"); interpreter.run()?; - assert_eq!( - interpreter.stack().len(), - 2, - "Expected 2 items on stack after hashing, found {:?}", - interpreter.stack() - ); - - let hash = H256::from_uint(&interpreter.stack()[1]); - - let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); - expected_state_trie_after.insert(addr_nibbles, rlp::encode(&account_after).to_vec()); - - let expected_state_trie_hash = expected_state_trie_after.hash(); - assert_eq!(hash, expected_state_trie_hash); Ok(()) } /// Tests an SLOAD within a code similar to the contract code in add11_yml. #[test] fn sload() -> Result<()> { - // We take the same `to` account as in add11_yml. let addr = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"); let addr_hashed = keccak(addr); @@ -461,7 +612,7 @@ fn sload() -> Result<()> { interpreter .pop() .expect("The stack length should not be empty."); - // Now, execute mpt_hash_state_trie. We check that the state trie has not + // Now, execute `mpt_hash_state_trie`. We check that the state trie has not // changed. let mpt_hash_state_trie = KERNEL.global_labels["mpt_hash_state_trie"]; interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; @@ -471,7 +622,7 @@ fn sload() -> Result<()> { .push(0xDEADBEEFu32.into()) .expect("The stack should not overflow."); interpreter - .push(1.into()) // Initial length of the trie data segment, unused. + .push(interpreter.get_global_metadata_field(GlobalMetadata::TrieDataSize)) // Initial length of the trie data segment, unused. .expect("The stack should not overflow."); interpreter.run()?; @@ -483,13 +634,6 @@ fn sload() -> Result<()> { ); let trie_data_segment_len = interpreter.stack()[0]; - assert_eq!( - trie_data_segment_len, - interpreter - .get_memory_segment(Segment::TrieData) - .len() - .into() - ); let hash = H256::from_uint(&interpreter.stack()[1]); diff --git a/evm_arithmetization/src/cpu/kernel/tests/balance.rs b/evm_arithmetization/src/cpu/kernel/tests/balance.rs index dcb91f212..4dbcdb722 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/balance.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/balance.rs @@ -9,7 +9,7 @@ use rand::{thread_rng, Rng}; 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::account_code::initialize_mpts; +use crate::cpu::kernel::tests::account_code::prepare_interpreter; use crate::cpu::kernel::tests::mpt::nibbles_64; use crate::generation::mpt::AccountRlp; use crate::Node; @@ -24,85 +24,6 @@ fn test_account(balance: U256) -> AccountRlp { } } -// Stolen from `tests/mpt/insert.rs` -// Prepare the interpreter by inserting the account in the state trie. -fn prepare_interpreter( - interpreter: &mut Interpreter, - address: Address, - account: &AccountRlp, -) -> Result<()> { - 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 trie_inputs = Default::default(); - - initialize_mpts(interpreter, &trie_inputs); - 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 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(Some(0.into())); - } - let value_ptr = trie_data.len(); - trie_data.push(Some(account.nonce)); - trie_data.push(Some(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(Some(H256::zero().into_uint())); - trie_data.push(Some(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()) - .expect("The stack should not overflow"); - interpreter - .push(value_ptr.into()) - .expect("The stack should not overflow"); // value_ptr - interpreter - .push(k.try_into().unwrap()) - .expect("The stack should not overflow"); // 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()) - .expect("The stack should not overflow"); - interpreter - .push(1.into()) // Initial trie data segment size, unused. - .expect("The stack should not overflow"); - interpreter.run()?; - - assert_eq!( - interpreter.stack().len(), - 2, - "Expected 2 items on stack after hashing, found {:?}", - interpreter.stack() - ); - let hash = H256::from_uint(&interpreter.stack()[1]); - - 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(()) -} - #[test] fn test_balance() -> Result<()> { let mut rng = thread_rng(); diff --git a/evm_arithmetization/src/cpu/kernel/tests/core/access_lists.rs b/evm_arithmetization/src/cpu/kernel/tests/core/access_lists.rs index 922bf9bdd..a173cd4b8 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/core/access_lists.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/core/access_lists.rs @@ -73,15 +73,14 @@ fn test_list_iterator() -> Result<()> { .get_addresses_access_list() .expect("Since we called init_access_lists there must be a list"); - let Some((pos_0, next_val_0, _)) = list.next() else { + let Some([next_val_0, _]) = list.next() else { return Err(anyhow::Error::msg("Couldn't get value")); }; - assert_eq!(pos_0, 0); assert_eq!(next_val_0, U256::MAX); - let Some((pos_0, _, _)) = list.next() else { + let Some([_, pos_0]) = list.next() else { return Err(anyhow::Error::msg("Couldn't get value")); }; - assert_eq!(pos_0, 0); + assert_eq!(pos_0, U256::from(Segment::AccessedAddresses as usize)); Ok(()) } diff --git a/evm_arithmetization/src/cpu/kernel/tests/mpt/delete.rs b/evm_arithmetization/src/cpu/kernel/tests/mpt/delete.rs index 428fb0a72..44602f92c 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mpt/delete.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mpt/delete.rs @@ -11,7 +11,10 @@ use crate::cpu::kernel::interpreter::Interpreter; use crate::cpu::kernel::tests::account_code::initialize_mpts; use crate::cpu::kernel::tests::mpt::{nibbles_64, test_account_1_rlp, test_account_2}; use crate::generation::mpt::AccountRlp; +use crate::generation::state::State; use crate::generation::TrieInputs; +use crate::memory::segments::Segment; +use crate::util::h2u; use crate::Node; #[test] @@ -105,6 +108,38 @@ fn test_state_trie( initialize_mpts(&mut interpreter, &trie_inputs); assert_eq!(interpreter.stack(), vec![]); + // Store initial accounts and storage. + interpreter + .halt_offsets + .push(KERNEL.global_labels["after_store_initial"]); + interpreter.generation_state.registers.program_counter = KERNEL.global_labels["store_initial"]; + interpreter.run(); + + // Set initial tries. + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter + .push((Segment::StorageLinkedList as usize + 8).into()) + .expect("The stack should not overflow"); + interpreter + .push((Segment::AccountsLinkedList as usize + 6).into()) + .expect("The stack should not overflow"); + interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); + + // Now, set the payload. + interpreter.generation_state.registers.program_counter = + KERNEL.global_labels["mpt_set_payload"]; + + interpreter.run()?; + + assert_eq!(interpreter.stack_len(), 2); + + let acc_ptr = interpreter.pop().expect("The stack should not be empty") - 2; + let storage_ptr = interpreter.pop().expect("The stack should not be empty") - 3; + interpreter.set_global_metadata_field(GlobalMetadata::InitialAccountsLinkedListLen, acc_ptr); + interpreter.set_global_metadata_field(GlobalMetadata::InitialStorageLinkedListLen, storage_ptr); + // Next, execute mpt_insert_state_trie. interpreter.generation_state.registers.program_counter = mpt_insert_state_trie; let trie_data = interpreter.get_trie_data_mut(); @@ -140,6 +175,14 @@ fn test_state_trie( interpreter.stack() ); + // Now, run `set_final_tries` so that the trie roots are correct. + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter.generation_state.registers.program_counter = + KERNEL.global_labels["set_final_tries"]; + interpreter.run()?; + // 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; @@ -159,20 +202,32 @@ fn test_state_trie( let state_trie_ptr = interpreter.pop().expect("The stack should not be empty"); interpreter.set_global_metadata_field(GlobalMetadata::StateTrieRoot, state_trie_ptr); - // Now, execute mpt_hash_state_trie. + // Now, execute `mpt_hash_state_trie`. + let expected_state_trie_hash = state_trie.hash(); + interpreter.set_global_metadata_field( + GlobalMetadata::StateTrieRootDigestAfter, + h2u(expected_state_trie_hash), + ); + interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; + interpreter .push(0xDEADBEEFu32.into()) .expect("The stack should not overflow"); interpreter - .push(1.into()) // Initial length of the trie data segment, unused. + .push(interpreter.get_global_metadata_field(GlobalMetadata::TrieDataSize)) // Initial trie data segment size, unused. .expect("The stack should not overflow"); interpreter.run()?; - let state_trie_hash = - H256::from_uint(&interpreter.pop().expect("The stack should not be empty")); - let expected_state_trie_hash = state_trie.hash(); - assert_eq!(state_trie_hash, expected_state_trie_hash); + assert_eq!( + interpreter.stack().len(), + 2, + "Expected 2 items on stack after hashing, found {:?}", + interpreter.stack() + ); + + let hash = interpreter.pop().expect("The stack should not be empty"); + assert_eq!(hash, h2u(expected_state_trie_hash)); Ok(()) } diff --git a/evm_arithmetization/src/cpu/kernel/tests/mpt/hash.rs b/evm_arithmetization/src/cpu/kernel/tests/mpt/hash.rs index 1f7562bf7..59c5fb384 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mpt/hash.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mpt/hash.rs @@ -117,7 +117,7 @@ fn test_state_trie(trie_inputs: TrieInputs) -> Result<()> { initialize_mpts(&mut interpreter, &trie_inputs); assert_eq!(interpreter.stack(), vec![]); - // Now, execute mpt_hash_state_trie. + // Now, execute `mpt_hash_state_trie`. interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; interpreter .push(0xDEADBEEFu32.into()) diff --git a/evm_arithmetization/src/cpu/kernel/tests/mpt/insert.rs b/evm_arithmetization/src/cpu/kernel/tests/mpt/insert.rs index 2d01dc064..28352c69e 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mpt/insert.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mpt/insert.rs @@ -4,6 +4,7 @@ use mpt_trie::nibbles::Nibbles; use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; use plonky2::field::goldilocks_field::GoldilocksField as F; +use super::{test_account_1, test_account_1_empty_storage_rlp}; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::Interpreter; @@ -13,6 +14,8 @@ use crate::cpu::kernel::tests::mpt::{ }; use crate::generation::mpt::AccountRlp; use crate::generation::TrieInputs; +use crate::memory::segments::Segment; +use crate::util::h2u; use crate::Node; #[test] @@ -25,7 +28,7 @@ fn mpt_insert_leaf_identical_keys() -> Result<()> { let key = nibbles_64(0xABC); let state_trie = Node::Leaf { nibbles: key, - value: test_account_1_rlp(), + value: test_account_1_empty_storage_rlp(), } .into(); test_state_trie(state_trie, key, test_account_2()) @@ -172,7 +175,7 @@ fn test_state_trie( storage_tries: vec![], }; 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 check_state_trie = KERNEL.global_labels["check_state_trie"]; let initial_stack = vec![]; let mut interpreter: Interpreter = Interpreter::new(0, initial_stack, None); @@ -180,6 +183,36 @@ fn test_state_trie( initialize_mpts(&mut interpreter, &trie_inputs); assert_eq!(interpreter.stack(), vec![]); + // Store initial accounts and storage. + interpreter + .halt_offsets + .push(KERNEL.global_labels["after_store_initial"]); + interpreter.generation_state.registers.program_counter = KERNEL.global_labels["store_initial"]; + interpreter.run(); + + // Set initial tries. + interpreter + .push(0xDEADBEEFu32.into()) + .expect("The stack should not overflow"); + interpreter + .push((Segment::StorageLinkedList as usize + 8).into()) + .expect("The stack should not overflow"); + interpreter + .push((Segment::AccountsLinkedList as usize + 6).into()) + .expect("The stack should not overflow"); + interpreter.push(interpreter.get_global_metadata_field(GlobalMetadata::StateTrieRoot)); + + // Now, set the payload. + interpreter.generation_state.registers.program_counter = + KERNEL.global_labels["mpt_set_payload"]; + + interpreter.run()?; + + let acc_ptr = interpreter.pop().expect("The stack should not be empty") - 2; + let storage_ptr = interpreter.pop().expect("The stack should not be empty") - 3; + interpreter.set_global_metadata_field(GlobalMetadata::InitialAccountsLinkedListLen, acc_ptr); + interpreter.set_global_metadata_field(GlobalMetadata::InitialStorageLinkedListLen, storage_ptr); + // Next, execute mpt_insert_state_trie. interpreter.generation_state.registers.program_counter = mpt_insert_state_trie; let trie_data = interpreter.get_trie_data_mut(); @@ -216,27 +249,31 @@ fn test_state_trie( interpreter.stack() ); - // Now, execute mpt_hash_state_trie. - interpreter.generation_state.registers.program_counter = mpt_hash_state_trie; + // Now, execute `mpt_hash_state_trie` and check the hash value (both are done + // under `check_state_trie`). + state_trie.insert(k, rlp::encode(&account).to_vec()); + let expected_state_trie_hash = state_trie.hash(); + interpreter.set_global_metadata_field( + GlobalMetadata::StateTrieRootDigestAfter, + h2u(expected_state_trie_hash), + ); + + interpreter.generation_state.registers.program_counter = check_state_trie; interpreter - .push(0xDEADBEEFu32.into()) - .expect("The stack should not overflow"); + .halt_offsets + .push(KERNEL.global_labels["check_txn_trie"]); + interpreter - .push(1.into()) // Initial length of the trie data segment, unused. + .push(interpreter.get_global_metadata_field(GlobalMetadata::TrieDataSize)) // Initial trie data segment size, unused. .expect("The stack should not overflow"); interpreter.run()?; assert_eq!( interpreter.stack().len(), - 2, + 1, "Expected 2 items on stack after hashing, found {:?}", interpreter.stack() ); - let hash = H256::from_uint(&interpreter.stack()[1]); - - 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_arithmetization/src/cpu/kernel/tests/mpt/linked_list.rs b/evm_arithmetization/src/cpu/kernel/tests/mpt/linked_list.rs new file mode 100644 index 000000000..c9c9428cf --- /dev/null +++ b/evm_arithmetization/src/cpu/kernel/tests/mpt/linked_list.rs @@ -0,0 +1,604 @@ +use std::collections::HashSet; + +use anyhow::Result; +use env_logger::try_init_from_env; +use env_logger::Env; +use env_logger::DEFAULT_FILTER_ENV; +use ethereum_types::{Address, H160, U256}; +use itertools::Itertools; +use num::traits::ToBytes; +use plonky2::field::goldilocks_field::GoldilocksField as F; +use plonky2_maybe_rayon::rayon::iter; +use rand::{thread_rng, Rng}; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::generation::linked_list::LinkedList; +use crate::memory::segments::Segment::{self, AccessedAddresses, AccessedStorageKeys}; +use crate::util::u256_to_usize; +use crate::witness::errors::ProgramError; +use crate::witness::errors::ProverInputError::InvalidInput; +use crate::witness::memory::MemoryAddress; + +fn init_logger() { + let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "debug")); +} + +#[test] +fn test_init_linked_lists() -> Result<()> { + init_logger(); + + let mut interpreter = Interpreter::::new(0, vec![], None); + + // Check the initial accounts linked list + let acc_addr_list: Vec = (0..4) + .map(|i| { + interpreter + .generation_state + .memory + .get_with_init(MemoryAddress::new(0, Segment::AccountsLinkedList, i)) + }) + .collect(); + assert_eq!( + vec![ + U256::MAX, + U256::zero(), + U256::zero(), + (Segment::AccountsLinkedList as usize).into(), + ], + acc_addr_list + ); + + // Check the initial storage linked list + let acc_addr_list: Vec = (0..5) + .map(|i| { + interpreter + .generation_state + .memory + .get_with_init(MemoryAddress::new(0, Segment::StorageLinkedList, i)) + }) + .collect(); + assert_eq!( + vec![ + U256::MAX, + U256::zero(), + U256::zero(), + U256::zero(), + (Segment::StorageLinkedList as usize).into(), + ], + acc_addr_list + ); + + Ok(()) +} + +#[test] +fn test_list_iterator() -> Result<()> { + init_logger(); + + let mut interpreter = Interpreter::::new(0, vec![], None); + + // test the list iterator + let accounts_mem = interpreter + .generation_state + .memory + .get_preinit_memory(Segment::AccountsLinkedList); + let mut accounts_list = + LinkedList::from_mem_and_segment(&accounts_mem, Segment::AccountsLinkedList).unwrap(); + + let Some([addr, ptr, ptr_cpy, scaled_pos_1]) = accounts_list.next() else { + return Err(anyhow::Error::msg("Couldn't get value")); + }; + assert_eq!(addr, U256::MAX); + assert_eq!(ptr, U256::zero()); + assert_eq!(ptr_cpy, U256::zero()); + assert_eq!(scaled_pos_1, (Segment::AccountsLinkedList as usize).into()); + let Some([addr, ptr, ptr_cpy, scaled_pos_1]) = accounts_list.next() else { + return Err(anyhow::Error::msg("Couldn't get value")); + }; + assert_eq!(addr, U256::MAX); + assert_eq!(ptr, U256::zero()); + assert_eq!(ptr_cpy, U256::zero()); + assert_eq!(scaled_pos_1, (Segment::AccountsLinkedList as usize).into()); + + let accounts_mem = interpreter + .generation_state + .memory + .get_preinit_memory(Segment::StorageLinkedList); + let mut storage_list = + LinkedList::from_mem_and_segment(&accounts_mem, Segment::StorageLinkedList).unwrap(); + let Some([addr, key, ptr, ptr_cpy, scaled_pos_1]) = storage_list.next() else { + return Err(anyhow::Error::msg("Couldn't get value")); + }; + assert_eq!(addr, U256::MAX); + assert_eq!(key, U256::zero()); + assert_eq!(ptr, U256::zero()); + assert_eq!(ptr_cpy, U256::zero()); + assert_eq!(scaled_pos_1, (Segment::StorageLinkedList as usize).into()); + let Some([addr, key, ptr, ptr_cpy, scaled_pos_1]) = storage_list.next() else { + return Err(anyhow::Error::msg("Couldn't get value")); + }; + assert_eq!(addr, U256::MAX); + assert_eq!(ptr, U256::zero()); + assert_eq!(ptr_cpy, U256::zero()); + assert_eq!(scaled_pos_1, (Segment::StorageLinkedList as usize).into()); + + Ok(()) +} + +#[test] +fn test_insert_account() -> Result<()> { + init_logger(); + + let mut interpreter = Interpreter::::new(0, vec![], None); + + // Initialize the accounts linked list. + let init_accounts_ll = vec![ + Some(U256::MAX), + Some(0.into()), + Some(0.into()), + Some((Segment::AccountsLinkedList as usize).into()), + ]; + let init_len = init_accounts_ll.len(); + interpreter.generation_state.memory.contexts[0].segments + [Segment::AccountsLinkedList.unscale()] + .content = init_accounts_ll; + interpreter.set_global_metadata_field( + GlobalMetadata::AccountsLinkedListNextAvailable, + (Segment::AccountsLinkedList as usize + init_len).into(), + ); + + let insert_account_label = KERNEL.global_labels["insert_account_with_overwrite"]; + + let retaddr = 0xdeadbeefu32.into(); + let mut rng = thread_rng(); + let address: H160 = rng.gen(); + let payload_ptr = U256::from(5); + + assert!(address != H160::zero(), "Cosmic luck or bad RNG?"); + + interpreter.push(retaddr); + interpreter.push(payload_ptr); + interpreter.push(U256::from(address.0.as_slice())); + interpreter.generation_state.registers.program_counter = insert_account_label; + + interpreter.run()?; + + let accounts_mem = interpreter + .generation_state + .memory + .get_preinit_memory(Segment::AccountsLinkedList); + let mut list = + LinkedList::from_mem_and_segment(&accounts_mem, Segment::AccountsLinkedList).unwrap(); + + let Some([addr, ptr, ptr_cpy, scaled_next_pos]) = list.next() else { + return Err(anyhow::Error::msg("Couldn't get value")); + }; + assert_eq!(addr, U256::from(address.0.as_slice())); + assert_eq!(ptr, payload_ptr); + assert_eq!(ptr_cpy, U256::zero()); // ptr_cpy is zero because the trie_data segment is empty + assert_eq!( + scaled_next_pos, + (Segment::AccountsLinkedList as usize).into() + ); + let Some([addr, ptr, ptr_cpy, scaled_new_pos]) = list.next() else { + return Err(anyhow::Error::msg("Couldn't get value")); + }; + assert_eq!(addr, U256::MAX); + assert_eq!(ptr, U256::zero()); + assert_eq!(ptr_cpy, U256::zero()); + assert_eq!( + scaled_new_pos, + (Segment::AccountsLinkedList as usize + 4).into() + ); + Ok(()) +} + +#[test] +fn test_insert_storage() -> Result<()> { + init_logger(); + + let mut interpreter = Interpreter::::new(0, vec![], None); + + // Initialize the storage linked list. + let init_storage_ll = vec![ + Some(U256::MAX), + Some(0.into()), + Some(0.into()), + Some(0.into()), + Some((Segment::StorageLinkedList as usize).into()), + ]; + let init_len = init_storage_ll.len(); + interpreter.generation_state.memory.contexts[0].segments + [Segment::StorageLinkedList.unscale()] + .content = init_storage_ll; + interpreter.set_global_metadata_field( + GlobalMetadata::StorageLinkedListNextAvailable, + (Segment::StorageLinkedList as usize + init_len).into(), + ); + + let insert_account_label = KERNEL.global_labels["insert_slot"]; + + let retaddr = 0xdeadbeefu32.into(); + let mut rng = thread_rng(); + let address: H160 = rng.gen(); + let key: H160 = rng.gen(); + let payload_ptr = U256::from(5); + + assert!(address != H160::zero(), "Cosmic luck or bad RNG?"); + + interpreter.push(retaddr); + interpreter.push(payload_ptr); + interpreter.push(U256::from(key.0.as_slice())); + interpreter.push(U256::from(address.0.as_slice())); + interpreter.generation_state.registers.program_counter = insert_account_label; + + interpreter.run()?; + assert_eq!(interpreter.stack(), &[payload_ptr]); + + let accounts_mem = interpreter + .generation_state + .memory + .get_preinit_memory(Segment::StorageLinkedList); + let mut list = + LinkedList::from_mem_and_segment(&accounts_mem, Segment::StorageLinkedList).unwrap(); + + let Some([inserted_addr, inserted_key, ptr, ptr_cpy, scaled_next_pos]) = list.next() else { + return Err(anyhow::Error::msg("Couldn't get value")); + }; + assert_eq!(inserted_addr, U256::from(address.0.as_slice())); + assert_eq!(inserted_key, U256::from(key.0.as_slice())); + assert_eq!(ptr, payload_ptr); + assert_eq!(ptr_cpy, U256::zero()); // ptr_cpy is zero because the trie data segment is empty + assert_eq!( + scaled_next_pos, + (Segment::StorageLinkedList as usize).into() + ); + let Some([inserted_addr, inserted_key, ptr, ptr_cpy, scaled_new_pos]) = list.next() else { + return Err(anyhow::Error::msg("Couldn't get value")); + }; + assert_eq!(inserted_addr, U256::MAX); + assert_eq!(inserted_key, U256::zero()); + assert_eq!(ptr, U256::zero()); + assert_eq!(ptr_cpy, U256::zero()); + assert_eq!( + scaled_new_pos, + (Segment::StorageLinkedList as usize + 5).into() + ); + Ok(()) +} + +#[test] +fn test_insert_and_delete_accounts() -> Result<()> { + init_logger(); + + let mut interpreter = Interpreter::::new(0, vec![], None); + + // Initialize the accounts linked list. + let init_accounts_ll = vec![ + Some(U256::MAX), + Some(0.into()), + Some(0.into()), + Some((Segment::AccountsLinkedList as usize).into()), + ]; + let init_len = init_accounts_ll.len(); + interpreter.generation_state.memory.contexts[0].segments + [Segment::AccountsLinkedList.unscale()] + .content = init_accounts_ll; + interpreter.set_global_metadata_field( + GlobalMetadata::AccountsLinkedListNextAvailable, + (Segment::AccountsLinkedList as usize + init_len).into(), + ); + + let insert_account_label = KERNEL.global_labels["insert_account_with_overwrite"]; + + let retaddr = 0xdeadbeefu32.into(); + let mut rng = thread_rng(); + let n = 10; + let mut addresses = (0..n) + .map(|i| Address::from_low_u64_be(i as u64 + 5)) + .collect::>() + .into_iter() + .collect::>(); + let delta_ptr = 100; + let addr_not_in_list = Address::from_low_u64_be(4); + assert!( + !addresses.contains(&addr_not_in_list), + "Cosmic luck or bad RNG?" + ); + + let offset = Segment::AccountsLinkedList as usize; + // Insert all addresses + for i in 0..n { + let addr = U256::from(addresses[i].0.as_slice()); + interpreter.push(0xdeadbeefu32.into()); + interpreter.push(addr + delta_ptr); // ptr = addr + delta_ptr for the sake of the test + interpreter.push(addr); + interpreter.generation_state.registers.program_counter = insert_account_label; + interpreter.run()?; + + // The copied ptr is at distance 4, the size of an account, from the previous + // copied ptr. + assert_eq!( + interpreter.generation_state.memory.get_with_init( + MemoryAddress::new_bundle(U256::from(offset + 4 * (i + 1) + 2)).unwrap(), + ), + (4 * i).into() + ); + } + + // The next free address in Segment::AccounLinkedList must be offset + (n + + // 1)*4. + assert_eq!( + interpreter.generation_state.memory.get_with_init( + MemoryAddress::new_bundle(U256::from( + GlobalMetadata::AccountsLinkedListNextAvailable as usize + )) + .unwrap(), + ), + U256::from(offset + (n + 1) * 4) + ); + + let search_account_label = KERNEL.global_labels["search_account"]; + // Test for address already in list. + for i in 0..n { + let addr_in_list = U256::from(addresses[i].0.as_slice()); + interpreter.push(retaddr); + interpreter.push(addr_in_list); + interpreter.generation_state.registers.program_counter = search_account_label; + interpreter.run()?; + + assert_eq!( + interpreter.pop().expect("The stack can't be empty"), + addr_in_list + delta_ptr + ); + } + + // Test for address not in the list. + interpreter.push(retaddr); + interpreter.push(U256::from(addr_not_in_list.0.as_slice()) + delta_ptr); + interpreter.push(U256::from(addr_not_in_list.0.as_slice())); + interpreter.generation_state.registers.program_counter = insert_account_label; + + interpreter.run()?; + + // Now the list of accounts have address 4 + addresses.push(addr_not_in_list); + + // The next free address in Segment::AccounLinkedList must be offset + (n + + // 2)*4. + assert_eq!( + interpreter.generation_state.memory.get_with_init( + MemoryAddress::new_bundle(U256::from( + GlobalMetadata::AccountsLinkedListNextAvailable as usize + )) + .unwrap(), + ), + U256::from(offset + (n + 2) * 4) + ); + + // Remove all even nodes. + let delete_account_label = KERNEL.global_labels["remove_account"]; + + let mut new_addresses = vec![]; + + for (i, j) in (0..n).tuples() { + // Remove addressese already in list. + let addr_in_list = U256::from(addresses[i].0.as_slice()); + interpreter.push(retaddr); + interpreter.push(addr_in_list); + interpreter.generation_state.registers.program_counter = delete_account_label; + interpreter.run()?; + assert!(interpreter.stack().is_empty()); + // we add the non deleted addres to new_addresses + new_addresses.push(addresses[j]); + } + // The last address is not removed. + new_addresses.push(*addresses.last().unwrap()); + + // We need to sort the list in order to properly compare + // the linked list with the interpreter's memory. + new_addresses.sort(); + + let accounts_mem = interpreter + .generation_state + .memory + .get_preinit_memory(Segment::AccountsLinkedList); + let mut list = + LinkedList::from_mem_and_segment(&accounts_mem, Segment::AccountsLinkedList).unwrap(); + + for (i, [addr, ptr, ptr_cpy, _]) in list.enumerate() { + if addr == U256::MAX { + assert_eq!(addr, U256::MAX); + assert_eq!(ptr, U256::zero()); + assert_eq!(ptr_cpy, U256::zero()); + break; + } + let addr_in_list = U256::from(new_addresses[i].0.as_slice()); + assert_eq!(addr, addr_in_list); + assert_eq!(ptr, addr + delta_ptr); + } + + Ok(()) +} + +#[test] +fn test_insert_and_delete_storage() -> Result<()> { + init_logger(); + + let mut interpreter = Interpreter::::new(0, vec![], None); + + // Initialize the storage linked list. + let init_storage_ll = vec![ + Some(U256::MAX), + Some(0.into()), + Some(0.into()), + Some(0.into()), + Some((Segment::StorageLinkedList as usize).into()), + ]; + let init_len = init_storage_ll.len(); + interpreter.generation_state.memory.contexts[0].segments + [Segment::StorageLinkedList.unscale()] + .content = init_storage_ll; + interpreter.set_global_metadata_field( + GlobalMetadata::StorageLinkedListNextAvailable, + (Segment::StorageLinkedList as usize + init_len).into(), + ); + + let insert_slot_label = KERNEL.global_labels["insert_slot"]; + + let retaddr = 0xdeadbeefu32.into(); + let mut rng = thread_rng(); + let n = 10; + let mut addresses_and_keys = (0..n) + .map(|i| { + [ + Address::from_low_u64_be(i as u64 + 5), + H160::from_low_u64_be(i as u64 + 6), + ] + }) + .collect::>() + .into_iter() + .collect::>(); + let delta_ptr = 100; + let addr_not_in_list = Address::from_low_u64_be(4); + let key_not_in_list = H160::from_low_u64_be(5); + assert!( + !addresses_and_keys.contains(&[addr_not_in_list, key_not_in_list]), + "Cosmic luck or bad RNG?" + ); + + let offset = Segment::StorageLinkedList as usize; + // Insert all addresses, key pairs + for i in 0..n { + let [addr, key] = addresses_and_keys[i].map(|x| U256::from(x.0.as_slice())); + interpreter.push(0xdeadbeefu32.into()); + interpreter.push(addr + delta_ptr); // ptr = addr + delta_ptr for the sake of the test + interpreter.push(key); + interpreter.push(addr); + interpreter.generation_state.registers.program_counter = insert_slot_label; + interpreter.run()?; + assert_eq!( + interpreter.pop().expect("The stack can't be empty"), + addr + delta_ptr + ); + // The ptr_cpy must be 0 + assert_eq!( + interpreter.generation_state.memory.get_with_init( + MemoryAddress::new_bundle(U256::from(offset + 5 * (i + 1) + 3)).unwrap(), + ), + i.into() + ); + } + + // The next free node in Segment::StorageLinkedList must be at offset + (n + + // 1)*5. + assert_eq!( + interpreter.generation_state.memory.get_with_init( + MemoryAddress::new_bundle(U256::from( + GlobalMetadata::StorageLinkedListNextAvailable as usize + )) + .unwrap(), + ), + U256::from(offset + (n + 1) * 5) + ); + + // Test for address already in list. + for i in 0..n { + let [addr_in_list, key_in_list] = addresses_and_keys[i].map(|x| U256::from(x.0.as_slice())); + interpreter.push(retaddr); + interpreter.push(addr_in_list + delta_ptr); + interpreter.push(key_in_list); + interpreter.push(addr_in_list); + interpreter.generation_state.registers.program_counter = insert_slot_label; + interpreter.run()?; + + assert_eq!( + interpreter.pop().expect("The stack can't be empty"), + addr_in_list + delta_ptr + ); + assert_eq!( + interpreter.generation_state.memory.get_with_init( + MemoryAddress::new_bundle(U256::from(offset + 5 * (i + 1) + 3)).unwrap(), + ), + i.into() + ); + } + + // Test for address not in the list. + interpreter.push(retaddr); + interpreter.push(U256::from(addr_not_in_list.0.as_slice()) + delta_ptr); + interpreter.push(U256::from(key_not_in_list.0.as_slice())); + interpreter.push(U256::from(addr_not_in_list.0.as_slice())); + interpreter.generation_state.registers.program_counter = insert_slot_label; + + interpreter.run()?; + + assert_eq!( + interpreter.pop().expect("The stack can't be empty"), + U256::from(addr_not_in_list.0.as_slice()) + delta_ptr + ); + + // Now the list of accounts have [4, 5] + addresses_and_keys.push([addr_not_in_list, key_not_in_list]); + + // The next free node in Segment::AccounLinkedList must be at offset + (n + + // 2)*5. + assert_eq!( + interpreter.generation_state.memory.get_with_init( + MemoryAddress::new_bundle(U256::from( + GlobalMetadata::StorageLinkedListNextAvailable as usize + )) + .unwrap(), + ), + U256::from(offset + (n + 2) * 5) + ); + + // Remove all even nodes. + let remove_slot_label = KERNEL.global_labels["remove_slot"]; + + let mut new_addresses = vec![]; + + for (i, j) in (0..n).tuples() { + // Test for [address, key] already in list. + let [addr_in_list, key_in_list] = addresses_and_keys[i].map(|x| U256::from(x.0.as_slice())); + interpreter.push(retaddr); + interpreter.push(key_in_list); + interpreter.push(addr_in_list); + interpreter.generation_state.registers.program_counter = remove_slot_label; + interpreter.run()?; + assert!(interpreter.stack().is_empty()); + // we add the non deleted addres to new_addresses + new_addresses.push(addresses_and_keys[j]); + } + // The last address is not removed. + new_addresses.push(*addresses_and_keys.last().unwrap()); + + // We need to sort the list in order to properly compare + // the linked list with the interpreter's memory. + new_addresses.sort(); + + let accounts_mem = interpreter + .generation_state + .memory + .get_preinit_memory(Segment::StorageLinkedList); + let mut list = + LinkedList::from_mem_and_segment(&accounts_mem, Segment::StorageLinkedList).unwrap(); + + for (i, [addr, key, ptr, ptr_cpy, _]) in list.enumerate() { + if addr == U256::MAX { + assert_eq!(addr, U256::MAX); + assert_eq!(key, U256::zero()); + assert_eq!(ptr, U256::zero()); + assert_eq!(ptr_cpy, U256::zero()); + break; + } + let [addr_in_list, key_in_list] = new_addresses[i].map(|x| U256::from(x.0.as_slice())); + assert_eq!(addr, addr_in_list); + assert_eq!(key, key_in_list); + assert_eq!(ptr, addr + delta_ptr); + } + + Ok(()) +} diff --git a/evm_arithmetization/src/cpu/kernel/tests/mpt/load.rs b/evm_arithmetization/src/cpu/kernel/tests/mpt/load.rs index ba3ade8f0..9d04700bf 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mpt/load.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mpt/load.rs @@ -70,14 +70,20 @@ fn load_all_mpts_leaf() -> Result<()> { assert_eq!( interpreter.get_trie_data(), vec![ - 0.into(), + 0.into(), // First address is unused, so that 0 can be treated as a null pointer. + // The next four elements correspond to the account stored in the linked list. + test_account_1().nonce, + test_account_1().balance, + 0.into(), // pointer to storage trie root before insertion + test_account_1().code_hash.into_uint(), + // Values used for hashing. type_leaf, 3.into(), 0xABC.into(), - 5.into(), // value ptr + 9.into(), // value ptr test_account_1().nonce, test_account_1().balance, - 9.into(), // pointer to storage trie root + 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(), @@ -208,17 +214,23 @@ fn load_all_mpts_ext_to_leaf() -> Result<()> { interpreter.get_trie_data(), vec![ 0.into(), // First address is unused, so that 0 can be treated as a null pointer. + // The next four elements correspond to the account stored in the linked list. + test_account_1().nonce, + test_account_1().balance, + 0.into(), // pointer to storage trie root before insertion + test_account_1().code_hash.into_uint(), + // Values used for hashing. type_extension, 3.into(), // 3 nibbles 0xABC.into(), // key part - 5.into(), // Pointer to the leaf node immediately below. + 9.into(), // Pointer to the leaf node immediately below. type_leaf, 3.into(), // 3 nibbles 0xDEF.into(), // key part - 9.into(), // value pointer + 13.into(), // value pointer test_account_1().nonce, test_account_1().balance, - 13.into(), // pointer to storage trie root + 17.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(), diff --git a/evm_arithmetization/src/cpu/kernel/tests/mpt/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/mpt/mod.rs index 84f64bb7b..cf8f8597a 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mpt/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mpt/mod.rs @@ -1,6 +1,7 @@ use ethereum_types::{BigEndianHash, H256, U256}; use mpt_trie::nibbles::Nibbles; use mpt_trie::partial_trie::HashedPartialTrie; +use mpt_trie::partial_trie::PartialTrie; use crate::generation::mpt::AccountRlp; use crate::Node; @@ -9,6 +10,7 @@ mod delete; mod hash; mod hex_prefix; mod insert; +mod linked_list; mod load; mod read; @@ -37,10 +39,23 @@ pub(crate) fn test_account_1() -> AccountRlp { } } +pub(crate) fn test_account_1_empty_storage() -> AccountRlp { + AccountRlp { + nonce: U256::from(1111), + balance: U256::from(2222), + storage_root: HashedPartialTrie::from(Node::Empty).hash(), + 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_1_empty_storage_rlp() -> Vec { + rlp::encode(&test_account_1_empty_storage()).to_vec() +} + pub(crate) fn test_account_2() -> AccountRlp { AccountRlp { nonce: U256::from(5555), @@ -50,6 +65,15 @@ pub(crate) fn test_account_2() -> AccountRlp { } } +pub(crate) fn test_account_2_empty_storage() -> AccountRlp { + AccountRlp { + nonce: U256::from(5555), + balance: U256::from(6666), + storage_root: HashedPartialTrie::from(Node::Empty).hash(), + code_hash: H256::from_uint(&U256::from(8888)), + } +} + pub(crate) fn test_account_2_rlp() -> Vec { rlp::encode(&test_account_2()).to_vec() } diff --git a/evm_arithmetization/src/cpu/kernel/tests/mpt/read.rs b/evm_arithmetization/src/cpu/kernel/tests/mpt/read.rs index b8a1e0722..571b45c38 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mpt/read.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mpt/read.rs @@ -25,7 +25,7 @@ fn mpt_read() -> Result<()> { initialize_mpts(&mut interpreter, &trie_inputs); assert_eq!(interpreter.stack(), vec![]); - // Now, execute mpt_read on the state trie. + // Now, execute `mpt_read` on the state trie. interpreter.generation_state.registers.program_counter = mpt_read; interpreter .push(0xdeadbeefu32.into()) diff --git a/evm_arithmetization/src/cpu/kernel/tests/receipt.rs b/evm_arithmetization/src/cpu/kernel/tests/receipt.rs index ee22fc1f2..4c688d3ef 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/receipt.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/receipt.rs @@ -59,6 +59,8 @@ fn test_process_receipt() -> Result<()> { 0.into(), // data_len ], ); + interpreter.set_memory_segment(Segment::TrieData, vec![0.into()]); + interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, 1.into()); interpreter.set_txn_field(NormalizedTxnField::GasLimit, U256::from(5000)); interpreter.set_memory_segment(Segment::TxnBloom, vec![0.into(); 256]); interpreter.set_memory_segment(Segment::Logs, vec![0.into()]); @@ -69,9 +71,11 @@ fn test_process_receipt() -> Result<()> { let segment_read = interpreter.get_memory_segment(Segment::TrieData); - // The expected TrieData has the form [payload_len, status, cum_gas_used, - // bloom_filter, logs_payload_len, num_logs, [logs]] - let mut expected_trie_data: Vec = vec![323.into(), success, 2000.into()]; + // The expected TrieData has the form [0, payload_len, status, cum_gas_used, + // bloom_filter, logs_payload_len, num_logs, [logs]]. + // The 0 is always the first element of `TrieSegmentData`, as it corresponds to + // the null pointer. + let mut expected_trie_data: Vec = vec![0.into(), 323.into(), success, 2000.into()]; expected_trie_data.extend( expected_bloom .into_iter() diff --git a/evm_arithmetization/src/generation/linked_list.rs b/evm_arithmetization/src/generation/linked_list.rs new file mode 100644 index 000000000..d72cf3340 --- /dev/null +++ b/evm_arithmetization/src/generation/linked_list.rs @@ -0,0 +1,103 @@ +use std::collections::HashSet; +use std::fmt; + +use anyhow::Result; +use env_logger::try_init_from_env; +use env_logger::Env; +use env_logger::DEFAULT_FILTER_ENV; +use ethereum_types::{Address, H160, U256}; +use itertools::Itertools; +use num::traits::ToBytes; +use plonky2::field::goldilocks_field::GoldilocksField as F; +use plonky2_maybe_rayon::rayon::iter; +use rand::{thread_rng, Rng}; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::memory::segments::Segment; +use crate::util::u256_to_usize; +use crate::witness::errors::ProgramError; +use crate::witness::errors::ProverInputError; +use crate::witness::errors::ProverInputError::InvalidInput; +use crate::witness::memory::MemoryAddress; + +// A linked list implemented using a vector `access_list_mem`. +// In this representation, the values of nodes are stored in the range +// `access_list_mem[i..i + node_size - 1]`, and `access_list_mem[i + node_size - +// 1]` holds the address of the next node, where i = node_size * j. +#[derive(Clone)] +pub(crate) struct LinkedList<'a, const N: usize> { + mem: &'a [Option], + mem_len: usize, + offset: usize, + pos: usize, +} + +pub(crate) fn empty_list_mem(segment: Segment) -> [Option; N] { + std::array::from_fn(|i| { + if i == 0 { + Some(U256::MAX) + } else if i == N - 1 { + Some((segment as usize).into()) + } else { + Some(U256::zero()) + } + }) +} + +impl<'a, const N: usize> LinkedList<'a, N> { + pub fn from_mem_and_segment( + mem: &'a [Option], + segment: Segment, + ) -> Result { + Self::from_mem_len_and_segment(mem, segment) + } + + pub fn from_mem_len_and_segment( + mem: &'a [Option], + segment: Segment, + ) -> Result { + if mem.is_empty() { + return Err(ProgramError::ProverInputError(InvalidInput)); + } + Ok(Self { + mem, + mem_len: mem.len(), + offset: segment as usize, + pos: 0, + }) + } +} + +impl<'a, const N: usize> fmt::Debug for LinkedList<'a, N> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Linked List {{"); + let cloned_list = self.clone(); + for node in cloned_list { + if node[0] == U256::MAX { + writeln!(f, "{:?}", node); + break; + } + writeln!(f, "{:?} ->", node); + } + write!(f, "}}") + } +} + +impl<'a, const N: usize> Iterator for LinkedList<'a, N> { + type Item = [U256; N]; + + fn next(&mut self) -> Option { + // The first node is always the special node, so we skip it in the first + // iteration. + if let Ok(new_pos) = u256_to_usize(self.mem[self.pos + N - 1].unwrap_or_default()) { + self.pos = new_pos - self.offset; + Some(std::array::from_fn(|i| { + self.mem[self.pos + i].unwrap_or_default() + })) + } else { + None + } + } +} diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index e79099e6a..a669ef2e3 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -32,11 +32,12 @@ use crate::util::{h2u, u256_to_usize}; use crate::witness::memory::{MemoryAddress, MemoryChannel, MemoryState}; use crate::witness::state::RegistersState; +pub(crate) mod linked_list; pub mod mpt; pub(crate) mod prover_input; pub(crate) mod rlp; pub(crate) mod state; -mod trie_extractor; +pub(crate) mod trie_extractor; use crate::witness::util::mem_write_log; @@ -94,6 +95,7 @@ pub struct GenerationInputs { /// post pre-initialization processing. #[derive(Clone, Debug, Deserialize, Serialize, Default)] pub(crate) struct TrimmedGenerationInputs { + pub(crate) trimmed_tries: TrimmedTrieInputs, /// The index of the transaction being proven within its block. pub(crate) txn_number_before: U256, /// The cumulative gas used through the execution of all transactions prior @@ -151,12 +153,33 @@ pub struct TrieInputs { pub storage_tries: Vec<(H256, HashedPartialTrie)>, } +#[derive(Clone, Debug, Deserialize, Serialize, Default)] +pub struct TrimmedTrieInputs { + /// A partial version of the state trie prior to these transactions. It + /// should include all nodes that will be accessed by these + /// transactions. + pub state_trie: HashedPartialTrie, + /// A partial version of each storage trie prior to these transactions. It + /// should include all storage tries, and nodes therein, that will be + /// accessed by these transactions. + pub storage_tries: Vec<(H256, HashedPartialTrie)>, +} + +impl TrieInputs { + pub(crate) fn trim(&self) -> TrimmedTrieInputs { + TrimmedTrieInputs { + state_trie: self.state_trie.clone(), + storage_tries: self.storage_tries.clone(), + } + } +} impl GenerationInputs { /// Outputs a trimmed version of the `GenerationInputs`, that do not contain /// the fields that have already been processed during pre-initialization, /// namely: the input tries, the signed transaction, and the withdrawals. pub(crate) fn trim(&self) -> TrimmedGenerationInputs { TrimmedGenerationInputs { + trimmed_tries: self.tries.trim(), txn_number_before: self.txn_number_before, gas_used_before: self.gas_used_before, gas_used_after: self.gas_used_after, @@ -388,6 +411,7 @@ pub fn generate_traces, const D: usize>( // previous segment execution, if any. let GenerationSegmentData { is_dummy, + set_preinit, segment_index, max_cpu_len_log, memory, @@ -396,6 +420,10 @@ pub fn generate_traces, const D: usize>( extra_data, } = segment_data; + if segment_data.set_preinit { + state.memory.preinitialized_segments = segment_data.memory.preinitialized_segments.clone(); + } + for &(address, val) in &actual_mem_before { state.memory.set(address, val); } diff --git a/evm_arithmetization/src/generation/mpt.rs b/evm_arithmetization/src/generation/mpt.rs index c8d266e14..edb7ab71a 100644 --- a/evm_arithmetization/src/generation/mpt.rs +++ b/evm_arithmetization/src/generation/mpt.rs @@ -10,8 +10,12 @@ use rlp::{Decodable, DecoderError, Encodable, PayloadInfo, Rlp, RlpStream}; use rlp_derive::{RlpDecodable, RlpEncodable}; use serde::{Deserialize, Serialize}; +use super::linked_list::empty_list_mem; +use super::prover_input::{ACCOUNTS_LINKED_LIST_NODE_SIZE, STORAGE_LINKED_LIST_NODE_SIZE}; +use super::TrimmedTrieInputs; use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::generation::TrieInputs; +use crate::memory::segments::Segment; use crate::util::h2u; use crate::witness::errors::{ProgramError, ProverInputError}; use crate::Node; @@ -26,7 +30,7 @@ pub struct AccountRlp { #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct TrieRootPtrs { - pub state_root_ptr: usize, + pub state_root_ptr: Option, pub txn_root_ptr: usize, pub receipt_root_ptr: usize, } @@ -127,7 +131,7 @@ const fn empty_nibbles() -> Nibbles { fn load_mpt( trie: &HashedPartialTrie, - trie_data: &mut Vec, + trie_data: &mut Vec>, parse_value: &F, ) -> Result where @@ -136,66 +140,65 @@ where let node_ptr = trie_data.len(); let type_of_trie = PartialTrieType::of(trie) as u32; if type_of_trie > 0 { - trie_data.push(type_of_trie.into()); + trie_data.push(Some(type_of_trie.into())); } match trie.deref() { Node::Empty => Ok(0), Node::Hash(h) => { - trie_data.push(h2u(*h)); - + trie_data.push(Some(h2u(*h))); Ok(node_ptr) } Node::Branch { children, value } => { // First, set children pointers to 0. let first_child_ptr = trie_data.len(); - trie_data.extend(vec![U256::zero(); 16]); + trie_data.extend(vec![Some(U256::zero()); 16]); // Then, set value. if value.is_empty() { - trie_data.push(U256::zero()); + trie_data.push(Some(U256::zero())); } else { - let parsed_value = parse_value(value)?; - trie_data.push((trie_data.len() + 1).into()); + let parsed_value = parse_value(value)?.into_iter().map(Some); + trie_data.push(Some((trie_data.len() + 1).into())); trie_data.extend(parsed_value); } // Now, load all children and update their pointers. for (i, child) in children.iter().enumerate() { let child_ptr = load_mpt(child, trie_data, parse_value)?; - trie_data[first_child_ptr + i] = child_ptr.into(); + trie_data[first_child_ptr + i] = Some(child_ptr.into()); } Ok(node_ptr) } Node::Extension { nibbles, child } => { - trie_data.push(nibbles.count.into()); - trie_data.push( + trie_data.push(Some(nibbles.count.into())); + trie_data.push(Some( nibbles .try_into() .map_err(|_| ProgramError::IntegerTooLarge)?, - ); - trie_data.push((trie_data.len() + 1).into()); + )); + trie_data.push(Some((trie_data.len() + 1).into())); let child_ptr = load_mpt(child, trie_data, parse_value)?; if child_ptr == 0 { - trie_data.push(0.into()); + trie_data.push(Some(0.into())); } Ok(node_ptr) } Node::Leaf { nibbles, value } => { - trie_data.push(nibbles.count.into()); - trie_data.push( + trie_data.push(Some(nibbles.count.into())); + trie_data.push(Some( nibbles .try_into() .map_err(|_| ProgramError::IntegerTooLarge)?, - ); + )); // Set `value_ptr_ptr`. - trie_data.push((trie_data.len() + 1).into()); + trie_data.push(Some((trie_data.len() + 1).into())); - let leaf = parse_value(value)?; + let leaf = parse_value(value)?.into_iter().map(Some); trie_data.extend(leaf); Ok(node_ptr) @@ -206,19 +209,19 @@ where fn load_state_trie( trie: &HashedPartialTrie, key: Nibbles, - trie_data: &mut Vec, + trie_data: &mut Vec>, + storage_tries_by_state_key: &HashMap, ) -> Result { let node_ptr = trie_data.len(); let type_of_trie = PartialTrieType::of(trie) as u32; if type_of_trie > 0 { - trie_data.push(type_of_trie.into()); + trie_data.push(Some(type_of_trie.into())); } match trie.deref() { Node::Empty => Ok(0), Node::Hash(h) => { - trie_data.push(h2u(*h)); - + trie_data.push(Some(h2u(*h))); Ok(node_ptr) } Node::Branch { children, value } => { @@ -229,9 +232,9 @@ fn load_state_trie( } // First, set children pointers to 0. let first_child_ptr = trie_data.len(); - trie_data.extend(vec![U256::zero(); 16]); + trie_data.extend(vec![Some(U256::zero()); 16]); // Then, set value pointer to 0. - trie_data.push(U256::zero()); + trie_data.push(Some(U256::zero())); // Now, load all children and update their pointers. for (i, child) in children.iter().enumerate() { @@ -242,25 +245,25 @@ fn load_state_trie( let child_ptr = load_state_trie(child, extended_key, trie_data, storage_tries_by_state_key)?; - trie_data[first_child_ptr + i] = child_ptr.into(); + trie_data[first_child_ptr + i] = Some(child_ptr.into()); } Ok(node_ptr) } Node::Extension { nibbles, child } => { - trie_data.push(nibbles.count.into()); - trie_data.push( + trie_data.push(Some(nibbles.count.into())); + trie_data.push(Some( nibbles .try_into() .map_err(|_| ProgramError::IntegerTooLarge)?, - ); + )); // Set `value_ptr_ptr`. - trie_data.push((trie_data.len() + 1).into()); + trie_data.push(Some((trie_data.len() + 1).into())); let extended_key = key.merge_nibbles(nibbles); let child_ptr = load_state_trie(child, extended_key, trie_data, storage_tries_by_state_key)?; if child_ptr == 0 { - trie_data.push(0.into()); + trie_data.push(Some(0.into())); } Ok(node_ptr) @@ -284,24 +287,24 @@ fn load_state_trie( assert_eq!(storage_trie.hash(), storage_root, "In TrieInputs, an account's storage_root didn't match the associated storage trie hash"); - trie_data.push(nibbles.count.into()); - trie_data.push( + trie_data.push(Some(nibbles.count.into())); + trie_data.push(Some( nibbles .try_into() .map_err(|_| ProgramError::IntegerTooLarge)?, - ); + )); // Set `value_ptr_ptr`. - trie_data.push((trie_data.len() + 1).into()); + trie_data.push(Some((trie_data.len() + 1).into())); - trie_data.push(nonce); - trie_data.push(balance); + trie_data.push(Some(nonce)); + trie_data.push(Some(balance)); // Storage trie ptr. let storage_ptr_ptr = trie_data.len(); - trie_data.push((trie_data.len() + 2).into()); - trie_data.push(code_hash.into_uint()); + trie_data.push(Some((trie_data.len() + 2).into())); + trie_data.push(Some(code_hash.into_uint())); let storage_ptr = load_mpt(storage_trie, trie_data, &parse_storage_value)?; if storage_ptr == 0 { - trie_data[storage_ptr_ptr] = 0.into(); + trie_data[storage_ptr_ptr] = Some(0.into()); } Ok(node_ptr) @@ -309,10 +312,211 @@ fn load_state_trie( } } -pub(crate) fn load_all_mpts( +fn get_state_and_storage_leaves( + trie: &HashedPartialTrie, + key: Nibbles, + state_leaves: &mut Vec>, + storage_leaves: &mut Vec>, + trie_data: &mut Vec>, + storage_tries_by_state_key: &HashMap, +) -> Result<(), ProgramError> { + match trie.deref() { + Node::Branch { children, value } => { + if !value.is_empty() { + return Err(ProgramError::ProverInputError( + ProverInputError::InvalidMptInput, + )); + } + + for (i, child) in children.iter().enumerate() { + let extended_key = key.merge_nibbles(&Nibbles { + count: 1, + packed: i.into(), + }); + + get_state_and_storage_leaves( + child, + extended_key, + state_leaves, + storage_leaves, + trie_data, + storage_tries_by_state_key, + )?; + } + + Ok(()) + } + Node::Extension { nibbles, child } => { + let extended_key = key.merge_nibbles(nibbles); + get_state_and_storage_leaves( + child, + extended_key, + state_leaves, + storage_leaves, + trie_data, + storage_tries_by_state_key, + )?; + + Ok(()) + } + 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" + ); + + // The last leaf must point to the new one. + let len = state_leaves.len(); + state_leaves[len - 1] = Some(U256::from( + Segment::AccountsLinkedList as usize + state_leaves.len(), + )); + // The nibbles are the address. + let address = merged_key + .try_into() + .map_err(|_| ProgramError::IntegerTooLarge)?; + state_leaves.push(Some(address)); + // Set `value_ptr_ptr`. + state_leaves.push(Some(trie_data.len().into())); + // Set counter. + state_leaves.push(Some(0.into())); + // Set the next node as the initial node. + state_leaves.push(Some((Segment::AccountsLinkedList as usize).into())); + + // Push the payload in the trie data. + trie_data.push(Some(nonce)); + trie_data.push(Some(balance)); + // The Storage pointer is only written in the trie. + trie_data.push(Some(0.into())); + trie_data.push(Some(code_hash.into_uint())); + get_storage_leaves( + address, + empty_nibbles(), + storage_trie, + storage_leaves, + trie_data, + &parse_storage_value, + )?; + + Ok(()) + } + _ => Ok(()), + } +} + +pub(crate) fn get_storage_leaves( + address: U256, + key: Nibbles, + trie: &HashedPartialTrie, + storage_leaves: &mut Vec>, + trie_data: &mut Vec>, + parse_value: &F, +) -> Result<(), ProgramError> +where + F: Fn(&[u8]) -> Result, ProgramError>, +{ + match trie.deref() { + Node::Branch { children, value } => { + // Now, load all children and update their pointers. + for (i, child) in children.iter().enumerate() { + let extended_key = key.merge_nibbles(&Nibbles { + count: 1, + packed: i.into(), + }); + get_storage_leaves( + address, + extended_key, + child, + storage_leaves, + trie_data, + parse_value, + )?; + } + + Ok(()) + } + + Node::Extension { nibbles, child } => { + let extended_key = key.merge_nibbles(nibbles); + get_storage_leaves( + address, + extended_key, + child, + storage_leaves, + trie_data, + parse_value, + )?; + + Ok(()) + } + Node::Leaf { nibbles, value } => { + // The last leaf must point to the new one. + let len = storage_leaves.len(); + let merged_key = key.merge_nibbles(nibbles); + storage_leaves[len - 1] = Some(U256::from( + Segment::StorageLinkedList as usize + storage_leaves.len(), + )); + // Write the address. + storage_leaves.push(Some(address)); + // Write the key. + storage_leaves.push(Some( + merged_key + .try_into() + .map_err(|_| ProgramError::IntegerTooLarge)?, + )); + // Write `value_ptr_ptr`. + storage_leaves.push(Some((trie_data.len()).into())); + // Write the counter. + storage_leaves.push(Some(0.into())); + // Set the next node as the initial node. + storage_leaves.push(Some((Segment::StorageLinkedList as usize).into())); + + let leaf = parse_value(value)?.into_iter().map(Some); + trie_data.extend(leaf); + + Ok(()) + } + _ => Ok(()), + } +} + +/// A type alias used to gather: +/// - the trie root pointers for all tries +/// - the vector of state trie leaves +/// - the vector of storage trie leaves +/// - the `TrieData` segment's memory content +type TriePtrsLinkedLists = ( + TrieRootPtrs, + Vec>, + Vec>, + Vec>, +); + +pub(crate) fn load_linked_lists_and_txn_and_receipt_mpts( trie_inputs: &TrieInputs, -) -> Result<(TrieRootPtrs, Vec), ProgramError> { - let mut trie_data = vec![U256::zero()]; +) -> Result { + let mut state_leaves = + empty_list_mem::(Segment::AccountsLinkedList).to_vec(); + let mut storage_leaves = + empty_list_mem::(Segment::StorageLinkedList).to_vec(); + let mut trie_data = vec![Some(U256::zero())]; + let storage_tries_by_state_key = trie_inputs .storage_tries .iter() @@ -323,13 +527,6 @@ pub(crate) fn load_all_mpts( }) .collect(); - let state_root_ptr = load_state_trie( - &trie_inputs.state_trie, - empty_nibbles(), - &mut trie_data, - &storage_tries_by_state_key, - )?; - let txn_root_ptr = load_mpt(&trie_inputs.transactions_trie, &mut trie_data, &|rlp| { let mut parsed_txn = vec![U256::from(rlp.len())]; parsed_txn.extend(rlp.iter().copied().map(U256::from)); @@ -338,13 +535,47 @@ pub(crate) fn load_all_mpts( let receipt_root_ptr = load_mpt(&trie_inputs.receipts_trie, &mut trie_data, &parse_receipts)?; - let trie_root_ptrs = TrieRootPtrs { - state_root_ptr, - txn_root_ptr, - receipt_root_ptr, - }; + get_state_and_storage_leaves( + &trie_inputs.state_trie, + empty_nibbles(), + &mut state_leaves, + &mut storage_leaves, + &mut trie_data, + &storage_tries_by_state_key, + ); + + Ok(( + TrieRootPtrs { + state_root_ptr: None, + txn_root_ptr, + receipt_root_ptr, + }, + state_leaves, + storage_leaves, + trie_data, + )) +} - Ok((trie_root_ptrs, trie_data)) +pub(crate) fn load_state_mpt( + trie_inputs: &TrimmedTrieInputs, + trie_data: &mut Vec>, +) -> Result { + 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()) + .expect("An H256 is 32 bytes long"); + (key, storage_trie) + }) + .collect(); + + load_state_trie( + &trie_inputs.state_trie, + empty_nibbles(), + trie_data, + &storage_tries_by_state_key, + ) } pub mod transaction_testing { diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 0641b8bde..e9838baab 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -5,10 +5,16 @@ use std::str::FromStr; use anyhow::{bail, Error}; use ethereum_types::{BigEndianHash, H256, U256, U512}; use itertools::Itertools; +use log::{Level, Log}; +use mpt_trie::partial_trie::HashedPartialTrie; use num_bigint::BigUint; use plonky2::field::types::Field; use serde::{Deserialize, Serialize}; +use super::linked_list::LinkedList; +use super::mpt::load_state_mpt; +use super::state::State; +use super::trie_extractor::get_state_trie; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::kernel::interpreter::simulate_cpu_and_get_user_jumps; @@ -33,6 +39,11 @@ use crate::witness::util::{current_context_peek, stack_peek}; #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct ProverInputFn(Vec); +pub const ADDRESSES_ACCESS_LIST_LEN: usize = 2; +pub const STORAGE_KEYS_ACCESS_LIST_LEN: usize = 4; +pub const ACCOUNTS_LINKED_LIST_NODE_SIZE: usize = 4; +pub const STORAGE_LINKED_LIST_NODE_SIZE: usize = 5; + impl From> for ProverInputFn { fn from(v: Vec) -> Self { Self(v) @@ -55,6 +66,7 @@ impl GenerationState { "num_bits" => self.run_num_bits(), "jumpdest_table" => self.run_jumpdest_table(input_fn), "access_lists" => self.run_access_lists(input_fn), + "linked_list" => self.run_linked_list(input_fn), _ => Err(ProgramError::ProverInputError(InvalidFunction)), } } @@ -74,9 +86,43 @@ impl GenerationState { fn run_trie_ptr(&mut self, input_fn: &ProverInputFn) -> Result { let trie = input_fn.0[1].as_str(); match trie { - "state" => Ok(U256::from(self.trie_root_ptrs.state_root_ptr)), + "state" => self + .trie_root_ptrs + .state_root_ptr + .map_or_else( + || { + self.set_preinit = true; + let mut new_content = self.memory.get_preinit_memory(Segment::TrieData); + + let n = load_state_mpt(&self.inputs.trimmed_tries, &mut new_content)?; + + self.memory.insert_preinitialized_segment( + Segment::TrieData, + crate::witness::memory::MemorySegmentState { + content: new_content, + }, + ); + Ok(n) + }, + Ok, + ) + .map(U256::from), "txn" => Ok(U256::from(self.trie_root_ptrs.txn_root_ptr)), "receipt" => Ok(U256::from(self.trie_root_ptrs.receipt_root_ptr)), + "trie_data_size" => Ok(self + .memory + .preinitialized_segments + .get(&Segment::TrieData) + .unwrap_or(&crate::witness::memory::MemorySegmentState { content: vec![] }) + .content + .len() + .max( + self.memory.contexts[0].segments[Segment::TrieData.unscale()] + .content + .len(), + ) + .into()), + _ => Err(ProgramError::ProverInputError(InvalidInput)), } } @@ -270,6 +316,51 @@ impl GenerationState { } } + /// Generates either the next used jump address or the proof for the last + /// jump address. + fn run_linked_list(&mut self, input_fn: &ProverInputFn) -> Result { + match input_fn.0[1].as_str() { + "insert_account" => self.run_next_insert_account(), + "remove_account" => self.run_next_remove_account(), + "insert_slot" => self.run_next_insert_slot(), + "remove_slot" => self.run_next_remove_slot(), + "remove_address_slots" => self.run_next_remove_address_slots(), + "accounts_linked_list_len" => { + let len = self + .memory + .preinitialized_segments + .get(&Segment::AccountsLinkedList) + .unwrap_or(&crate::witness::memory::MemorySegmentState { content: vec![] }) + .content + .len() + .max( + self.memory.contexts[0].segments[Segment::AccountsLinkedList.unscale()] + .content + .len(), + ); + + Ok((Segment::AccountsLinkedList as usize + len).into()) + } + "storage_linked_list_len" => { + let len = self + .memory + .preinitialized_segments + .get(&Segment::StorageLinkedList) + .unwrap_or(&crate::witness::memory::MemorySegmentState { content: vec![] }) + .content + .len() + .max( + self.memory.contexts[0].segments[Segment::StorageLinkedList.unscale()] + .content + .len(), + ); + + Ok((Segment::StorageLinkedList as usize + len).into()) + } + _ => Err(ProgramError::ProverInputError(InvalidInput)), + } + } + /// Returns the next used jump address. fn run_next_jumpdest_table_address(&mut self) -> Result { let context = u256_to_usize(stack_peek(self, 0)? >> CONTEXT_SCALING_FACTOR)?; @@ -321,7 +412,7 @@ impl GenerationState { /// Returns a non-jumpdest proof for the address on the top of the stack. A /// non-jumpdest proof is the closest address to the address on the top of /// the stack, if the closest address is >= 32, or zero otherwise. - fn run_next_non_jumpdest_proof(&mut self) -> Result { + fn run_next_non_jumpdest_proof(&self) -> Result { let code = self.get_current_code()?; let address = u256_to_usize(stack_peek(self, 0)?)?; let closest_opcode_addr = get_closest_opcode_address(&code, address); @@ -334,57 +425,206 @@ impl GenerationState { /// Returns a pointer to an element in the list whose value is such that /// `value <= addr < next_value` and `addr` is the top of the stack. - fn run_next_addresses_insert(&mut self) -> Result { + fn run_next_addresses_insert(&self) -> Result { let addr = stack_peek(self, 0)?; - for (curr_ptr, next_addr, _) in self.get_addresses_access_list()? { - if next_addr > addr { - // In order to avoid pointers to the next ptr, we use the fact - // that valid pointers and Segment::AccessedAddresses are always even - return Ok(((Segment::AccessedAddresses as usize + curr_ptr) / 2usize).into()); - } + if let Some((([_, ptr], _), _)) = self + .get_addresses_access_list()? + .zip(self.get_addresses_access_list()?.skip(1)) + .zip(self.get_addresses_access_list()?.skip(2)) + .find(|&((_, [prev_addr, _]), [next_addr, _])| { + (prev_addr <= addr || prev_addr == U256::MAX) && addr < next_addr + }) + { + Ok(ptr / U256::from(2)) + } else { + Ok((Segment::AccessedAddresses as usize).into()) } - Ok((Segment::AccessedAddresses as usize).into()) } /// Returns a pointer to an element in the list whose value is such that /// `value < addr == next_value` and addr is the top of the stack. - /// If the element is not in the list returns loops forever - fn run_next_addresses_remove(&mut self) -> Result { + /// If the element is not in the list, it loops forever + fn run_next_addresses_remove(&self) -> Result { let addr = stack_peek(self, 0)?; - for (curr_ptr, next_addr, _) in self.get_addresses_access_list()? { - if next_addr == addr { - return Ok(((Segment::AccessedAddresses as usize + curr_ptr) / 2usize).into()); - } + if let Some(([_, ptr], _)) = self + .get_addresses_access_list()? + .zip(self.get_addresses_access_list()?.skip(2)) + .find(|&(_, [next_addr, _])| next_addr == addr) + { + Ok(ptr / U256::from(2)) + } else { + Ok((Segment::AccessedAddresses as usize).into()) } - Ok((Segment::AccessedAddresses as usize).into()) } /// Returns a pointer to the predecessor of the top of the stack in the /// accessed storage keys list. - fn run_next_storage_insert(&mut self) -> Result { + fn run_next_storage_insert(&self) -> Result { let addr = stack_peek(self, 0)?; let key = stack_peek(self, 1)?; - for (curr_ptr, next_addr, next_key) in self.get_storage_keys_access_list()? { - if next_addr > addr || (next_addr == addr && next_key > key) { - // In order to avoid pointers to the key, value or next ptr, we use the fact - // that valid pointers and Segment::AccessedAddresses are always multiples of 4 - return Ok(((Segment::AccessedStorageKeys as usize + curr_ptr) / 4usize).into()); - } + if let Some((([.., ptr], _), _)) = self + .get_storage_keys_access_list()? + .zip(self.get_storage_keys_access_list()?.skip(1)) + .zip(self.get_storage_keys_access_list()?.skip(2)) + .find( + |&((_, [prev_addr, prev_key, ..]), [next_addr, next_key, ..])| { + let prev_is_less_or_equal = (prev_addr < addr || prev_addr == U256::MAX) + || (prev_addr == addr && prev_key <= key); + let next_is_strictly_larger = + next_addr > addr || (next_addr == addr && next_key > key); + prev_is_less_or_equal && next_is_strictly_larger + }, + ) + { + Ok(ptr / U256::from(4)) + } else { + Ok((Segment::AccessedStorageKeys as usize).into()) } - Ok((Segment::AccessedAddresses as usize).into()) } /// Returns a pointer to the predecessor of the top of the stack in the /// accessed storage keys list. - fn run_next_storage_remove(&mut self) -> Result { + fn run_next_storage_remove(&self) -> Result { let addr = stack_peek(self, 0)?; let key = stack_peek(self, 1)?; - for (curr_ptr, next_addr, next_key) in self.get_storage_keys_access_list()? { - if (next_addr == addr && next_key == key) || next_addr == U256::MAX { - return Ok(((Segment::AccessedStorageKeys as usize + curr_ptr) / 4usize).into()); - } + if let Some(([.., ptr], _)) = self + .get_storage_keys_access_list()? + .zip(self.get_storage_keys_access_list()?.skip(2)) + .find(|&(_, [next_addr, next_key, ..])| (next_addr == addr && next_key == key)) + { + Ok(ptr / U256::from(4)) + } else { + Ok((Segment::AccessedStorageKeys as usize).into()) + } + } + + /// Returns a pointer to a node in the list such that + /// `node[0] <= addr < next_node[0]` and `addr` is the top of the stack. + fn run_next_insert_account(&self) -> Result { + let addr = stack_peek(self, 0)?; + let accounts_mem = self.memory.get_preinit_memory(Segment::AccountsLinkedList); + let accounts_linked_list = + LinkedList::::from_mem_and_segment( + &accounts_mem, + Segment::AccountsLinkedList, + )?; + + if let Some(([.., pred_ptr], [node_addr, ..], _)) = accounts_linked_list + .tuple_windows() + .find(|&(_, [prev_addr, ..], [next_addr, ..])| { + (prev_addr <= addr || prev_addr == U256::MAX) && addr < next_addr + }) + { + Ok(pred_ptr / U256::from(ACCOUNTS_LINKED_LIST_NODE_SIZE)) + } else { + Ok((Segment::AccountsLinkedList as usize).into()) + } + } + + /// Returns an unscaled pointer to an element in the list such that + /// `node[0] <= addr < next_node[0]`, or node[0] == addr and `node[1] <= + /// key < next_node[1]`, where `addr` and `key` are the elements at the top + /// of the stack. + fn run_next_insert_slot(&self) -> Result { + let addr = stack_peek(self, 0)?; + let key = stack_peek(self, 1)?; + let storage_mem = self.memory.get_preinit_memory(Segment::StorageLinkedList); + let storage_linked_list = + LinkedList::::from_mem_and_segment( + &storage_mem, + Segment::StorageLinkedList, + )?; + + if let Some(([.., pred_ptr], _, _)) = storage_linked_list.tuple_windows().find( + |&(_, [prev_addr, prev_key, ..], [next_addr, next_key, ..])| { + let prev_is_less_or_equal = (prev_addr < addr || prev_addr == U256::MAX) + || (prev_addr == addr && prev_key <= key); + let next_is_strictly_larger = + next_addr > addr || (next_addr == addr && next_key > key); + prev_is_less_or_equal && next_is_strictly_larger + }, + ) { + Ok((pred_ptr - U256::from(Segment::StorageLinkedList as usize)) + / U256::from(STORAGE_LINKED_LIST_NODE_SIZE)) + } else { + Ok(U256::zero()) + } + } + + /// Returns a pointer `ptr` to a node of the form [next_addr, ..] in the + /// list such that `next_addr = addr` and `addr` is the top of the stack. + /// If the element is not in the list, loops forever. + fn run_next_remove_account(&self) -> Result { + let addr = stack_peek(self, 0)?; + let accounts_mem = self.memory.get_preinit_memory(Segment::AccountsLinkedList); + let accounts_linked_list = + LinkedList::::from_mem_and_segment( + &accounts_mem, + Segment::AccountsLinkedList, + )?; + + if let Some(([.., ptr], _, _)) = accounts_linked_list + .tuple_windows() + .find(|&(_, _, [next_node_addr, ..])| next_node_addr == addr) + { + Ok(ptr / ACCOUNTS_LINKED_LIST_NODE_SIZE) + } else { + Ok((Segment::AccountsLinkedList as usize).into()) + } + } + + /// Returns a pointer `ptr` to a node = `[next_addr, next_key]` in the list + /// such that `next_addr == addr` and `next_key == key`, + /// and `addr, key` are the elements at the top of the stack. + /// If the element is not in the list, loops forever. + fn run_next_remove_slot(&self) -> Result { + let addr = stack_peek(self, 0)?; + let key = stack_peek(self, 1)?; + let storage_mem = self.memory.get_preinit_memory(Segment::StorageLinkedList); + let storage_linked_list = + LinkedList::::from_mem_and_segment( + &storage_mem, + Segment::StorageLinkedList, + )?; + + if let Some(([.., ptr], _, _)) = storage_linked_list + .tuple_windows() + .find(|&(_, _, [next_addr, next_key, ..])| next_addr == addr && next_key == key) + { + Ok((ptr - U256::from(Segment::StorageLinkedList as usize)) + / U256::from(STORAGE_LINKED_LIST_NODE_SIZE)) + } else { + Ok((Segment::StorageLinkedList as usize).into()) + } + } + + /// Returns a pointer `ptr` to a storage node in the storage linked list. + /// The node's next element = `[next_addr, next_key]` is such that + /// `next_addr = addr`, if such an element exists, or such that + /// `next_addr = @U256_MAX`. This is used to determine the first storage + /// node for the account at `addr`. `addr` is the element at the top of the + /// stack. + fn run_next_remove_address_slots(&self) -> Result { + let addr = stack_peek(self, 0)?; + let storage_mem = self.memory.get_preinit_memory(Segment::StorageLinkedList); + let storage_linked_list = + LinkedList::::from_mem_and_segment( + &storage_mem, + Segment::StorageLinkedList, + )?; + + if let Some(([.., pred_ptr], _, _)) = storage_linked_list.tuple_windows().find( + |&(_, [prev_addr, prev_key, ..], [next_addr, next_key, ..])| { + let prev_is_less = (prev_addr < addr || prev_addr == U256::MAX); + let next_is_larger_or_equal = next_addr >= addr; + prev_is_less && next_is_larger_or_equal + }, + ) { + Ok((pred_ptr - U256::from(Segment::StorageLinkedList as usize)) + / U256::from(STORAGE_LINKED_LIST_NODE_SIZE)) + } else { + Ok((Segment::StorageLinkedList as usize).into()) } - Ok((Segment::AccessedStorageKeys as usize).into()) } } @@ -468,16 +708,17 @@ impl GenerationState { } } - pub(crate) fn get_addresses_access_list(&self) -> Result { + pub(crate) fn get_addresses_access_list( + &self, + ) -> Result, ProgramError> { // `GlobalMetadata::AccessedAddressesLen` stores the value of the next available // virtual address in the segment. In order to get the length we need - // to subtract `Segment::AccessedAddresses` as usize. - let acc_addr_len = - u256_to_usize(self.get_global_metadata(GlobalMetadata::AccessedAddressesLen))? - - Segment::AccessedAddresses as usize; - AccList::from_mem_and_segment( - &self.memory.contexts[0].segments[Segment::AccessedAddresses.unscale()].content - [..acc_addr_len], + // to substract `Segment::AccessedAddresses` as usize. + let mem_len = self.memory.contexts[0].segments[Segment::AccessedAddresses.unscale()] + .content + .len(); + LinkedList::from_mem_and_segment( + &self.memory.contexts[0].segments[Segment::AccessedAddresses.unscale()].content, Segment::AccessedAddresses, ) } @@ -490,20 +731,14 @@ impl GenerationState { )) } - pub(crate) fn get_storage_keys_access_list(&self) -> Result { + pub(crate) fn get_storage_keys_access_list( + &self, + ) -> Result, ProgramError> { // GlobalMetadata::AccessedStorageKeysLen stores the value of the next available // virtual address in the segment. In order to get the length we need - // to subtract Segment::AccessedStorageKeys as usize - let acc_storage_len = u256_to_usize( - self.memory.get_with_init(MemoryAddress::new( - 0, - Segment::GlobalMetadata, - GlobalMetadata::AccessedStorageKeysLen.unscale(), - )) - Segment::AccessedStorageKeys as usize, - )?; - AccList::from_mem_and_segment( - &self.memory.contexts[0].segments[Segment::AccessedStorageKeys.unscale()].content - [..acc_storage_len], + // to substract `Segment::AccessedStorageKeys` as usize. + LinkedList::from_mem_and_segment( + &self.memory.contexts[0].segments[Segment::AccessedStorageKeys.unscale()].content, Segment::AccessedStorageKeys, ) } @@ -601,68 +836,6 @@ impl<'a> Iterator for CodeIterator<'a> { } } -// Iterates over a linked list implemented using a vector `access_list_mem`. -// In this representation, the values of nodes are stored in the range -// `access_list_mem[i..i + node_size - 1]`, and `access_list_mem[i + node_size - -// 1]` holds the address of the next node, where i = node_size * j. -pub(crate) struct AccList<'a> { - access_list_mem: &'a [Option], - node_size: usize, - offset: usize, - pos: usize, -} - -impl<'a> AccList<'a> { - const fn from_mem_and_segment( - access_list_mem: &'a [Option], - segment: Segment, - ) -> Result { - if access_list_mem.is_empty() { - return Err(ProgramError::ProverInputError(InvalidInput)); - } - Ok(Self { - access_list_mem, - node_size: match segment { - Segment::AccessedAddresses => 2, - Segment::AccessedStorageKeys => 4, - _ => return Err(ProgramError::ProverInputError(InvalidInput)), - }, - offset: segment as usize, - pos: 0, - }) - } -} - -impl<'a> Iterator for AccList<'a> { - type Item = (usize, U256, U256); - - fn next(&mut self) -> Option { - if let Ok(new_pos) = - u256_to_usize(self.access_list_mem[self.pos + self.node_size - 1].unwrap_or_default()) - { - let old_pos = self.pos; - self.pos = new_pos - self.offset; - if self.node_size == 2 { - // addresses - Some(( - old_pos, - self.access_list_mem[self.pos].unwrap_or_default(), - U256::zero(), - )) - } else { - // storage_keys - Some(( - old_pos, - self.access_list_mem[self.pos].unwrap_or_default(), - self.access_list_mem[self.pos + 1].unwrap_or_default(), - )) - } - } else { - None - } - } -} - enum EvmField { Bls381Base, Bls381Scalar, diff --git a/evm_arithmetization/src/generation/state.rs b/evm_arithmetization/src/generation/state.rs index 36108bf0b..f33d37581 100644 --- a/evm_arithmetization/src/generation/state.rs +++ b/evm_arithmetization/src/generation/state.rs @@ -6,15 +6,19 @@ use ethereum_types::{Address, BigEndianHash, H160, H256, U256}; use itertools::Itertools; use keccak_hash::keccak; use log::Level; +use mpt_trie::partial_trie::HashedPartialTrie; use plonky2::field::types::Field; -use super::mpt::{load_all_mpts, TrieRootPtrs}; +use super::mpt::TrieRootPtrs; use super::{TrieInputs, TrimmedGenerationInputs, NUM_EXTRA_CYCLES_AFTER}; use crate::byte_packing::byte_packing_stark::BytePackingOp; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; use crate::cpu::stack::MAX_USER_STACK_SIZE; +use crate::generation::mpt::load_linked_lists_and_txn_and_receipt_mpts; use crate::generation::rlp::all_rlp_prover_inputs_reversed; +use crate::generation::trie_extractor::get_state_trie; use crate::generation::CpuColumnsView; use crate::generation::GenerationInputs; use crate::keccak_sponge::columns::KECCAK_WIDTH_BYTES; @@ -180,7 +184,6 @@ pub(crate) trait State { max_cpu_len_log.map(|max_len_log| (1 << max_len_log) - NUM_EXTRA_CYCLES_AFTER); let mut final_registers = RegistersState::default(); - let final_mem = self.get_active_memory(); let mut running = true; let mut final_clock = 0; loop { @@ -265,6 +268,7 @@ pub(crate) trait State { if might_overflow_op(op) { self.get_mut_registers().check_overflow = true; } + Ok(()) } Err(e) => { @@ -336,6 +340,9 @@ pub struct GenerationState { pub(crate) next_txn_index: usize, + /// Indicates whether we should set the preinitialized segments before + /// proving. + pub(crate) set_preinit: bool, /// Memory used by stale contexts can be pruned so proving segments can be /// smaller. pub(crate) stale_contexts: Vec, @@ -369,15 +376,34 @@ pub struct GenerationState { } impl GenerationState { - fn preinitialize_mpts(&mut self, trie_inputs: &TrieInputs) -> TrieRootPtrs { - let (trie_roots_ptrs, trie_data) = - load_all_mpts(trie_inputs).expect("Invalid MPT data for preinitialization"); - - self.memory.contexts[0].segments[Segment::TrieData.unscale()].content = - trie_data.iter().map(|&val| Some(val)).collect(); + fn preinitialize_linked_lists_and_txn_and_receipt_mpts( + &mut self, + trie_inputs: &TrieInputs, + ) -> TrieRootPtrs { + let (trie_roots_ptrs, state_leaves, storage_leaves, trie_data) = + load_linked_lists_and_txn_and_receipt_mpts(trie_inputs) + .expect("Invalid MPT data for preinitialization"); + + self.memory.insert_preinitialized_segment( + Segment::AccountsLinkedList, + crate::witness::memory::MemorySegmentState { + content: state_leaves, + }, + ); + self.memory.insert_preinitialized_segment( + Segment::StorageLinkedList, + crate::witness::memory::MemorySegmentState { + content: storage_leaves, + }, + ); + self.memory.insert_preinitialized_segment( + Segment::TrieData, + crate::witness::memory::MemorySegmentState { content: trie_data }, + ); trie_roots_ptrs } + pub(crate) fn new(inputs: &GenerationInputs, kernel_code: &[u8]) -> Result { let rlp_prover_inputs = all_rlp_prover_inputs_reversed(&inputs.signed_txns); let withdrawal_prover_inputs = all_withdrawals_prover_inputs_reversed(&inputs.withdrawals); @@ -389,19 +415,21 @@ impl GenerationState { memory: MemoryState::new(kernel_code), traces: Traces::default(), next_txn_index: 0, + set_preinit: false, stale_contexts: Vec::new(), rlp_prover_inputs, withdrawal_prover_inputs, state_key_to_address: HashMap::new(), bignum_modmul_result_limbs, trie_root_ptrs: TrieRootPtrs { - state_root_ptr: 0, + state_root_ptr: Some(0), txn_root_ptr: 0, receipt_root_ptr: 0, }, jumpdest_table: None, }; - let trie_root_ptrs = state.preinitialize_mpts(&inputs.tries); + let trie_root_ptrs = + state.preinitialize_linked_lists_and_txn_and_receipt_mpts(&inputs.tries); state.trie_root_ptrs = trie_root_ptrs; Ok(state) @@ -478,13 +506,14 @@ impl GenerationState { memory: self.memory.clone(), traces: Traces::default(), next_txn_index: 0, + set_preinit: self.set_preinit, stale_contexts: Vec::new(), rlp_prover_inputs: self.rlp_prover_inputs.clone(), state_key_to_address: self.state_key_to_address.clone(), bignum_modmul_result_limbs: self.bignum_modmul_result_limbs.clone(), withdrawal_prover_inputs: self.withdrawal_prover_inputs.clone(), trie_root_ptrs: TrieRootPtrs { - state_root_ptr: 0, + state_root_ptr: Some(0), txn_root_ptr: 0, receipt_root_ptr: 0, }, diff --git a/evm_arithmetization/src/memory/columns.rs b/evm_arithmetization/src/memory/columns.rs index a6d2ac1a6..0051bbe5d 100644 --- a/evm_arithmetization/src/memory/columns.rs +++ b/evm_arithmetization/src/memory/columns.rs @@ -42,9 +42,12 @@ pub(crate) const VIRTUAL_FIRST_CHANGE: usize = SEGMENT_FIRST_CHANGE + 1; // Contains `next_segment * addr_changed * next_is_read`. pub(crate) const INITIALIZE_AUX: usize = VIRTUAL_FIRST_CHANGE + 1; +// Used to allow pre-initialization of some context 0 segments. +pub(crate) const PREINITIALIZED_SEGMENTS: usize = INITIALIZE_AUX + 1; + // Contains `row_index` + 1 if and only if context `row_index` is stale, // and zero if not. -pub(crate) const STALE_CONTEXTS: usize = INITIALIZE_AUX + 1; +pub(crate) const STALE_CONTEXTS: usize = PREINITIALIZED_SEGMENTS + 1; // Flag indicating whether the current context needs to be pruned. It is set to // 1 when the value in `STALE_CONTEXTS` is non-zero. diff --git a/evm_arithmetization/src/memory/memory_stark.rs b/evm_arithmetization/src/memory/memory_stark.rs index 53d975498..7b1415e12 100644 --- a/evm_arithmetization/src/memory/memory_stark.rs +++ b/evm_arithmetization/src/memory/memory_stark.rs @@ -19,7 +19,9 @@ use starky::evaluation_frame::StarkEvaluationFrame; use starky::lookup::{Column, Filter, Lookup}; use starky::stark::Stark; -use super::columns::{MEM_AFTER_FILTER, STALE_CONTEXTS, STALE_CONTEXTS_FREQUENCIES}; +use super::columns::{ + MEM_AFTER_FILTER, PREINITIALIZED_SEGMENTS, STALE_CONTEXTS, STALE_CONTEXTS_FREQUENCIES, +}; use super::segments::Segment; use crate::all_stark::{EvmStarkFrame, Table}; use crate::memory::columns::{ @@ -185,6 +187,11 @@ pub(crate) fn generate_first_change_flags_and_rc( let address_changed = row[CONTEXT_FIRST_CHANGE] + row[SEGMENT_FIRST_CHANGE] + row[VIRTUAL_FIRST_CHANGE]; row[INITIALIZE_AUX] = next_segment * address_changed * next_is_read; + + row[PREINITIALIZED_SEGMENTS] = (next_segment + - F::from_canonical_usize(Segment::TrieData.unscale())) + * (next_segment - F::from_canonical_usize(Segment::AccountsLinkedList.unscale())) + * (next_segment - F::from_canonical_usize(Segment::StorageLinkedList.unscale())) } } @@ -530,6 +537,18 @@ impl, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark usize { @@ -122,6 +126,8 @@ impl Segment { Self::ContextCheckpoints, Self::BlockHashes, Self::RegistersStates, + Self::AccountsLinkedList, + Self::StorageLinkedList, ] } @@ -162,6 +168,8 @@ impl Segment { Segment::ContextCheckpoints => "SEGMENT_CONTEXT_CHECKPOINTS", Segment::BlockHashes => "SEGMENT_BLOCK_HASHES", Segment::RegistersStates => "SEGMENT_REGISTERS_STATES", + Segment::AccountsLinkedList => "SEGMENT_ACCOUNTS_LINKED_LIST", + Segment::StorageLinkedList => "SEGMENT_STORAGE_LINKED_LIST", } } @@ -201,6 +209,8 @@ impl Segment { Segment::ContextCheckpoints => 256, Segment::BlockHashes => 256, Segment::RegistersStates => 256, + Segment::AccountsLinkedList => 256, + Segment::StorageLinkedList => 256, } } } diff --git a/evm_arithmetization/src/prover.rs b/evm_arithmetization/src/prover.rs index 6ab08896f..54f7165d9 100644 --- a/evm_arithmetization/src/prover.rs +++ b/evm_arithmetization/src/prover.rs @@ -39,6 +39,9 @@ use crate::witness::state::RegistersState; pub struct GenerationSegmentData { /// Indicates whether this corresponds to a dummy segment. pub(crate) is_dummy: bool, + /// Indicates whether we should set the preinitialized segments before + /// proving. + pub(crate) set_preinit: bool, /// Indicates the position of this segment in a sequence of /// executions for a larger payload. pub(crate) segment_index: usize, @@ -494,6 +497,7 @@ pub fn check_abort_signal(abort_signal: Option>) -> Result<()> { #[allow(clippy::unwrap_or_default)] fn build_segment_data( segment_index: usize, + set_preinit: bool, registers_before: Option, registers_after: Option, memory: Option, @@ -501,6 +505,7 @@ fn build_segment_data( ) -> GenerationSegmentData { GenerationSegmentData { is_dummy: false, + set_preinit, segment_index, registers_before: registers_before.unwrap_or(RegistersState::new()), registers_after: registers_after.unwrap_or(RegistersState::new()), @@ -576,7 +581,7 @@ pub(crate) fn generate_next_segment( interpreter.generation_state.memory = partial.memory.clone(); partial } else { - build_segment_data(0, None, None, None, &interpreter) + build_segment_data(0, false, None, None, None, &interpreter) }; let segment_index = segment_data.segment_index; @@ -591,6 +596,7 @@ pub(crate) fn generate_next_segment( let partial_segment_data = Some(build_segment_data( segment_index + 1, + interpreter.generation_state.set_preinit || segment_data.set_preinit, Some(updated_registers), Some(updated_registers), mem_after, @@ -621,7 +627,7 @@ pub fn generate_all_data_segments( let mut segment_index = 0; - let mut segment_data = build_segment_data(segment_index, None, None, None, &interpreter); + let mut segment_data = build_segment_data(segment_index, false, None, None, None, &interpreter); while segment_data.registers_after.program_counter != KERNEL.global_labels["halt"] { let (updated_registers, mem_after) = @@ -635,6 +641,7 @@ pub fn generate_all_data_segments( segment_data = build_segment_data( segment_index, + interpreter.generation_state.set_preinit, Some(updated_registers), Some(updated_registers), mem_after, @@ -647,7 +654,13 @@ pub fn generate_all_data_segments( /// A utility module designed to test witness generation externally. pub mod testing { + use mpt_trie::partial_trie::HashedPartialTrie; + use super::*; + use crate::{ + cpu::kernel::constants::global_metadata::GlobalMetadata, + generation::trie_extractor::get_state_trie, util::u256_to_usize, + }; use crate::{ cpu::kernel::interpreter::Interpreter, generation::{output_debug_tries, state::State}, @@ -661,6 +674,19 @@ pub mod testing { let mut interpreter: Interpreter = Interpreter::new_with_generation_inputs(initial_offset, initial_stack, &inputs, None); let result = interpreter.run(); + + let final_state_trie: HashedPartialTrie = get_state_trie( + &interpreter.get_generation_state().memory, + u256_to_usize( + interpreter + .get_generation_state() + .memory + .read_global_metadata(GlobalMetadata::StateTrieRoot), + ) + .unwrap(), + ) + .unwrap(); + if result.is_err() { output_debug_tries(interpreter.get_generation_state())?; } @@ -717,7 +743,8 @@ pub mod testing { let mut segment_index = 0; - let mut segment_data = build_segment_data(segment_index, None, None, None, &interpreter); + let mut segment_data = + build_segment_data(segment_index, false, None, None, None, &interpreter); while segment_data.registers_after.program_counter != KERNEL.global_labels["halt"] { segment_index += 1; @@ -727,6 +754,7 @@ pub mod testing { segment_data = build_segment_data( segment_index, + interpreter.generation_state.set_preinit, Some(updated_registers), Some(updated_registers), mem_after, diff --git a/evm_arithmetization/src/witness/memory.rs b/evm_arithmetization/src/witness/memory.rs index ac5215698..e69e75f96 100644 --- a/evm_arithmetization/src/witness/memory.rs +++ b/evm_arithmetization/src/witness/memory.rs @@ -79,11 +79,17 @@ impl MemoryAddress { /// It will recover the virtual offset as the lowest 32-bit limb, the /// segment as the next limb, and the context as the next one. pub(crate) fn new_bundle(addr: U256) -> Result { - let virt = addr.low_u32().into(); - let segment = (addr >> SEGMENT_SCALING_FACTOR).low_u32().into(); - let context = (addr >> CONTEXT_SCALING_FACTOR).low_u32().into(); + let virt = addr.low_u32() as usize; + let segment = (addr >> SEGMENT_SCALING_FACTOR).low_u32() as usize; + let context = (addr >> CONTEXT_SCALING_FACTOR).low_u32() as usize; + + if segment >= Segment::COUNT { + return Err(MemoryError(SegmentTooLarge { + segment: segment.into(), + })); + } - Self::new_u256s(context, segment, virt) + Ok(Self::new(context, Segment::all()[segment], virt)) } pub(crate) fn increment(&mut self) { @@ -215,6 +221,28 @@ impl MemoryState { Some(val) } + /// Returns the memory values associated with a preinitialized segment. We + /// need a specific behaviour here, since the values can be stored either in + /// `preinitialized_segments` or in the memory itself. + pub(crate) fn get_preinit_memory(&self, segment: Segment) -> Vec> { + assert!( + segment == Segment::AccountsLinkedList + || segment == Segment::StorageLinkedList + || segment == Segment::TrieData + ); + let len = self + .preinitialized_segments + .get(&segment) + .unwrap_or(&MemorySegmentState { content: vec![] }) + .content + .len() + .max(self.contexts[0].segments[segment.unscale()].content.len()); + + (0..len) + .map(|i| Some(self.get_with_init(MemoryAddress::new(0, segment, i)))) + .collect::>() + } + /// Returns a memory value, or 0 if the memory is unset. If we have some /// preinitialized segments (in interpreter mode), then the values might not /// be stored in memory yet. If the value in memory is not set and the @@ -236,7 +264,7 @@ impl MemoryState { .len() { self.preinitialized_segments.get(&segment).unwrap().content[offset] - .expect("We checked that the offset is not out of bounds.") + .unwrap_or_default() } else { 0.into() } diff --git a/evm_arithmetization/tests/log_opcode.rs b/evm_arithmetization/tests/log_opcode.rs index 0008f462e..1cc03d048 100644 --- a/evm_arithmetization/tests/log_opcode.rs +++ b/evm_arithmetization/tests/log_opcode.rs @@ -450,14 +450,14 @@ fn test_log_with_aggreg() -> anyhow::Result<()> { &all_stark, &[ 16..17, - 11..15, + 8..10, 12..17, 8..11, 8..9, 6..12, 17..20, - 16..17, - 7..17, + 16..18, + 7..18, ], &config, );