From bfd6834dc2da2eb40439f600fc6c1e4527eff278 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 12 May 2023 13:04:46 +0200 Subject: [PATCH] Journal of state changes + state reversion (#1028) * Add segments and global metadata * Add journal asm files * Start revert * Revert access lists * Revert balance transfer * Revert code change * Revert nonce change * Revert storage change * Checkpoints * Add some journal entries * Add some journal entries * Add some journal entries * Fix revert * Checkpoint in sys_call * Minor * PR feedback * More checkpoints * Fix checkpoint check * Minor * Checkpoints in precompiles * Storage change checkpoint * Add touched addresses * Add touched addresses revert * Add touched addresses journal events * Delete all empty touch addresses * Implement selfdestruct * Update aggregator.rs --- evm/src/cpu/kernel/aggregator.rs | 11 + evm/src/cpu/kernel/asm/core/access_lists.asm | 75 +++++++ evm/src/cpu/kernel/asm/core/call.asm | 21 ++ evm/src/cpu/kernel/asm/core/create.asm | 12 +- .../asm/core/create_contract_account.asm | 2 + evm/src/cpu/kernel/asm/core/nonce.asm | 14 +- .../kernel/asm/core/precompiles/blake2_f.asm | 1 + .../kernel/asm/core/precompiles/bn_add.asm | 1 + .../kernel/asm/core/precompiles/bn_mul.asm | 1 + .../cpu/kernel/asm/core/precompiles/ecrec.asm | 1 + .../kernel/asm/core/precompiles/expmod.asm | 1 + .../cpu/kernel/asm/core/precompiles/id.asm | 1 + .../kernel/asm/core/precompiles/rip160.asm | 1 + .../kernel/asm/core/precompiles/sha256.asm | 1 + .../kernel/asm/core/precompiles/snarkv.asm | 1 + evm/src/cpu/kernel/asm/core/process_txn.asm | 4 + .../cpu/kernel/asm/core/selfdestruct_list.asm | 61 +++++- evm/src/cpu/kernel/asm/core/terminate.asm | 8 +- .../cpu/kernel/asm/core/touched_addresses.asm | 103 ++++++++++ evm/src/cpu/kernel/asm/core/transfer.asm | 2 + .../kernel/asm/journal/account_destroyed.asm | 18 ++ .../cpu/kernel/asm/journal/account_loaded.asm | 12 ++ .../kernel/asm/journal/account_touched.asm | 10 + .../kernel/asm/journal/balance_transfer.asm | 16 ++ .../cpu/kernel/asm/journal/code_change.asm | 17 ++ evm/src/cpu/kernel/asm/journal/journal.asm | 192 ++++++++++++++++++ .../cpu/kernel/asm/journal/nonce_change.asm | 17 ++ evm/src/cpu/kernel/asm/journal/revert.asm | 84 ++++++++ .../cpu/kernel/asm/journal/storage_change.asm | 61 ++++++ .../cpu/kernel/asm/journal/storage_loaded.asm | 12 ++ evm/src/cpu/kernel/asm/mpt/delete/delete.asm | 21 ++ .../kernel/asm/mpt/storage/storage_write.asm | 10 +- .../cpu/kernel/constants/context_metadata.rs | 5 +- .../cpu/kernel/constants/global_metadata.rs | 18 +- evm/src/cpu/kernel/constants/journal_entry.rs | 43 ++++ evm/src/cpu/kernel/constants/mod.rs | 5 + evm/src/memory/segments.rs | 20 +- evm/src/prover.rs | 27 +++ 38 files changed, 893 insertions(+), 17 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/core/touched_addresses.asm create mode 100644 evm/src/cpu/kernel/asm/journal/account_destroyed.asm create mode 100644 evm/src/cpu/kernel/asm/journal/account_loaded.asm create mode 100644 evm/src/cpu/kernel/asm/journal/account_touched.asm create mode 100644 evm/src/cpu/kernel/asm/journal/balance_transfer.asm create mode 100644 evm/src/cpu/kernel/asm/journal/code_change.asm create mode 100644 evm/src/cpu/kernel/asm/journal/journal.asm create mode 100644 evm/src/cpu/kernel/asm/journal/nonce_change.asm create mode 100644 evm/src/cpu/kernel/asm/journal/revert.asm create mode 100644 evm/src/cpu/kernel/asm/journal/storage_change.asm create mode 100644 evm/src/cpu/kernel/asm/journal/storage_loaded.asm create mode 100644 evm/src/cpu/kernel/constants/journal_entry.rs diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index f65ba4ec..02fc48af 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -42,6 +42,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/core/util.asm"), include_str!("asm/core/access_lists.asm"), include_str!("asm/core/selfdestruct_list.asm"), + include_str!("asm/core/touched_addresses.asm"), include_str!("asm/core/precompiles/main.asm"), include_str!("asm/core/precompiles/ecrec.asm"), include_str!("asm/core/precompiles/sha256.asm"), @@ -134,6 +135,16 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/rlp/read_to_memory.asm"), include_str!("asm/shift.asm"), include_str!("asm/signed.asm"), + include_str!("asm/journal/journal.asm"), + include_str!("asm/journal/account_loaded.asm"), + include_str!("asm/journal/account_destroyed.asm"), + include_str!("asm/journal/account_touched.asm"), + include_str!("asm/journal/balance_transfer.asm"), + include_str!("asm/journal/nonce_change.asm"), + include_str!("asm/journal/storage_change.asm"), + include_str!("asm/journal/storage_loaded.asm"), + include_str!("asm/journal/code_change.asm"), + include_str!("asm/journal/revert.asm"), include_str!("asm/transactions/common_decoding.asm"), include_str!("asm/transactions/router.asm"), include_str!("asm/transactions/type_0.asm"), diff --git a/evm/src/cpu/kernel/asm/core/access_lists.asm b/evm/src/cpu/kernel/asm/core/access_lists.asm index 0c5e595e..0ba56c30 100644 --- a/evm/src/cpu/kernel/asm/core/access_lists.asm +++ b/evm/src/cpu/kernel/asm/core/access_lists.asm @@ -41,6 +41,7 @@ insert_accessed_addresses_loop: insert_address: %stack (i, len, addr, retdest) -> (i, addr, len, retdest) + DUP2 %journal_add_account_loaded // Add a journal entry for the loaded account. %mstore_kernel(@SEGMENT_ACCESSED_ADDRESSES) // Store new address at the end of the array. // stack: len, retdest %increment @@ -52,6 +53,35 @@ insert_accessed_addresses_found: %stack (i, len, addr, retdest) -> (retdest, 0) // Return 0 to indicate that the address was already present. JUMP +/// Remove the address from the access list. +/// Panics if the address is not in the access list. +global remove_accessed_addresses: + // stack: addr, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESSED_ADDRESSES_LEN) + // stack: len, addr, retdest + PUSH 0 +remove_accessed_addresses_loop: + %stack (i, len, addr, retdest) -> (i, len, i, len, addr, retdest) + EQ %jumpi(panic) + // stack: i, len, addr, retdest + DUP1 %mload_kernel(@SEGMENT_ACCESSED_ADDRESSES) + // stack: loaded_addr, i, len, addr, retdest + DUP4 + // stack: addr, loaded_addr, i, len, addr, retdest + EQ %jumpi(remove_accessed_addresses_found) + // stack: i, len, addr, retdest + %increment + %jump(remove_accessed_addresses_loop) +remove_accessed_addresses_found: + %stack (i, len, addr, retdest) -> (len, 1, i, retdest) + SUB DUP1 %mstore_global_metadata(@GLOBAL_METADATA_ACCESSED_ADDRESSES_LEN) // Decrement the access list length. + // stack: len-1, i, retdest + %mload_kernel(@SEGMENT_ACCESSED_ADDRESSES) // Load the last address in the access list. + // stack: last_addr, i, retdest + SWAP1 + %mstore_kernel(@SEGMENT_ACCESSED_ADDRESSES) // Store the last address at the position of the removed address. + JUMP + %macro insert_accessed_storage_keys %stack (addr, key, value) -> (addr, key, value, %%after) @@ -87,6 +117,8 @@ insert_accessed_storage_keys_loop: %jump(insert_accessed_storage_keys_loop) insert_storage_key: + // stack: i, len, addr, key, value, retdest + DUP4 DUP4 %journal_add_storage_loaded // Add a journal entry for the loaded storage key. // stack: i, len, addr, key, value, retdest DUP1 %increment DUP1 %increment @@ -106,3 +138,46 @@ insert_accessed_storage_keys_found: %mload_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) %stack (original_value, len, addr, key, value, retdest) -> (retdest, 0, original_value) // Return 0 to indicate that the storage key was already present. JUMP + +/// Remove the storage key and its value from the access list. +/// Panics if the key is not in the list. +global remove_accessed_storage_keys: + // stack: addr, key, retdest + %mload_global_metadata(@GLOBAL_METADATA_ACCESSED_STORAGE_KEYS_LEN) + // stack: len, addr, key, retdest + PUSH 0 +remove_accessed_storage_keys_loop: + %stack (i, len, addr, key, retdest) -> (i, len, i, len, addr, key, retdest) + EQ %jumpi(panic) + // stack: i, len, addr, key, retdest + DUP1 %increment %mload_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: loaded_key, i, len, addr, key, retdest + DUP2 %mload_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: loaded_addr, loaded_key, i, len, addr, key, retdest + DUP5 EQ + // stack: loaded_addr==addr, loaded_key, i, len, addr, key, retdest + SWAP1 DUP6 EQ + // stack: loaded_key==key, loaded_addr==addr, i, len, addr, key, retdest + MUL // AND + %jumpi(remove_accessed_storage_keys_found) + // stack: i, len, addr, key, retdest + %add_const(3) + %jump(remove_accessed_storage_keys_loop) + +remove_accessed_storage_keys_found: + %stack (i, len, addr, key, retdest) -> (len, 3, i, retdest) + SUB DUP1 %mstore_global_metadata(@GLOBAL_METADATA_ACCESSED_STORAGE_KEYS_LEN) // Decrease the access list length. + // stack: len-3, i, retdest + DUP1 %add_const(2) %mload_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: last_value, len-3, i, retdest + DUP2 %add_const(1) %mload_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: last_key, last_value, len-3, i, retdest + DUP3 %mload_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: last_addr, last_key, last_value, len-3, i, retdest + DUP5 %mstore_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) // Move the last tuple to the position of the removed tuple. + // stack: last_key, last_value, len-3, i, retdest + DUP4 %add_const(1) %mstore_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: last_value, len-3, i, retdest + DUP3 %add_const(2) %mstore_kernel(@SEGMENT_ACCESSED_STORAGE_KEYS) + // stack: len-3, i, retdest + %pop2 JUMP diff --git a/evm/src/cpu/kernel/asm/core/call.asm b/evm/src/cpu/kernel/asm/core/call.asm index 0184f365..546694e3 100644 --- a/evm/src/cpu/kernel/asm/core/call.asm +++ b/evm/src/cpu/kernel/asm/core/call.asm @@ -11,6 +11,8 @@ global sys_call: MUL // Cheaper than AND %jumpi(fault_exception) + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. + %stack (kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size) -> (args_size, args_offset, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size) %checked_mem_expansion @@ -21,6 +23,7 @@ global sys_call: SWAP2 // stack: address, gas, kexit_info, value, args_offset, args_size, ret_offset, ret_size %u256_to_addr // Truncate to 160 bits + DUP1 %insert_touched_addresses DUP1 %insert_accessed_addresses %call_charge_gas(1, 1) @@ -34,6 +37,7 @@ global sys_call: %copy_mem_to_calldata // stack: new_ctx, kexit_info, callgas, address, value, args_offset, args_size, ret_offset, ret_size DUP5 DUP5 %address %transfer_eth %jumpi(panic) // TODO: Fix this panic. + DUP5 DUP5 %address %journal_add_balance_transfer DUP3 %set_new_ctx_gas_limit %set_new_ctx_parent_pc(after_call_instruction) DUP9 DUP9 DUP4 DUP4 DUP8 // Duplicate address, new_ctx, kexit_info, ret_offset, and ret_size. @@ -55,6 +59,8 @@ global sys_call: // Creates a new sub context as if calling itself, but with the code of the // given account. In particular the storage remains the same. global sys_callcode: + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. + // stack: kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size %stack (kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size) -> (args_size, args_offset, kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size) @@ -66,6 +72,7 @@ global sys_callcode: SWAP2 // stack: address, gas, kexit_info, value, args_offset, args_size, ret_offset, ret_size %u256_to_addr // Truncate to 160 bits + DUP1 %insert_touched_addresses DUP1 %insert_accessed_addresses %call_charge_gas(1, 0) @@ -103,6 +110,8 @@ global sys_callcode: // are CREATE, CREATE2, LOG0, LOG1, LOG2, LOG3, LOG4, SSTORE, SELFDESTRUCT and // CALL if the value sent is not 0. global sys_staticcall: + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. + // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size %stack (kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size) -> (args_size, args_offset, kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size) @@ -114,6 +123,7 @@ global sys_staticcall: SWAP2 // stack: address, gas, kexit_info, args_offset, args_size, ret_offset, ret_size %u256_to_addr // Truncate to 160 bits + DUP1 %insert_touched_addresses DUP1 %insert_accessed_addresses // Add a value of 0 to the stack. Slightly inefficient but that way we can reuse %call_charge_gas. @@ -151,6 +161,8 @@ global sys_staticcall: // given account. In particular the storage, the current sender and the current // value remain the same. global sys_delegatecall: + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. + // stack: kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size %stack (kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size) -> (args_size, args_offset, kexit_info, gas, address, args_offset, args_size, ret_offset, ret_size) @@ -162,6 +174,7 @@ global sys_delegatecall: SWAP2 // stack: address, gas, kexit_info, args_offset, args_size, ret_offset, ret_size %u256_to_addr // Truncate to 160 bits + DUP1 %insert_touched_addresses DUP1 %insert_accessed_addresses // Add a value of 0 to the stack. Slightly inefficient but that way we can reuse %call_charge_gas. @@ -197,6 +210,8 @@ global sys_delegatecall: // We go here after any CALL type instruction (but not after the special call by the transaction originator). global after_call_instruction: // stack: success, leftover_gas, new_ctx, kexit_info, ret_offset, ret_size + DUP1 ISZERO %jumpi(after_call_instruction_failed) +after_call_instruction_contd: SWAP3 // stack: kexit_info, leftover_gas, new_ctx, success, ret_offset, ret_size // Add the leftover gas into the appropriate bits of kexit_info. @@ -207,6 +222,11 @@ global after_call_instruction: %copy_returndata_to_mem EXIT_KERNEL +after_call_instruction_failed: + // stack: success, leftover_gas, new_ctx, kexit_info, ret_offset, ret_size + %mload_context_metadata(@CTX_METADATA_CHECKPOINT) %revert_checkpoint + %jump(after_call_instruction_contd) + // Set @CTX_METADATA_STATIC to 1. Note that there is no corresponding set_static_false routine // because it will already be 0 by default. %macro set_static_true @@ -295,6 +315,7 @@ global after_call_instruction: // Switch to the new context and go to usermode with PC=0. DUP1 // new_ctx SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. PUSH 0 // jump dest EXIT_KERNEL // (Old context) stack: new_ctx diff --git a/evm/src/cpu/kernel/asm/core/create.asm b/evm/src/cpu/kernel/asm/core/create.asm index 2edd9bbe..beeb759b 100644 --- a/evm/src/cpu/kernel/asm/core/create.asm +++ b/evm/src/cpu/kernel/asm/core/create.asm @@ -91,6 +91,7 @@ global create_common: // stack: status, address, value, code_offset, code_len, kexit_info %jumpi(fault_exception) // stack: address, value, code_offset, code_len, kexit_info + DUP2 DUP2 %address %journal_add_balance_transfer // Add journal entry for the balance transfer. %create_context // stack: new_ctx, address, value, code_offset, code_len, kexit_info @@ -192,10 +193,15 @@ after_constructor: // TODO: Should it be copy-on-write (with make_account_copy) instead of mutating the trie? global set_codehash: // stack: addr, codehash, retdest - %mpt_read_state_trie - // stack: account_ptr, codehash, retdest + DUP1 %insert_touched_addresses + DUP1 %mpt_read_state_trie + // stack: account_ptr, addr, codehash, retdest %add_const(3) - // stack: codehash_ptr, codehash, retdest + // stack: codehash_ptr, addr, codehash, retdest + DUP1 %mload_trie_data + // stack: prev_codehash, codehash_ptr, addr, codehash, retdest + DUP3 %journal_add_code_change // Add the code change to the journal. + %stack (codehash_ptr, addr, codehash) -> (codehash_ptr, codehash) %mstore_trie_data // stack: retdest JUMP diff --git a/evm/src/cpu/kernel/asm/core/create_contract_account.asm b/evm/src/cpu/kernel/asm/core/create_contract_account.asm index 00a19272..e7c346fb 100644 --- a/evm/src/cpu/kernel/asm/core/create_contract_account.asm +++ b/evm/src/cpu/kernel/asm/core/create_contract_account.asm @@ -3,6 +3,7 @@ // Post stack: status %macro create_contract_account // stack: value, address + DUP2 %insert_touched_addresses DUP2 %mpt_read_state_trie // stack: existing_account_ptr, value, address // If the account doesn't exist, there's no need to check its balance or nonce, @@ -23,6 +24,7 @@ // Write the new account's data to MPT data, and get a pointer to it. %get_trie_data_size // stack: account_ptr, new_acct_value, address + PUSH 0 DUP4 %journal_add_nonce_change PUSH 1 %append_to_trie_data // nonce = 1 // stack: account_ptr, new_acct_value, address SWAP1 %append_to_trie_data // balance = new_acct_value diff --git a/evm/src/cpu/kernel/asm/core/nonce.asm b/evm/src/cpu/kernel/asm/core/nonce.asm index 973a032d..48486be9 100644 --- a/evm/src/cpu/kernel/asm/core/nonce.asm +++ b/evm/src/cpu/kernel/asm/core/nonce.asm @@ -22,17 +22,21 @@ global nonce: // Increment the given account's nonce. Assumes the account already exists; panics otherwise. global increment_nonce: // stack: address, retdest + DUP1 %mpt_read_state_trie - // stack: account_ptr, retdest + // stack: account_ptr, address, retdest DUP1 ISZERO %jumpi(increment_nonce_no_such_account) - // stack: nonce_ptr, retdest + // stack: nonce_ptr, address, retdest DUP1 %mload_trie_data - // stack: nonce, nonce_ptr, retdest + // stack: nonce, nonce_ptr, address, retdest + DUP1 DUP4 %journal_add_nonce_change + // stack: nonce, nonce_ptr, address, retdest %increment SWAP1 - // stack: nonce_ptr, nonce', retdest + // stack: nonce_ptr, nonce', address, retdest %mstore_trie_data - // stack: retdest + // stack: address, retdest + POP JUMP global increment_nonce_no_such_account: PANIC diff --git a/evm/src/cpu/kernel/asm/core/precompiles/blake2_f.asm b/evm/src/cpu/kernel/asm/core/precompiles/blake2_f.asm index d5971a81..1810d992 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/blake2_f.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/blake2_f.asm @@ -4,6 +4,7 @@ global precompile_blake2_f: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/precompiles/bn_add.asm b/evm/src/cpu/kernel/asm/core/precompiles/bn_add.asm index 1ab4093c..fc838916 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/bn_add.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/bn_add.asm @@ -4,6 +4,7 @@ global precompile_bn_add: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/precompiles/bn_mul.asm b/evm/src/cpu/kernel/asm/core/precompiles/bn_mul.asm index 31cbbae0..b772d1c4 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/bn_mul.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/bn_mul.asm @@ -4,6 +4,7 @@ global precompile_bn_mul: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/precompiles/ecrec.asm b/evm/src/cpu/kernel/asm/core/precompiles/ecrec.asm index 777a9f77..1f143068 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/ecrec.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/ecrec.asm @@ -4,6 +4,7 @@ global precompile_ecrec: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/precompiles/expmod.asm b/evm/src/cpu/kernel/asm/core/precompiles/expmod.asm index 1af8c714..66e65011 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/expmod.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/expmod.asm @@ -133,6 +133,7 @@ global precompile_expmod: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/precompiles/id.asm b/evm/src/cpu/kernel/asm/core/precompiles/id.asm index 7cfb6dfc..5d68bd61 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/id.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/id.asm @@ -4,6 +4,7 @@ global precompile_id: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/precompiles/rip160.asm b/evm/src/cpu/kernel/asm/core/precompiles/rip160.asm index 70711234..8444088c 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/rip160.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/rip160.asm @@ -4,6 +4,7 @@ global precompile_rip160: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/precompiles/sha256.asm b/evm/src/cpu/kernel/asm/core/precompiles/sha256.asm index e03574a1..61be27fc 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/sha256.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/sha256.asm @@ -4,6 +4,7 @@ global precompile_sha256: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/precompiles/snarkv.asm b/evm/src/cpu/kernel/asm/core/precompiles/snarkv.asm index 433186b5..7c6df1a4 100644 --- a/evm/src/cpu/kernel/asm/core/precompiles/snarkv.asm +++ b/evm/src/cpu/kernel/asm/core/precompiles/snarkv.asm @@ -4,6 +4,7 @@ global precompile_snarkv: // stack: new_ctx, (old stack) DUP1 SET_CONTEXT + %checkpoint %mstore_context_metadata(@CTX_METADATA_CHECKPOINT) // Checkpoint and store it in context metadata. // stack: (empty) PUSH 0x100000000 // = 2^32 (is_kernel = true) // stack: kexit_info diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index 97d9b8c5..25327709 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -174,6 +174,8 @@ global process_contract_creation_txn_after_constructor: // stack: leftover_gas, new_ctx, address, retdest %pay_coinbase_and_refund_sender // TODO: Delete accounts in self-destruct list and empty touched addresses. + %delete_all_touched_addresses + %delete_all_selfdestructed_addresses // stack: new_ctx, address, retdest POP POP @@ -265,6 +267,8 @@ global process_message_txn_after_call: // stack: leftover_gas, new_ctx, retdest %pay_coinbase_and_refund_sender // TODO: Delete accounts in self-destruct list and empty touched addresses. + %delete_all_touched_addresses + %delete_all_selfdestructed_addresses // stack: new_ctx, retdest POP JUMP diff --git a/evm/src/cpu/kernel/asm/core/selfdestruct_list.asm b/evm/src/cpu/kernel/asm/core/selfdestruct_list.asm index cc6f5b42..5084541a 100644 --- a/evm/src/cpu/kernel/asm/core/selfdestruct_list.asm +++ b/evm/src/cpu/kernel/asm/core/selfdestruct_list.asm @@ -1,5 +1,6 @@ /// Self-destruct list. -/// Implemented as an append-only array, with the length stored in the global metadata. +/// Implemented as an array, with the length stored in the global metadata. +/// Note: This array allows duplicates. %macro insert_selfdestruct_list // stack: addr @@ -10,3 +11,61 @@ %increment %mstore_global_metadata(@GLOBAL_METADATA_SELFDESTRUCT_LIST_LEN) // Store new length. %endmacro + +/// Remove one occurrence of the address from the list. +/// Panics if the address is not in the list. +global remove_selfdestruct_list: + // stack: addr, retdest + %mload_global_metadata(@GLOBAL_METADATA_SELFDESTRUCT_LIST_LEN) + // stack: len, addr, retdest + PUSH 0 +remove_selfdestruct_list_loop: + %stack (i, len, addr, retdest) -> (i, len, i, len, addr, retdest) + EQ %jumpi(panic) + // stack: i, len, addr, retdest + DUP1 %mload_kernel(@SEGMENT_SELFDESTRUCT_LIST) + // stack: loaded_addr, i, len, addr, retdest + DUP4 + // stack: addr, loaded_addr, i, len, addr, retdest + EQ %jumpi(remove_selfdestruct_list_found) + // stack: i, len, addr, retdest + %increment + %jump(remove_selfdestruct_list_loop) +remove_selfdestruct_list_found: + %stack (i, len, addr, retdest) -> (len, 1, i, retdest) + SUB DUP1 %mstore_global_metadata(@GLOBAL_METADATA_SELFDESTRUCT_LIST_LEN) // Decrement the list length. + // stack: len-1, i, retdest + %mload_kernel(@SEGMENT_SELFDESTRUCT_LIST) // Load the last address in the list. + // stack: last_addr, i, retdest + SWAP1 + %mstore_kernel(@SEGMENT_SELFDESTRUCT_LIST) // Store the last address at the position of the removed address. + JUMP + +global delete_all_selfdestructed_addresses: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_SELFDESTRUCT_LIST_LEN) + // stack: len, retdest + PUSH 0 +delete_all_selfdestructed_addresses_loop: + // stack: i, len, retdest + DUP2 DUP2 EQ %jumpi(delete_all_selfdestructed_addresses_done) + // stack: i, len, retdest + DUP1 %mload_kernel(@SEGMENT_SELFDESTRUCT_LIST) + // stack: loaded_addr, i, len, retdest + DUP1 %is_non_existent ISZERO %jumpi(bingo) + // stack: loaded_addr, i, len, retdest + POP %increment %jump(delete_all_selfdestructed_addresses_loop) +bingo: + // stack: loaded_addr, i, len, retdest + %delete_account + %increment %jump(delete_all_selfdestructed_addresses_loop) +delete_all_selfdestructed_addresses_done: + // stack: i, len, retdest + %pop2 JUMP + +%macro delete_all_selfdestructed_addresses + %stack () -> (%%after) + %jump(delete_all_selfdestructed_addresses) +%%after: + // stack: (empty) +%endmacro diff --git a/evm/src/cpu/kernel/asm/core/terminate.asm b/evm/src/cpu/kernel/asm/core/terminate.asm index e078d9b8..c77c31bf 100644 --- a/evm/src/cpu/kernel/asm/core/terminate.asm +++ b/evm/src/cpu/kernel/asm/core/terminate.asm @@ -85,9 +85,13 @@ global sys_selfdestruct: // stack: balance_ptr, 0, balance, address, recipient, kexit_info %mstore_trie_data // TODO: This should be a copy-on-write operation. + %stack (balance, address, recipient, kexit_info) -> + (address, recipient, balance, address, recipient, recipient, balance, kexit_info) + %journal_add_account_destroyed + // If the recipient is the same as the address, then we're done. // Otherwise, send the balance to the recipient. - %stack (balance, address, recipient, kexit_info) -> (address, recipient, recipient, balance, kexit_info) + // stack: address, recipient, recipient, balance, kexit_info EQ %jumpi(sys_selfdestruct_same_addr) // stack: recipient, balance, kexit_info %add_eth @@ -148,8 +152,8 @@ sys_revert_finish: // - state modification is attempted during a static call global fault_exception: // stack: (empty) + %mload_context_metadata(@CTX_METADATA_CHECKPOINT) %revert_checkpoint PUSH 0 // leftover_gas - // TODO: Revert state changes. // Set the parent context's return data size to 0. %mstore_parent_context_metadata(@CTX_METADATA_RETURNDATA_SIZE, 0) PUSH 0 // success diff --git a/evm/src/cpu/kernel/asm/core/touched_addresses.asm b/evm/src/cpu/kernel/asm/core/touched_addresses.asm new file mode 100644 index 00000000..f2c0394a --- /dev/null +++ b/evm/src/cpu/kernel/asm/core/touched_addresses.asm @@ -0,0 +1,103 @@ +%macro insert_touched_addresses + %stack (addr) -> (addr, %%after) + %jump(insert_touched_addresses) +%%after: + // stack: (empty) +%endmacro + +%macro insert_touched_addresses_no_return + %insert_touched_addresses + POP +%endmacro + +/// Inserts the address into the list if it is not already present. +global insert_touched_addresses: + // stack: addr, retdest + %mload_global_metadata(@GLOBAL_METADATA_TOUCHED_ADDRESSES_LEN) + // stack: len, addr, retdest + PUSH 0 +insert_touched_addresses_loop: + %stack (i, len, addr, retdest) -> (i, len, i, len, addr, retdest) + EQ %jumpi(insert_address) + // stack: i, len, addr, retdest + DUP1 %mload_kernel(@SEGMENT_TOUCHED_ADDRESSES) + // stack: loaded_addr, i, len, addr, retdest + DUP4 + // stack: addr, loaded_addr, i, len, addr, retdest + EQ %jumpi(insert_touched_addresses_found) + // stack: i, len, addr, retdest + %increment + %jump(insert_touched_addresses_loop) + +insert_address: + %stack (i, len, addr, retdest) -> (i, addr, len, retdest) + DUP2 %journal_add_account_touched // Add a journal entry for the touched account. + %mstore_kernel(@SEGMENT_TOUCHED_ADDRESSES) // Store new address at the end of the array. + // stack: len, retdest + %increment + %mstore_global_metadata(@GLOBAL_METADATA_TOUCHED_ADDRESSES_LEN) // Store new length. + JUMP + +insert_touched_addresses_found: + %stack (i, len, addr, retdest) -> (retdest) + JUMP + +/// Remove the address from the list. +/// Panics if the address is not in the list. +/// TODO: Unused? +global remove_touched_addresses: + // stack: addr, retdest + %mload_global_metadata(@GLOBAL_METADATA_TOUCHED_ADDRESSES_LEN) + // stack: len, addr, retdest + PUSH 0 +remove_touched_addresses_loop: + %stack (i, len, addr, retdest) -> (i, len, i, len, addr, retdest) + EQ %jumpi(panic) + // stack: i, len, addr, retdest + DUP1 %mload_kernel(@SEGMENT_TOUCHED_ADDRESSES) + // stack: loaded_addr, i, len, addr, retdest + DUP4 + // stack: addr, loaded_addr, i, len, addr, retdest + EQ %jumpi(remove_touched_addresses_found) + // stack: i, len, addr, retdest + %increment + %jump(remove_touched_addresses_loop) +remove_touched_addresses_found: + %stack (i, len, addr, retdest) -> (len, 1, i, retdest) + SUB DUP1 %mstore_global_metadata(@GLOBAL_METADATA_TOUCHED_ADDRESSES_LEN) // Decrement the list length. + // stack: len-1, i, retdest + %mload_kernel(@SEGMENT_TOUCHED_ADDRESSES) // Load the last address in the list. + // stack: last_addr, i, retdest + SWAP1 + %mstore_kernel(@SEGMENT_TOUCHED_ADDRESSES) // Store the last address at the position of the removed address. + JUMP + + +global delete_all_touched_addresses: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_TOUCHED_ADDRESSES_LEN) + // stack: len, retdest + PUSH 0 +delete_all_touched_addresses_loop: + // stack: i, len, retdest + DUP2 DUP2 EQ %jumpi(delete_all_touched_addresses_done) + // stack: i, len, retdest + DUP1 %mload_kernel(@SEGMENT_TOUCHED_ADDRESSES) + // stack: loaded_addr, i, len, retdest + DUP1 %is_empty %jumpi(bingo) + // stack: loaded_addr, i, len, retdest + POP %increment %jump(delete_all_touched_addresses_loop) +bingo: + // stack: loaded_addr, i, len, retdest + %delete_account + %increment %jump(delete_all_touched_addresses_loop) +delete_all_touched_addresses_done: + // stack: i, len, retdest + %pop2 JUMP + +%macro delete_all_touched_addresses + %stack () -> (%%after) + %jump(delete_all_touched_addresses) +%%after: + // stack: (empty) +%endmacro \ No newline at end of file diff --git a/evm/src/cpu/kernel/asm/core/transfer.asm b/evm/src/cpu/kernel/asm/core/transfer.asm index 9ab71627..6b23dd61 100644 --- a/evm/src/cpu/kernel/asm/core/transfer.asm +++ b/evm/src/cpu/kernel/asm/core/transfer.asm @@ -29,6 +29,7 @@ global transfer_eth_failure: // TODO: Should it be copy-on-write (with make_account_copy) instead of mutating the trie? global deduct_eth: // stack: addr, amount, retdest + DUP1 %insert_touched_addresses %mpt_read_state_trie // stack: account_ptr, amount, retdest DUP1 ISZERO %jumpi(deduct_eth_no_such_account) // If the account pointer is null, return 1. @@ -65,6 +66,7 @@ global deduct_eth_insufficient_balance: // TODO: Should it be copy-on-write (with make_account_copy) instead of mutating the trie? global add_eth: // stack: addr, amount, retdest + DUP1 %insert_touched_addresses DUP1 %mpt_read_state_trie // stack: account_ptr, addr, amount, retdest DUP1 ISZERO %jumpi(add_eth_new_account) // If the account pointer is null, we need to create the account. diff --git a/evm/src/cpu/kernel/asm/journal/account_destroyed.asm b/evm/src/cpu/kernel/asm/journal/account_destroyed.asm new file mode 100644 index 00000000..691331b4 --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/account_destroyed.asm @@ -0,0 +1,18 @@ +// struct AccountDestroyed { address, target, prev_balance } + +%macro journal_add_account_destroyed + %journal_add_3(@JOURNAL_ENTRY_ACCOUNT_DESTROYED) +%endmacro + +global revert_account_destroyed: + // stack: entry_type, ptr, retdest + POP + %journal_load_3 + // stack: address, target, prev_balance, retdest + PUSH revert_account_destroyed_contd DUP2 + %jump(remove_selfdestruct_list) +revert_account_destroyed_contd: + // stack: address, target, prev_balance, retdest + SWAP1 %transfer_eth %jumpi(panic) + JUMP + diff --git a/evm/src/cpu/kernel/asm/journal/account_loaded.asm b/evm/src/cpu/kernel/asm/journal/account_loaded.asm new file mode 100644 index 00000000..1c2a328a --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/account_loaded.asm @@ -0,0 +1,12 @@ +// struct AccountLoaded { address } + +%macro journal_add_account_loaded + %journal_add_1(@JOURNAL_ENTRY_ACCOUNT_LOADED) +%endmacro + +global revert_account_loaded: + // stack: entry_type, ptr, retdest + POP + %journal_load_1 + // stack: address, retdest + %jump(remove_accessed_addresses) \ No newline at end of file diff --git a/evm/src/cpu/kernel/asm/journal/account_touched.asm b/evm/src/cpu/kernel/asm/journal/account_touched.asm new file mode 100644 index 00000000..d157464d --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/account_touched.asm @@ -0,0 +1,10 @@ +// struct AccountTouched { address } + +%macro journal_add_account_touched + %journal_add_1(@JOURNAL_ENTRY_ACCOUNT_TOUCHED) +%endmacro + +// Note: We don't need to remove touched addresses. In fact doing so leads to bugs because of the way we load accounts in the MPT. +global revert_account_touched: + // stack: entry_type, ptr, retdest + %pop2 JUMP diff --git a/evm/src/cpu/kernel/asm/journal/balance_transfer.asm b/evm/src/cpu/kernel/asm/journal/balance_transfer.asm new file mode 100644 index 00000000..a6173daa --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/balance_transfer.asm @@ -0,0 +1,16 @@ +// struct BalanceTransfer { from, to, balance } + +%macro journal_add_balance_transfer + %journal_add_3(@JOURNAL_ENTRY_BALANCE_TRANSFER) +%endmacro + +global revert_balance_transfer: + // stack: entry_type, ptr, retdest + POP + %journal_load_3 + // stack: from, to, balance, retdest + SWAP1 + // stack: to, from, balance, retdest + %transfer_eth + %jumpi(panic) // This should never happen. + JUMP diff --git a/evm/src/cpu/kernel/asm/journal/code_change.asm b/evm/src/cpu/kernel/asm/journal/code_change.asm new file mode 100644 index 00000000..725a2ab8 --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/code_change.asm @@ -0,0 +1,17 @@ +// struct CodeChange { address, prev_codehash } + +%macro journal_add_code_change + %journal_add_2(@JOURNAL_ENTRY_CODE_CHANGE) +%endmacro + +global revert_code_change: + // stack: ptr, retdest + %journal_load_2 + // stack: address, prev_codehash, retdest + %mpt_read_state_trie + // stack: account_ptr, prev_codehash, retdest + %add_const(3) + // stack: codehash_ptr, prev_codehash, retdest + %mstore_trie_data + // stack: retdest + JUMP diff --git a/evm/src/cpu/kernel/asm/journal/journal.asm b/evm/src/cpu/kernel/asm/journal/journal.asm new file mode 100644 index 00000000..938894a1 --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/journal.asm @@ -0,0 +1,192 @@ +%macro journal_size + %mload_global_metadata(@GLOBAL_METADATA_JOURNAL_LEN) +%endmacro + +%macro mstore_journal + // stack: virtual, value + %mstore_kernel(@SEGMENT_JOURNAL) + // stack: (empty) +%endmacro + +%macro mload_journal + // stack: virtual + %mload_kernel(@SEGMENT_JOURNAL) + // stack: value +%endmacro + +%macro append_journal + // stack: pointer + %journal_size + // stack: journal_size, pointer + SWAP1 DUP2 + // stack: journal_size, pointer, journal_size + %mstore_journal + // stack: journal_size + %increment + %mstore_global_metadata(@GLOBAL_METADATA_JOURNAL_LEN) +%endmacro + +%macro journal_data_size + %mload_global_metadata(@GLOBAL_METADATA_JOURNAL_DATA_LEN) +%endmacro + +%macro mstore_journal_data + // stack: virtual, value + %mstore_kernel(@SEGMENT_JOURNAL_DATA) + // stack: (empty) +%endmacro + +%macro mload_journal_data + // stack: virtual + %mload_kernel(@SEGMENT_JOURNAL_DATA) + // stack: value +%endmacro + +%macro append_journal_data + // stack: value + %journal_data_size + // stack: size, value + SWAP1 DUP2 + // stack: size, value, size + %mstore_journal_data + // stack: size + %increment + %mstore_global_metadata(@GLOBAL_METADATA_JOURNAL_DATA_LEN) +%endmacro + +%macro journal_add_1(type) + // stack: w + %journal_data_size + // stack: ptr, w + PUSH $type %append_journal_data + // stack: ptr, w + SWAP1 + // stack: w, ptr + %append_journal_data + // stack: ptr + %append_journal +%endmacro + +%macro journal_add_2(type) + // stack: w, x + %journal_data_size + // stack: ptr, w, x + PUSH $type %append_journal_data + // stack: ptr, w, x + SWAP1 %append_journal_data + // stack: ptr, x + SWAP1 %append_journal_data + // stack: ptr + %append_journal +%endmacro + +%macro journal_add_3(type) + // stack: w, x, y + %journal_data_size + // stack: ptr, w, x, y + PUSH $type %append_journal_data + // stack: ptr, w, x, y + SWAP1 %append_journal_data + // stack: ptr, x, y + SWAP1 %append_journal_data + // stack: ptr, y + SWAP1 %append_journal_data + // stack: ptr + %append_journal +%endmacro + +%macro journal_add_4(type) + // stack: w, x, y, z + %journal_data_size + // stack: ptr, w, x, y, z + PUSH $type %append_journal_data + // stack: ptr, w, x, y, z + SWAP1 %append_journal_data + // stack: ptr, x, y, z + SWAP1 %append_journal_data + // stack: ptr, y, z + SWAP1 %append_journal_data + // stack: ptr, z + SWAP1 %append_journal_data + // stack: ptr + %append_journal +%endmacro + +%macro journal_load_1 + // ptr + %add_const(1) + %mload_journal_data + // w +%endmacro + +%macro journal_load_2 + // ptr + DUP1 + %add_const(2) + %mload_journal_data + // x, ptr + SWAP1 + %add_const(1) + %mload_journal_data + // w, x +%endmacro + +%macro journal_load_3 + // ptr + DUP1 + %add_const(3) + %mload_journal_data + // y, ptr + SWAP1 + DUP1 + // ptr, ptr, y + %add_const(2) + %mload_journal_data + // x, ptr, y + SWAP1 + %add_const(1) + %mload_journal_data + // w, x, y +%endmacro + +%macro journal_load_4 + // ptr + DUP1 + %add_const(4) + %mload_journal_data + // z, ptr + SWAP1 + DUP1 + // ptr, ptr, z + %add_const(3) + %mload_journal_data + // y, ptr, z + SWAP1 + DUP1 + // ptr, ptr, y, z + %add_const(2) + %mload_journal_data + // x, ptr, y, z + SWAP1 + %add_const(1) + %mload_journal_data + // w, x, y, z +%endmacro + +%macro current_checkpoint + %mload_global_metadata(@GLOBAL_METADATA_CURRENT_CHECKPOINT) +%endmacro + + +%macro checkpoint + // stack: (empty) + %current_checkpoint + // stack: current_checkpoint + %journal_size + // stack: journal_size, current_checkpoint + DUP2 %mstore_kernel(@SEGMENT_JOURNAL_CHECKPOINTS) + // stack: current_checkpoint + DUP1 %increment + %mstore_global_metadata(@GLOBAL_METADATA_CURRENT_CHECKPOINT) + // stack: current_checkpoint +%endmacro diff --git a/evm/src/cpu/kernel/asm/journal/nonce_change.asm b/evm/src/cpu/kernel/asm/journal/nonce_change.asm new file mode 100644 index 00000000..3ab8f136 --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/nonce_change.asm @@ -0,0 +1,17 @@ +// struct NonceChange { address, prev_nonce } + +%macro journal_add_nonce_change + %journal_add_2(@JOURNAL_ENTRY_NONCE_CHANGE) +%endmacro + +global revert_nonce_change: + // stack: entry_type, ptr, retdest + POP + %journal_load_2 + // stack: address, prev_nonce, retdest + %mpt_read_state_trie + // stack: nonce_ptr, prev_nonce retdest + %mstore_trie_data + // stack: retdest + JUMP + diff --git a/evm/src/cpu/kernel/asm/journal/revert.asm b/evm/src/cpu/kernel/asm/journal/revert.asm new file mode 100644 index 00000000..f1f50f72 --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/revert.asm @@ -0,0 +1,84 @@ +%macro revert + // stack: journal_size + %decrement + %stack (journal_size_m_1) -> (journal_size_m_1, %%after, journal_size_m_1) + %mload_journal + // stack: ptr, %%after, journal_size-1 + DUP1 %mload_journal_data + // stack: entry_type, ptr, %%after, journal_size-1 + DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_LOADED) %jumpi(revert_account_loaded) + DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_DESTROYED) %jumpi(revert_account_destroyed) + DUP1 %eq_const(@JOURNAL_ENTRY_ACCOUNT_TOUCHED) %jumpi(revert_account_touched) + DUP1 %eq_const(@JOURNAL_ENTRY_BALANCE_TRANSFER) %jumpi(revert_balance_transfer) + DUP1 %eq_const(@JOURNAL_ENTRY_NONCE_CHANGE) %jumpi(revert_nonce_change) + DUP1 %eq_const(@JOURNAL_ENTRY_STORAGE_CHANGE) %jumpi(revert_storage_change) + DUP1 %eq_const(@JOURNAL_ENTRY_STORAGE_LOADED) %jumpi(revert_storage_loaded) + %eq_const(@JOURNAL_ENTRY_CODE_CHANGE) %jumpi(revert_code_change) + PANIC // This should never happen. +%%after: + // stack: journal_size-1 +%endmacro + +global revert_batch: + // stack: target_size, retdest + %journal_size + // stack: journal_size, target_size, retdest + DUP2 DUP2 LT %jumpi(panic) // Sanity check to avoid infinite loop. +while_loop: + // stack: journal_size, target_size, retdest + DUP2 DUP2 EQ %jumpi(revert_batch_done) + // stack: journal_size, target_size, retdest + %revert + // stack: journal_size-1, target_size, retdest + %jump(while_loop) + +revert_batch_done: + // stack: journal_size, target_size, retdest + %mstore_global_metadata(@GLOBAL_METADATA_JOURNAL_LEN) + POP JUMP + +revert_one_checkpoint: + // stack: current_checkpoint, retdest + DUP1 ISZERO %jumpi(first_checkpoint) + // stack: current_checkpoint, retdest + %decrement + // stack: current_checkpoint-1, retdest + DUP1 %mload_kernel(@SEGMENT_JOURNAL_CHECKPOINTS) + // stack: target_size, current_checkpoints-1, retdest + %jump(do_revert) +first_checkpoint: + // stack: current_checkpoint, retdest + %decrement + // stack: current_checkpoint-1, retdest + PUSH 0 + // stack: target_size, current_checkpoints-1, retdest +do_revert: + %stack (target_size, current_checkpoints_m_1, retdest) -> (target_size, after_revert, current_checkpoints_m_1, retdest) + %jump(revert_batch) +after_revert: + // stack: current_checkpoint-1, retdest + SWAP1 JUMP + + +global revert_checkpoint: + // stack: target_checkpoint, retdest + %current_checkpoint + // stack: current_checkpoint, target_checkpoint, retdest + DUP2 DUP2 LT %jumpi(panic) // Sanity check that current_cp >= target_cp. This should never happen. +while: + // stack: current_checkpoint, target_checkpoint, retdest + DUP2 DUP2 EQ %jumpi(revert_checkpoint_done) + %stack (current_checkpoint) -> (current_checkpoint, while) + %jump(revert_one_checkpoint) +revert_checkpoint_done: + // stack: current_checkpoint, target_checkpoint, retdest + POP + %mstore_global_metadata(@GLOBAL_METADATA_CURRENT_CHECKPOINT) + JUMP + +%macro revert_checkpoint + %stack (target_checkpoint) -> (target_checkpoint, %%after) + %jump(revert_checkpoint) +%%after: + // stack: (empty) +%endmacro diff --git a/evm/src/cpu/kernel/asm/journal/storage_change.asm b/evm/src/cpu/kernel/asm/journal/storage_change.asm new file mode 100644 index 00000000..6f34e7f3 --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/storage_change.asm @@ -0,0 +1,61 @@ +// struct StorageChange { address, slot, prev_value } + +%macro journal_add_storage_change + %journal_add_3(@JOURNAL_ENTRY_STORAGE_CHANGE) +%endmacro + +global revert_storage_change: + // stack: entry_type, ptr, retdest + POP + %journal_load_3 + // 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 + %stack (storage_root_ptr, num_nibbles, storage_key, address, prev_value, retdest) -> + (storage_root_ptr, num_nibbles, storage_key, prev_value, new_storage_root, address, retdest) + %jump(mpt_insert) + +delete: + // stack: address, slot, prev_value, retdest + SWAP2 POP + %stack (slot, address, retdest) -> (slot, new_storage_root, 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: old_account_ptr, new_storage_root_ptr, address, retdest + %make_account_copy + // stack: new_account_ptr, new_storage_root_ptr, address, retdest + + // Update the copied account with our new storage root pointer. + %stack (new_account_ptr, new_storage_root_ptr) -> (new_account_ptr, new_storage_root_ptr, new_account_ptr) + %add_const(2) + // stack: new_account_storage_root_ptr_ptr, new_storage_root_ptr, new_account_ptr, address, retdest + %mstore_trie_data + // stack: new_account_ptr, address, retdest + + DUP2 %addr_to_state_key + %stack (state_key, new_account_ptr, address, retdest) -> (state_key, new_account_ptr, retdest) + %jump(mpt_insert_state_trie) diff --git a/evm/src/cpu/kernel/asm/journal/storage_loaded.asm b/evm/src/cpu/kernel/asm/journal/storage_loaded.asm new file mode 100644 index 00000000..9d1f3745 --- /dev/null +++ b/evm/src/cpu/kernel/asm/journal/storage_loaded.asm @@ -0,0 +1,12 @@ +// struct StorageLoaded { address, slot } + +%macro journal_add_storage_loaded + %journal_add_2(@JOURNAL_ENTRY_STORAGE_LOADED) +%endmacro + +global revert_storage_loaded: + // stack: entry_type, ptr, retdest + POP + %journal_load_2 + // stack: address, slot, retdest + %jump(remove_accessed_storage_keys) diff --git a/evm/src/cpu/kernel/asm/mpt/delete/delete.asm b/evm/src/cpu/kernel/asm/mpt/delete/delete.asm index 5df1e283..913ba1fc 100644 --- a/evm/src/cpu/kernel/asm/mpt/delete/delete.asm +++ b/evm/src/cpu/kernel/asm/mpt/delete/delete.asm @@ -22,3 +22,24 @@ mpt_delete_leaf: %pop4 PUSH 0 // empty node ptr 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) + JUMP + +%macro delete_account + %stack (address) -> (address, %%after) + %jump(delete_account) +%%after: + // stack: (empty) +%endmacro \ No newline at end of file diff --git a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm index 12066894..31b1c462 100644 --- a/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm +++ b/evm/src/cpu/kernel/asm/mpt/storage/storage_write.asm @@ -85,9 +85,13 @@ sstore_no_refund: sstore_after_refund: // stack: kexit_info, current_value, slot, value // Check if `value` is equal to `current_value`, and if so exit the kernel early. - %stack (kexit_info, current_value, slot, value) -> (value, current_value, slot, value, kexit_info) + %stack (kexit_info, current_value, slot, value) -> (value, current_value, current_value, slot, value, kexit_info) EQ %jumpi(sstore_noop) + // stack: current_value, slot, value, kexit_info + DUP2 %address %journal_add_storage_change + // stack: slot, value, kexit_info + // If the value is zero, delete the slot from the storage trie. // stack: slot, value, kexit_info DUP2 ISZERO %jumpi(sstore_delete) @@ -134,8 +138,8 @@ after_state_insert: EXIT_KERNEL sstore_noop: - // stack: slot, value, kexit_info - %pop2 + // stack: current_value, slot, value, kexit_info + %pop3 EXIT_KERNEL // Delete the slot from the storage trie. diff --git a/evm/src/cpu/kernel/constants/context_metadata.rs b/evm/src/cpu/kernel/constants/context_metadata.rs index 32af6e35..ef4023c5 100644 --- a/evm/src/cpu/kernel/constants/context_metadata.rs +++ b/evm/src/cpu/kernel/constants/context_metadata.rs @@ -28,10 +28,11 @@ pub(crate) enum ContextMetadata { StackSize = 11, /// The gas limit for this call (not the entire transaction). GasLimit = 12, + Checkpoint = 13, } impl ContextMetadata { - pub(crate) const COUNT: usize = 13; + pub(crate) const COUNT: usize = 14; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -48,6 +49,7 @@ impl ContextMetadata { Self::MemWords, Self::StackSize, Self::GasLimit, + Self::Checkpoint, ] } @@ -67,6 +69,7 @@ impl ContextMetadata { ContextMetadata::MemWords => "CTX_METADATA_MEM_WORDS", ContextMetadata::StackSize => "CTX_METADATA_STACK_SIZE", ContextMetadata::GasLimit => "CTX_METADATA_GAS_LIMIT", + ContextMetadata::Checkpoint => "CTX_METADATA_CHECKPOINT", } } } diff --git a/evm/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs index 4c6873c8..4822c3d3 100644 --- a/evm/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -51,10 +51,18 @@ pub(crate) enum GlobalMetadata { AccessedStorageKeysLen = 24, /// Length of the self-destruct list. SelfDestructListLen = 25, + + /// Length of the journal. + JournalLen = 26, + /// Length of the `JournalData` segment. + JournalDataLen = 27, + /// Current checkpoint. + CurrentCheckpoint = 28, + TouchedAddressesLen = 29, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 25; + pub(crate) const COUNT: usize = 29; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -83,6 +91,10 @@ impl GlobalMetadata { Self::AccessedAddressesLen, Self::AccessedStorageKeysLen, Self::SelfDestructListLen, + Self::JournalLen, + Self::JournalDataLen, + Self::CurrentCheckpoint, + Self::TouchedAddressesLen, ] } @@ -114,6 +126,10 @@ impl GlobalMetadata { Self::AccessedAddressesLen => "GLOBAL_METADATA_ACCESSED_ADDRESSES_LEN", Self::AccessedStorageKeysLen => "GLOBAL_METADATA_ACCESSED_STORAGE_KEYS_LEN", Self::SelfDestructListLen => "GLOBAL_METADATA_SELFDESTRUCT_LIST_LEN", + Self::JournalLen => "GLOBAL_METADATA_JOURNAL_LEN", + Self::JournalDataLen => "GLOBAL_METADATA_JOURNAL_DATA_LEN", + Self::CurrentCheckpoint => "GLOBAL_METADATA_CURRENT_CHECKPOINT", + Self::TouchedAddressesLen => "GLOBAL_METADATA_TOUCHED_ADDRESSES_LEN", } } } diff --git a/evm/src/cpu/kernel/constants/journal_entry.rs b/evm/src/cpu/kernel/constants/journal_entry.rs new file mode 100644 index 00000000..39d06fe9 --- /dev/null +++ b/evm/src/cpu/kernel/constants/journal_entry.rs @@ -0,0 +1,43 @@ +#[allow(dead_code)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] +pub(crate) enum JournalEntry { + AccountLoaded = 0, + AccountDestroyed = 1, + AccountTouched = 2, + BalanceTransfer = 3, + NonceChange = 4, + StorageChange = 5, + StorageLoaded = 6, + CodeChange = 7, +} + +impl JournalEntry { + pub(crate) const COUNT: usize = 8; + + pub(crate) fn all() -> [Self; Self::COUNT] { + [ + Self::AccountLoaded, + Self::AccountDestroyed, + Self::AccountTouched, + Self::BalanceTransfer, + Self::NonceChange, + Self::StorageChange, + Self::StorageLoaded, + Self::CodeChange, + ] + } + + /// The variable name that gets passed into kernel assembly code. + pub(crate) fn var_name(&self) -> &'static str { + match self { + Self::AccountLoaded => "JOURNAL_ENTRY_ACCOUNT_LOADED", + Self::AccountDestroyed => "JOURNAL_ENTRY_ACCOUNT_DESTROYED", + Self::AccountTouched => "JOURNAL_ENTRY_ACCOUNT_TOUCHED", + Self::BalanceTransfer => "JOURNAL_ENTRY_BALANCE_TRANSFER", + Self::NonceChange => "JOURNAL_ENTRY_NONCE_CHANGE", + Self::StorageChange => "JOURNAL_ENTRY_STORAGE_CHANGE", + Self::StorageLoaded => "JOURNAL_ENTRY_STORAGE_LOADED", + Self::CodeChange => "JOURNAL_ENTRY_CODE_CHANGE", + } + } +} diff --git a/evm/src/cpu/kernel/constants/mod.rs b/evm/src/cpu/kernel/constants/mod.rs index e8489650..2c1adbb5 100644 --- a/evm/src/cpu/kernel/constants/mod.rs +++ b/evm/src/cpu/kernel/constants/mod.rs @@ -6,12 +6,14 @@ use hex_literal::hex; use crate::cpu::decode::invalid_opcodes_user; use crate::cpu::kernel::constants::context_metadata::ContextMetadata; use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; +use crate::cpu::kernel::constants::journal_entry::JournalEntry; use crate::cpu::kernel::constants::trie_type::PartialTrieType; use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField; use crate::memory::segments::Segment; pub(crate) mod context_metadata; pub(crate) mod global_metadata; +pub(crate) mod journal_entry; pub(crate) mod trie_type; pub(crate) mod txn_fields; @@ -67,6 +69,9 @@ pub fn evm_constants() -> HashMap { for trie_type in PartialTrieType::all() { c.insert(trie_type.var_name().into(), (trie_type as u32).into()); } + for entry in JournalEntry::all() { + c.insert(entry.var_name().into(), (entry as u32).into()); + } c.insert( "INVALID_OPCODES_USER".into(), U256::from_little_endian(&invalid_opcodes_user()), diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index a0875de3..04c6cf35 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -50,10 +50,16 @@ pub enum Segment { AccessedStorageKeys = 24, /// List of addresses that have called SELFDESTRUCT in the current transaction. SelfDestructList = 25, + /// Journal of state changes. List of pointers to `JournalData`. Length in `GlobalMetadata`. + Journal = 26, + JournalData = 27, + JournalCheckpoints = 28, + /// List of addresses that have been touched in the current transaction. + TouchedAddresses = 29, } impl Segment { - pub(crate) const COUNT: usize = 26; + pub(crate) const COUNT: usize = 30; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -83,6 +89,10 @@ impl Segment { Self::AccessedAddresses, Self::AccessedStorageKeys, Self::SelfDestructList, + Self::Journal, + Self::JournalData, + Self::JournalCheckpoints, + Self::TouchedAddresses, ] } @@ -115,6 +125,10 @@ impl Segment { Segment::AccessedAddresses => "SEGMENT_ACCESSED_ADDRESSES", Segment::AccessedStorageKeys => "SEGMENT_ACCESSED_STORAGE_KEYS", Segment::SelfDestructList => "SEGMENT_SELFDESTRUCT_LIST", + Segment::Journal => "SEGMENT_JOURNAL", + Segment::JournalData => "SEGMENT_JOURNAL_DATA", + Segment::JournalCheckpoints => "SEGMENT_JOURNAL_CHECKPOINTS", + Segment::TouchedAddresses => "SEGMENT_TOUCHED_ADDRESSES", } } @@ -147,6 +161,10 @@ impl Segment { Segment::AccessedAddresses => 256, Segment::AccessedStorageKeys => 256, Segment::SelfDestructList => 256, + Segment::Journal => 256, + Segment::JournalData => 256, + Segment::JournalCheckpoints => 256, + Segment::TouchedAddresses => 256, } } } diff --git a/evm/src/prover.rs b/evm/src/prover.rs index 414b8d50..c51f4995 100644 --- a/evm/src/prover.rs +++ b/evm/src/prover.rs @@ -90,6 +90,33 @@ where Ok((proof, outputs)) } +/// Generate traces, then create all STARK proofs. Returns information about the post-state, +/// intended for debugging, in addition to the proof. +pub fn dont_prove_with_outputs( + all_stark: &AllStark, + config: &StarkConfig, + inputs: GenerationInputs, + timing: &mut TimingTree, +) -> Result<(PublicValues, GenerationOutputs)> +where + F: RichField + Extendable, + C: GenericConfig, + [(); ArithmeticStark::::COLUMNS]:, + [(); CpuStark::::COLUMNS]:, + [(); KeccakStark::::COLUMNS]:, + [(); KeccakSpongeStark::::COLUMNS]:, + [(); LogicStark::::COLUMNS]:, + [(); MemoryStark::::COLUMNS]:, +{ + timed!(timing, "build kernel", Lazy::force(&KERNEL)); + let (_traces, public_values, outputs) = timed!( + timing, + "generate all traces", + generate_traces(all_stark, inputs, config, timing)? + ); + Ok((public_values, outputs)) +} + /// Compute all STARK proofs. pub(crate) fn prove_with_traces( all_stark: &AllStark,