From c0b4f155f4e8bef3d86a79d303e865401c5706d5 Mon Sep 17 00:00:00 2001 From: Robin Salen Date: Thu, 4 May 2023 09:57:02 +0200 Subject: [PATCH] Implement receipts and logs Co-authored-by: Hamy Ratoanina Co-authored-by: Linda Guiga --- evm/Cargo.toml | 1 + evm/src/cpu/kernel/aggregator.rs | 2 + evm/src/cpu/kernel/asm/bloom_filter.asm | 176 ++++++ .../cpu/kernel/asm/core/create_receipt.asm | 236 +++++++ evm/src/cpu/kernel/asm/core/log.asm | 201 +++++- evm/src/cpu/kernel/asm/core/process_txn.asm | 113 +++- evm/src/cpu/kernel/asm/main.asm | 21 +- .../asm/mpt/hash/hash_trie_specific.asm | 135 +++- .../asm/mpt/insert/insert_trie_specific.asm | 50 ++ .../asm/mpt/load/load_trie_specific.asm | 84 ++- .../cpu/kernel/asm/rlp/encode_rlp_string.asm | 6 + .../cpu/kernel/constants/global_metadata.rs | 52 +- evm/src/cpu/kernel/interpreter.rs | 7 +- evm/src/cpu/kernel/tests/log.rs | 198 ++++++ evm/src/cpu/kernel/tests/mod.rs | 2 + evm/src/cpu/kernel/tests/receipt.rs | 595 ++++++++++++++++++ evm/src/generation/mpt.rs | 70 ++- evm/src/memory/segments.rs | 31 +- evm/src/proof.rs | 15 +- evm/src/recursive_verifier.rs | 7 + evm/src/witness/memory.rs | 10 +- evm/tests/add11_yml.rs | 1 + evm/tests/log_opcode.rs | 507 +++++++++++++++ evm/tests/simple_transfer.rs | 1 + 24 files changed, 2441 insertions(+), 80 deletions(-) create mode 100644 evm/src/cpu/kernel/asm/bloom_filter.asm create mode 100644 evm/src/cpu/kernel/asm/core/create_receipt.asm create mode 100644 evm/src/cpu/kernel/tests/log.rs create mode 100644 evm/src/cpu/kernel/tests/receipt.rs create mode 100644 evm/tests/log_opcode.rs diff --git a/evm/Cargo.toml b/evm/Cargo.toml index e1864e90..dc579887 100644 --- a/evm/Cargo.toml +++ b/evm/Cargo.toml @@ -11,6 +11,7 @@ edition = "2021" [dependencies] anyhow = "1.0.40" +bytes = "1.4.0" env_logger = "0.10.0" eth_trie_utils = "0.6.0" ethereum-types = "0.14.0" diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index f3e50039..160702df 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -29,6 +29,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/core/create_addresses.asm"), include_str!("asm/core/create_contract_account.asm"), include_str!("asm/core/exception.asm"), + include_str!("asm/core/create_receipt.asm"), include_str!("asm/core/gas.asm"), include_str!("asm/core/intrinsic_gas.asm"), include_str!("asm/core/jumpdest_analysis.asm"), @@ -158,6 +159,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/util/math.asm"), include_str!("asm/account_code.asm"), include_str!("asm/balance.asm"), + include_str!("asm/bloom_filter.asm"), ]; let parsed_files = files.iter().map(|f| parse(f)).collect_vec(); diff --git a/evm/src/cpu/kernel/asm/bloom_filter.asm b/evm/src/cpu/kernel/asm/bloom_filter.asm new file mode 100644 index 00000000..d30b3c20 --- /dev/null +++ b/evm/src/cpu/kernel/asm/bloom_filter.asm @@ -0,0 +1,176 @@ +/// Implementation of Bloom filters for logs. + +// Adds a Bloom entry to the transaction Bloom filter and the block Bloom filter. +// +// This is calculated by taking the least significant 11 bits from +// the first 3 16-bit bytes of the keccak_256 hash of bloom_entry. +add_to_bloom: + // stack: is_topic, bloom_entry, retdest + %compute_entry_hash + // stack: hash, retdest + DUP1 + // stack: hash, hash, retdest + %shr_const(240) + // stack: hahs_shft_240, hash, retdest + %bloom_byte_indices + // stack: byte_index, byte_bit_index, hash, retdest + %bloom_write_bit + // stack: hash, retdest + + // We shift the hash by 16 bits and repeat. + DUP1 %shr_const(224) + // stack: hash_shft_224, hash, retdest + %bloom_byte_indices + // stack: byte_index, byte_bit_index, hash, retdest + %bloom_write_bit + // stack: hash, retdest + + // We shift again the hash by 16 bits and repeat. + %shr_const(208) + // stack: hash_shft_208, retdest + %bloom_byte_indices + // stack: byte_index, byte_bit_index, retdest + %bloom_write_bit + // stack: retdest + JUMP + +// The LOGS segment is [log0_ptr, log1_ptr...]. logs_len is a global metadata for the number of logs. +// A log in the LOGS_DATA segment is [log_payload_len, address, num_topics, [topics], data_len, [data]]. +global logs_bloom: + // stack: retdest + %mload_global_metadata(@GLOBAL_METADATA_LOGS_LEN) + // stack: logs_len, retdest + PUSH 0 + +logs_bloom_loop: + // stack: i, logs_len, retdest + DUP2 DUP2 EQ + // stack: i == logs_len, i, logs_len, retdest + %jumpi(logs_bloom_end) + // stack: i, logs_len, retdest + DUP1 + %mload_kernel(@SEGMENT_LOGS) + // stack: log_payload_len_ptr, i, logs_len, retdest + + // Add address to bloom filter. + %increment + // stack: addr_ptr, i, logs_len, retdest + DUP1 + %mload_kernel(@SEGMENT_LOGS_DATA) + // stack: addr, addr_ptr, i, logs_len, retdest + PUSH 0 + // stack: is_topic, addr, addr_ptr, i, logs_len, retdest + %add_to_bloom + // stack: addr_ptr, i, logs_len, retdest + %increment + // stack: num_topics_ptr, i, logs_len, retdest + DUP1 + %mload_kernel(@SEGMENT_LOGS_DATA) + // stack: num_topics, num_topics_ptr, i, logs_len, retdest + SWAP1 %increment + // stack: topics_ptr, num_topics, i, logs_len, retdest + PUSH 0 + +logs_bloom_topic_loop: + // stack: j, topics_ptr, num_topics, i, logs_len, retdest + DUP3 DUP2 EQ + // stack: j == num_topics, j, topics_ptr, num_topics, i, logs_len, retdest + %jumpi(logs_bloom_topic_end) + DUP2 DUP2 ADD + // stack: curr_topic_ptr, j, topics_ptr, num_topics, i, logs_len, retdest + %mload_kernel(@SEGMENT_LOGS_DATA) + // stack: topic, j, topics_ptr, num_topics, i, logs_len, retdest + PUSH 1 + // stack: is_topic, topic, j, topics_ptr, num_topics, i, logs_len, retdest + %add_to_bloom + // stack: j, topics_ptr, num_topics, i, logs_len, retdest + %increment + %jump(logs_bloom_topic_loop) + +logs_bloom_topic_end: + // stack: num_topics, topics_ptr, num_topics, i, logs_len, retdest + %pop3 + %increment + %jump(logs_bloom_loop) + +logs_bloom_end: + // stack: logs_len, logs_len, retdest + %pop2 + JUMP + +%macro compute_entry_hash + // stack: is_topic, bloom_entry + ISZERO + %jumpi(%%compute_entry_hash_address) + // stack: bloom_entry + %keccak256_word(32) + // stack: topic_hash + %jump(%%after) + +%%compute_entry_hash_address: + // stack: bloom_entry + %keccak256_word(20) + // stack: address_hash + +%%after: +%endmacro + +%macro add_to_bloom + %stack (is_topic, bloom_entry) -> (is_topic, bloom_entry, %%after) + %jump(add_to_bloom) + +%%after: +%endmacro + +// Computes the byte index and bit index within to update the Bloom filter with. +// The hash value must be properly shifted prior calling this macro. +%macro bloom_byte_indices + // stack: hash + %and_const(0x07FF) + PUSH 0x07FF + SUB + // stack: bit_index + DUP1 + %and_const(0x7) + SWAP1 + %shr_const(0x3) + // stack: byte_index, byte_bit_index +%endmacro + + +// Updates the corresponding bloom filter byte with provided bit. +// Also updates the block bloom filter. +%macro bloom_write_bit + // stack: byte_index, byte_bit_index + DUP2 + // stack: byte_bit_index, byte_index, byte_bit_index + PUSH 7 SUB + PUSH 1 SWAP1 SHL + // Updates the current txn bloom filter. + // stack: one_shifted_by_index, byte_index, byte_bit_index + DUP2 DUP1 + // stack: byte_index, byte_index, one_shifted_by_index, byte_index, byte_bit_index + // load bloom_byte from current txn bloom filter + %mload_kernel(@SEGMENT_TXN_BLOOM) + %stack (old_bloom_byte, byte_index, one_shifted_by_index) -> (old_bloom_byte, one_shifted_by_index, byte_index, one_shifted_by_index) + OR + // stack: new_bloom_byte, byte_index, one_shifted_by_index, byte_index, byte_bit_index + SWAP1 + %mstore_kernel(@SEGMENT_TXN_BLOOM) + // stack: one_shifted_by_index, byte_index, byte_bit_index + + // Updates the block bloom filter. + SWAP2 POP DUP1 + %mload_kernel(@SEGMENT_BLOCK_BLOOM) + // stack: old_bloom_byte, byte_index, one_shifted_by_index + DUP3 OR + // stack: new_bloom_byte, byte_index, one_shifted_by_index + SWAP1 + %mstore_kernel(@SEGMENT_BLOCK_BLOOM) + // stack: one_shifted_by_index + POP + // stack: empty +%endmacro + + + diff --git a/evm/src/cpu/kernel/asm/core/create_receipt.asm b/evm/src/cpu/kernel/asm/core/create_receipt.asm new file mode 100644 index 00000000..4dfcdc5e --- /dev/null +++ b/evm/src/cpu/kernel/asm/core/create_receipt.asm @@ -0,0 +1,236 @@ +// Pre-stack: status, leftover_gas, prev_cum_gas, txn_nb, retdest +// Post stack: new_cum_gas, txn_nb +// A receipt is stored in MPT_TRIE_DATA as: +// [payload_len, status, cum_gas_used, bloom, logs_payload_len, num_logs, [logs]] +// +// In this function, we: +// - compute cum_gas, +// - check if the transaction failed and set number of logs to 0 if it is the case, +// - compute the bloom filter, +// - write the receipt in MPT_TRIE_DATA , +// - insert a new node in receipt_trie, +// - set the bloom filter back to 0 +global process_receipt: + // stack: status, leftover_gas, prev_cum_gas, txn_nb, retdest + DUP2 DUP4 + // stack: prev_cum_gas, leftover_gas, status, leftover_gas, prev_cum_gas, txn_nb, retdest + %compute_cumulative_gas + // stack: new_cum_gas, status, leftover_gas, prev_cum_gas, txn_nb, retdest + SWAP3 POP + // stack: status, leftover_gas, new_cum_gas, txn_nb, retdest + SWAP1 POP + // stack: status, new_cum_gas, txn_nb, retdest + // Now, we need to check whether the transaction has failed. + DUP1 ISZERO %jumpi(failed_receipt) + +process_receipt_after_status: + // stack: status, new_cum_gas, txn_nb, retdest + PUSH process_receipt_after_bloom + %jump(logs_bloom) + +process_receipt_after_bloom: + // stack: status, new_cum_gas, txn_nb, retdest + DUP2 DUP4 + // stack: txn_nb, new_cum_gas, status, new_cum_gas, txn_nb, retdest + SWAP2 + // stack: status, new_cum_gas, txn_nb, new_cum_gas, txn_nb, retdest + + // Compute the total RLP payload length of the receipt. + PUSH 1 // status is always 1 byte. + // stack: payload_len, status, new_cum_gas, txn_nb, new_cum_gas, txn_nb, retdest + DUP3 + %rlp_scalar_len // cum_gas is a simple scalar. + ADD + // stack: payload_len, status, new_cum_gas, txn_nb, new_cum_gas, txn_nb, retdest + // Next is the bloom_filter, which is a 256-byte array. Its RLP encoding is + // 1 + 2 + 256 bytes. + %add_const(259) + // stack: payload_len, status, new_cum_gas, txn_nb, new_cum_gas, txn_nb, retdest + // Last is the logs. + %mload_global_metadata(@GLOBAL_METADATA_LOGS_PAYLOAD_LEN) + %rlp_list_len + ADD + // stack: payload_len, status, new_cum_gas, txn_nb, new_cum_gas, txn_nb, retdest + // Now we can write the receipt in MPT_TRIE_DATA. + %get_trie_data_size + // stack: receipt_ptr, payload_len, status, new_cum_gas, txn_nb, new_cum_gas, txn_nb, retdest + // Write payload_len. + SWAP1 + %append_to_trie_data + // stack: receipt_ptr, status, new_cum_gas, txn_nb, new_cum_gas, txn_nb, retdest + // Write status. + SWAP1 + %append_to_trie_data + // stack: receipt_ptr, new_cum_gas, txn_nb, new_cum_gas, txn_nb, retdest + // Write cum_gas_used. + SWAP1 + %append_to_trie_data + // stack: receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + // Write Bloom filter. + PUSH 256 // Bloom length. + PUSH 0 PUSH @SEGMENT_TXN_BLOOM PUSH 0 // Bloom memory address. + %get_trie_data_size PUSH @SEGMENT_TRIE_DATA PUSH 0 // MPT dest address. + // stack: DST, SRC, 256, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %memcpy + // stack: receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + // Update trie data size. + %get_trie_data_size + %add_const(256) + %set_trie_data_size + + // Now we write logs. + // stack: receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + // We start with the logs payload length. + %mload_global_metadata(@GLOBAL_METADATA_LOGS_PAYLOAD_LEN) + %append_to_trie_data + // stack: receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %mload_global_metadata(@GLOBAL_METADATA_LOGS_LEN) + // Then the number of logs. + // stack: num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP1 %append_to_trie_data + PUSH 0 + +// Each log is written in MPT_TRIE_DATA as: +// [payload_len, address, num_topics, [topics], data_len, [data]]. +process_receipt_logs_loop: + // stack: i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP2 DUP2 + EQ + // stack: i == num_logs, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %jumpi(process_receipt_after_write) + // stack: i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP1 + %mload_kernel(@SEGMENT_LOGS) + // stack: log_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + // Write payload_len. + DUP1 + %mload_kernel(@SEGMENT_LOGS_DATA) + %append_to_trie_data + // stack: log_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + // Write address. + %increment + // stack: addr_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP1 + %mload_kernel(@SEGMENT_LOGS_DATA) + %append_to_trie_data + // stack: addr_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + //Write num_topics. + %increment + // stack: num_topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP1 + %mload_kernel(@SEGMENT_LOGS_DATA) + // stack: num_topics, num_topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP1 + %append_to_trie_data + // stack: num_topics, num_topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + SWAP1 %increment SWAP1 + // stack: num_topics, topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + PUSH 0 + +process_receipt_topics_loop: + // stack: j, num_topics, topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP2 DUP2 + EQ + // stack: j == num_topics, j, num_topics, topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %jumpi(process_receipt_topics_end) + // stack: j, num_topics, topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + // Write j-th topic. + DUP3 DUP2 + ADD + // stack: cur_topic_ptr, j, num_topics, topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %mload_kernel(@SEGMENT_LOGS_DATA) + %append_to_trie_data + // stack: j, num_topics, topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %increment + %jump(process_receipt_topics_loop) + +process_receipt_topics_end: + // stack: num_topics, num_topics, topics_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + POP + ADD + // stack: data_len_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + // Write data_len + DUP1 + %mload_kernel(@SEGMENT_LOGS_DATA) + // stack: data_len, data_len_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP1 + %append_to_trie_data + // stack: data_len, data_len_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + SWAP1 %increment SWAP1 + // stack: data_len, data_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + PUSH 0 + +process_receipt_data_loop: + // stack: j, data_len, data_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + DUP2 DUP2 + EQ + // stack: j == data_len, j, data_len, data_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %jumpi(process_receipt_data_end) + // stack: j, data_len, data_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + // Write j-th data byte. + DUP3 DUP2 + ADD + // stack: cur_data_ptr, j, data_len, data_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %mload_kernel(@SEGMENT_LOGS_DATA) + %append_to_trie_data + // stack: j, data_len, data_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %increment + %jump(process_receipt_data_loop) + +process_receipt_data_end: + // stack: data_len, data_len, data_ptr, i, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %pop3 + %increment + %jump(process_receipt_logs_loop) + +process_receipt_after_write: + // stack: num_logs, num_logs, receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + %pop2 + // stack: receipt_ptr, txn_nb, new_cum_gas, txn_nb, retdest + SWAP1 + // stack: txn_nb, receipt_ptr, new_cum_gas, txn_nb, retdest + %mpt_insert_receipt_trie + // stack: new_cum_gas, txn_nb, retdest + // Now, we set the Bloom filter back to 0. + PUSH 0 + %rep 256 + // stack: counter, new_cum_gas, txn_nb, retdest + PUSH 0 DUP2 + // stack: counter, 0, counter, new_cum_gas, txn_nb, retdest + %mstore_kernel(@SEGMENT_TXN_BLOOM) + // stack: counter, new_cum_gas, txn_nb, retdest + %increment + %endrep + POP + // stack: new_cum_gas, txn_nb, retdest + %stack (new_cum_gas, txn_nb, retdest) -> (retdest, new_cum_gas, txn_nb) + JUMP + +failed_receipt: + // stack: status, new_cum_gas, txn_nb + // It is the receipt of a failed transaction, so set num_logs to 0. This will also lead to Bloom filter = 0. + PUSH 0 + %mstore_global_metadata(@GLOBAL_METADATA_LOGS_LEN) + // stack: status, new_cum_gas, txn_nb + %jump(process_receipt_after_status) + +%macro process_receipt + // stack: success, leftover_gas, cur_cum_gas, txn_nb + %stack (success, leftover_gas, cur_cum_gas, txn_nb) -> (success, leftover_gas, cur_cum_gas, txn_nb, %%after) + %jump(process_receipt) +%%after: +%endmacro + +%macro compute_cumulative_gas + // stack: cur_cum_gas, leftover_gas + DUP2 + // stack: leftover_gas, prev_cum_gas, leftover_gas + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + // stack: gas_limit, leftover_gas, prev_cum_gas, leftover_gas + DUP2 DUP2 LT %jumpi(panic) + // stack: gas_limit, leftover_gas, prev_cum_gas, leftover_gas + SUB + // stack: used_txn_gas, prev_cum_gas, leftover_gas + ADD SWAP1 POP + // stack: new_cum_gas +%endmacro diff --git a/evm/src/cpu/kernel/asm/core/log.asm b/evm/src/cpu/kernel/asm/core/log.asm index af2836c1..727e3fa4 100644 --- a/evm/src/cpu/kernel/asm/core/log.asm +++ b/evm/src/cpu/kernel/asm/core/log.asm @@ -1,5 +1,3 @@ -// TODO: Implement receipts - global sys_log0: %check_static // stack: kexit_info, offset, size @@ -14,8 +12,10 @@ log0_after_mem_gas: DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) // stack: gas, kexit_info, offset, size %charge_gas - %stack (kexit_info, offset, size) -> (kexit_info) - EXIT_KERNEL + %address + PUSH 0 + %stack (zero, address, kexit_info, offset, size) -> (address, zero, size, offset, finish_sys_log, kexit_info) + %jump(log_n_entry) global sys_log1: %check_static @@ -31,8 +31,10 @@ log1_after_mem_gas: DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) %add_const(@GAS_LOGTOPIC) // stack: gas, kexit_info, offset, size, topic %charge_gas - %stack (kexit_info, offset, size, topic) -> (kexit_info) - EXIT_KERNEL + %address + PUSH 1 + %stack (one, address, kexit_info, offset, size, topic) -> (address, one, topic, size, offset, finish_sys_log, kexit_info) + %jump(log_n_entry) global sys_log2: %check_static @@ -48,8 +50,10 @@ log2_after_mem_gas: DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) // stack: gas, kexit_info, offset, size, topic1, topic2 %charge_gas - %stack (kexit_info, offset, size, topic1, topic2) -> (kexit_info) - EXIT_KERNEL + %address + PUSH 2 + %stack (two, address, kexit_info, offset, size, topic1, topic2) -> (address, two, topic1, topic2, size, offset, finish_sys_log, kexit_info) + %jump(log_n_entry) global sys_log3: %check_static @@ -65,8 +69,10 @@ log3_after_mem_gas: DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) // stack: gas, kexit_info, offset, size, topic1, topic2, topic3 %charge_gas - %stack (kexit_info, offset, size, topic1, topic2, topic3) -> (kexit_info) - EXIT_KERNEL + %address + PUSH 3 + %stack (three, address, kexit_info, offset, size, topic1, topic2, topic3) -> (address, three, topic1, topic2, topic3, size, offset, finish_sys_log, kexit_info) + %jump(log_n_entry) global sys_log4: %check_static @@ -82,5 +88,178 @@ log4_after_mem_gas: DUP3 %mul_const(@GAS_LOGDATA) %add_const(@GAS_LOG) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) %add_const(@GAS_LOGTOPIC) // stack: gas, kexit_info, offset, size, topic1, topic2, topic3, topic4 %charge_gas - %stack (kexit_info, offset, size, topic1, topic2, topic3, topic4) -> (kexit_info) + %address + PUSH 4 + %stack (four, address, kexit_info, offset, size, topic1, topic2, topic3, topic4) -> (address, four, topic1, topic2, topic3, topic4, size, offset, finish_sys_log, kexit_info) + %jump(log_n_entry) + +finish_sys_log: + // stack: kexit_info EXIT_KERNEL + +global log_n_entry: + // stack: address, num_topics, topics, data_len, data_offset, retdest + %mload_global_metadata(@GLOBAL_METADATA_LOGS_LEN) + %mload_global_metadata(@GLOBAL_METADATA_LOGS_DATA_LEN) + // stack: log_ptr, logs_len, address, num_topics, topics, data_len, data_offset, retdest + DUP1 DUP3 + // stack: log_ptr, logs_len, log_ptr, logs_len, address, num_topics, topics, data_len, data_offset, retdest + %mstore_kernel(@SEGMENT_LOGS) + // stack: log_ptr, logs_len, address, num_topics, topics, data_len, data_offset, retdest + SWAP1 %increment + %mstore_global_metadata(@GLOBAL_METADATA_LOGS_LEN) + // stack: log_ptr, address, num_topics, topics, data_len, data_offset, retdest + %increment + // stack: addr_ptr, address, num_topics, topics, data_len, data_offset, retdest + // Store the address. + DUP2 DUP2 + %mstore_kernel(@SEGMENT_LOGS_DATA) + %increment + // stack: num_topics_ptr, address, num_topics, topics, data_len, data_offset, retdest + SWAP1 POP + // stack: num_topics_ptr, num_topics, topics, data_len, data_offset, retdest + // Store num_topics. + DUP2 DUP2 + %mstore_kernel(@SEGMENT_LOGS_DATA) + %increment + // stack: topics_ptr, num_topics, topics, data_len, data_offset, retdest + DUP2 + // stack: num_topics, topics_ptr, num_topics, topics, data_len, data_offset, retdest + ISZERO + %jumpi(log_after_topics) + // stack: topics_ptr, num_topics, topics, data_len, data_offset, retdest + // Store the first topic. + DUP3 DUP2 + %mstore_kernel(@SEGMENT_LOGS_DATA) + %increment + %stack (curr_topic_ptr, num_topics, topic1) -> (curr_topic_ptr, num_topics) + DUP2 %eq_const(1) + %jumpi(log_after_topics) + // stack: curr_topic_ptr, num_topics, remaining_topics, data_len, data_offset, retdest + // Store the second topic. + DUP3 DUP2 + %mstore_kernel(@SEGMENT_LOGS_DATA) + %increment + %stack (curr_topic_ptr, num_topics, topic2) -> (curr_topic_ptr, num_topics) + DUP2 %eq_const(2) + %jumpi(log_after_topics) + // stack: curr_topic_ptr, num_topics, remaining_topics, data_len, data_offset, retdest + // Store the third topic. + DUP3 DUP2 + %mstore_kernel(@SEGMENT_LOGS_DATA) + %increment + %stack (curr_topic_ptr, num_topics, topic3) -> (curr_topic_ptr, num_topics) + DUP2 %eq_const(3) + %jumpi(log_after_topics) + // stack: curr_topic_ptr, num_topics, remaining_topic, data_len, data_offset, retdest + // Store the fourth topic. + DUP3 DUP2 + %mstore_kernel(@SEGMENT_LOGS_DATA) + %increment + %stack (data_len_ptr, num_topics, topic4) -> (data_len_ptr, num_topics) + DUP2 %eq_const(4) + %jumpi(log_after_topics) + // Invalid num_topics. + PANIC + +log_after_topics: + // stack: data_len_ptr, num_topics, data_len, data_offset, retdest + // Compute RLP length of the log. + DUP3 + // stack: data_len, data_len_ptr, num_topics, data_len, data_offset, retdest + DUP5 SWAP1 + %rlp_data_len + // stack: rlp_data_len, data_len_ptr, num_topics, data_len, data_offset, retdest + DUP3 + // stack: num_topics, rlp_data_len, data_len_ptr, num_topics, data_len, data_offset, retdest + // Each topic is encoded with 1+32 bytes. + %mul_const(33) + %rlp_list_len + // stack: rlp_topics_len, rlp_data_len, data_len_ptr, num_topics, data_len, data_offset, retdest + ADD + // The address is encoded with 1+20 bytes. + %add_const(21) + // stack: log_payload_len, data_len_ptr, num_topics, data_len, data_offset, retdest + %mload_global_metadata(@GLOBAL_METADATA_LOGS_DATA_LEN) + DUP2 SWAP1 + // stack: log_ptr, log_payload_len, log_payload_len, data_len_ptr, num_topics, data_len, data_offset, retdest + %mstore_kernel(@SEGMENT_LOGS_DATA) + // stack: log_payload_len, data_len_ptr, num_topics, data_len, data_offset, retdest + %rlp_list_len + // stack: rlp_log_len, data_len_ptr, num_topics, data_len, data_offset, retdest + %mload_global_metadata(@GLOBAL_METADATA_LOGS_PAYLOAD_LEN) + ADD + %mstore_global_metadata(@GLOBAL_METADATA_LOGS_PAYLOAD_LEN) + // stack: data_len_ptr, num_topics, data_len, data_offset, retdest + // Store data_len. + DUP3 DUP2 + %mstore_kernel(@SEGMENT_LOGS_DATA) + %increment + // stack: data_ptr, num_topics, data_len, data_offset, retdest + SWAP1 POP + // stack: data_ptr, data_len, data_offset, retdest + DUP1 SWAP2 + // stack: data_len, data_ptr, data_ptr, data_offset, retdest + ADD + // stack: next_log_ptr, data_ptr, data_offset, retdest + SWAP1 + // stack: data_ptr, next_log_ptr, data_offset, retdest + +store_log_data_loop: + // stack: cur_data_ptr, next_log_ptr, cur_data_offset, retdest + DUP2 DUP2 EQ + // stack: cur_data_ptr == next_log_ptr, cur_data_ptr, next_log_ptr, cur_data_offset, retdest + %jumpi(store_log_data_loop_end) + // stack: cur_data_ptr, next_log_ptr, cur_data_offset, retdest + DUP3 + %mload_current(@SEGMENT_MAIN_MEMORY) + // stack: cur_data, cur_data_ptr, next_log_ptr, cur_data_offset, retdest + // Store current data byte. + DUP2 + %mstore_kernel(@SEGMENT_LOGS_DATA) + // stack: cur_data_ptr, next_log_ptr, cur_data_offset, retdest + SWAP2 %increment SWAP2 + // stack: cur_data_ptr, next_log_ptr, next_data_offset, retdest + %increment + %jump(store_log_data_loop) + +store_log_data_loop_end: + // stack: cur_data_ptr, next_log_ptr, cur_data_offset, retdest + POP + %mstore_global_metadata(@GLOBAL_METADATA_LOGS_DATA_LEN) + POP + JUMP + +rlp_data_len: + // stack: data_len, data_ptr, retdest + DUP1 ISZERO %jumpi(data_single_byte) // data will be encoded with a single byte + DUP1 PUSH 1 EQ %jumpi(one_byte_data) // data is encoded with either 1 or 2 bytes + // If we are here, data_len >= 2, and we can use rlp_list_len to determine the encoding length + %rlp_list_len + // stack: rlp_data_len, data_ptr, retdest + SWAP1 POP SWAP1 + JUMP + +data_single_byte: + // stack: data_len, data_ptr, retdest + %pop2 + PUSH 1 + SWAP1 + JUMP + +one_byte_data: + // stack: data_len, data_ptr, retdest + DUP2 + %mload_current(@SEGMENT_MAIN_MEMORY) + // stack: data_byte, data_len, data_ptr, retdest + %lt_const(0x80) %jumpi(data_single_byte) // special byte that only requires one byte to be encoded + %pop2 + PUSH 2 SWAP1 + JUMP + +%macro rlp_data_len + // stack: data_len, data_ptr + %stack (data_len, data_ptr) -> (data_len, data_ptr, %%after) + %jump(rlp_data_len) +%%after: +%endmacro diff --git a/evm/src/cpu/kernel/asm/core/process_txn.asm b/evm/src/cpu/kernel/asm/core/process_txn.asm index 450845ef..6a1b1c6c 100644 --- a/evm/src/cpu/kernel/asm/core/process_txn.asm +++ b/evm/src/cpu/kernel/asm/core/process_txn.asm @@ -4,7 +4,7 @@ // TODO: Save checkpoints in @CTX_METADATA_STATE_TRIE_CHECKPOINT_PTR and @SEGMENT_STORAGE_TRIE_CHECKPOINT_PTRS. // Pre stack: retdest -// Post stack: (empty) +// Post stack: success, leftover_gas global process_normalized_txn: // stack: retdest %compute_fees @@ -29,16 +29,16 @@ global process_normalized_txn: // stack: sender, retdest // Check that txn nonce matches account nonce. - DUP1 %nonce - DUP1 %eq_const(@MAX_NONCE) %assert_zero(invalid_txn) // EIP-2681 + DUP1 %nonce + DUP1 %eq_const(@MAX_NONCE) %assert_zero(invalid_txn_2) // EIP-2681 // stack: sender_nonce, sender, retdest %mload_txn_field(@TXN_FIELD_NONCE) // stack: tx_nonce, sender_nonce, sender, retdest - %assert_eq(invalid_txn) + %assert_eq(invalid_txn_1) // stack: sender, retdest // Assert sender has no code. - DUP1 %ext_code_empty %assert_nonzero(invalid_txn) + DUP1 %ext_code_empty %assert_nonzero(invalid_txn_1) // stack: sender, retdest // Assert sender balance >= gas_limit * gas_price + value. @@ -182,40 +182,44 @@ global process_contract_creation_txn_after_code_loaded: global process_contract_creation_txn_after_constructor: // stack: success, leftover_gas, new_ctx, address, retdest - DUP1 POP // TODO: Success will go into the receipt when we support that. + // We eventually return leftover_gas and success. + %stack (success, leftover_gas, new_ctx, address, retdest) -> (success, leftover_gas, new_ctx, address, retdest, success) + ISZERO %jumpi(contract_creation_fault_3) // EIP-3541: Reject new contract code starting with the 0xEF byte PUSH 0 %mload_current(@SEGMENT_RETURNDATA) %eq_const(0xEF) %jumpi(contract_creation_fault_3_zero_leftover) - // stack: leftover_gas, new_ctx, address, retdest + // stack: leftover_gas, new_ctx, address, retdest, success %returndatasize // Size of the code. - // stack: code_size, leftover_gas, new_ctx, address, retdest + // stack: code_size, leftover_gas, new_ctx, address, retdest, success DUP1 %gt_const(@MAX_CODE_SIZE) %jumpi(contract_creation_fault_4) - // stack: code_size, leftover_gas, new_ctx, address, retdest + // stack: code_size, leftover_gas, new_ctx, address, retdest, success %mul_const(@GAS_CODEDEPOSIT) SWAP1 - // stack: leftover_gas, codedeposit_cost, new_ctx, address, retdest + // stack: leftover_gas, codedeposit_cost, new_ctx, address, retdest, success DUP2 DUP2 LT %jumpi(contract_creation_fault_4) - // stack: leftover_gas, codedeposit_cost, new_ctx, address, retdest + // stack: leftover_gas, codedeposit_cost, new_ctx, address, retdest, success SUB // Store the code hash of the new contract. - // stack: leftover_gas, new_ctx, address, retdest + // stack: leftover_gas, new_ctx, address, retdest, success GET_CONTEXT %returndatasize %stack (size, ctx) -> (ctx, @SEGMENT_RETURNDATA, 0, size) // context, segment, offset, len KECCAK_GENERAL - // stack: codehash, leftover_gas, new_ctx, address, retdest + // stack: codehash, leftover_gas, new_ctx, address, retdest, success %observe_new_contract DUP4 - // stack: address, codehash, leftover_gas, new_ctx, address, retdest + // stack: address, codehash, leftover_gas, new_ctx, address, retdest, success %set_codehash - // stack: leftover_gas, new_ctx, address, retdest + %stack (leftover_gas, new_ctx, address, retdest, success) -> (leftover_gas, new_ctx, address, retdest, success, leftover_gas) %pay_coinbase_and_refund_sender + // stack: leftover_gas', new_ctx, address, retdest, success, leftover_gas + SWAP5 POP %delete_all_touched_addresses %delete_all_selfdestructed_addresses - // stack: new_ctx, address, retdest + // stack: new_ctx, address, retdest, success, leftover_gas POP POP JUMP @@ -258,10 +262,17 @@ global process_message_txn_return: // stack: retdest // Since no code was executed, the leftover gas is the non-intrinsic gas. %non_intrinisic_gas - // stack: leftover_gas, retdest + DUP1 + // stack: leftover_gas, leftover_gas, retdest %pay_coinbase_and_refund_sender + // stack: leftover_gas', leftover_gas, retdest + SWAP1 POP %delete_all_touched_addresses - // stack: retdest + // stack: leftover_gas', retdest + SWAP1 + PUSH 1 // success + SWAP1 + // stack: retdest, success, leftover_gas JUMP global process_message_txn_code_loaded: @@ -291,19 +302,22 @@ process_message_txn_code_loaded_finish: global process_message_txn_after_call: // stack: success, leftover_gas, new_ctx, retdest - DUP1 POP // TODO: Success will go into the receipt when we support that. + // We will return leftover_gas and success. + %stack (success, leftover_gas, new_ctx, retdest) -> (success, leftover_gas, new_ctx, retdest, success, leftover_gas) ISZERO %jumpi(process_message_txn_fail) process_message_txn_after_call_contd: - // stack: leftover_gas, new_ctx, retdest + // stack: leftover_gas, new_ctx, retdest, success, leftover_gas %pay_coinbase_and_refund_sender + // stack: leftover_gas', new_ctx, retdest, success, leftover_gas + SWAP4 POP %delete_all_touched_addresses %delete_all_selfdestructed_addresses - // stack: new_ctx, retdest + // stack: new_ctx, retdest, success, leftover_gas POP JUMP process_message_txn_fail: - // stack: leftover_gas, new_ctx, retdest + // stack: leftover_gas, new_ctx, retdest, success, leftover_gas // Transfer value back to the caller. %mload_txn_field(@TXN_FIELD_VALUE) ISZERO %jumpi(process_message_txn_after_call_contd) %mload_txn_field(@TXN_FIELD_VALUE) @@ -340,15 +354,16 @@ process_message_txn_fail: // stack: coinbase, used_gas_tip, leftover_gas' %add_eth // stack: leftover_gas' + DUP1 // Refund gas to the origin. %mload_txn_field(@TXN_FIELD_COMPUTED_FEE_PER_GAS) MUL - // stack: leftover_gas_cost + // stack: leftover_gas_cost, leftover_gas' %mload_txn_field(@TXN_FIELD_ORIGIN) - // stack: origin, leftover_gas_cost + // stack: origin, leftover_gas_cost, leftover_gas' %add_eth - // stack: (empty) + // stack: leftover_gas' %endmacro // Sets @TXN_FIELD_MAX_FEE_PER_GAS and @TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS. @@ -358,9 +373,9 @@ process_message_txn_fail: %mload_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) %mload_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) // stack: max_fee, max_priority_fee, base_fee - DUP3 DUP2 %assert_ge(invalid_txn) // Assert max_fee >= base_fee + DUP3 DUP2 %assert_ge(invalid_txn_3) // Assert max_fee >= base_fee // stack: max_fee, max_priority_fee, base_fee - DUP2 DUP2 %assert_ge(invalid_txn) // Assert max_fee >= max_priority_fee + DUP2 DUP2 %assert_ge(invalid_txn_3) // Assert max_fee >= max_priority_fee %stack (max_fee, max_priority_fee, base_fee) -> (max_fee, base_fee, max_priority_fee, base_fee) SUB // stack: max_fee - base_fee, max_priority_fee, base_fee @@ -386,41 +401,73 @@ create_contract_account_fault: %revert_checkpoint // stack: address, retdest POP - PUSH 0 // leftover gas + PUSH 0 // leftover_gas + // stack: leftover_gas, retdest %pay_coinbase_and_refund_sender + // stack: leftover_gas', retdest %delete_all_touched_addresses %delete_all_selfdestructed_addresses + // stack: leftover_gas', retdest + SWAP1 PUSH 0 // success + // stack: success, retdest, leftover_gas + SWAP1 JUMP contract_creation_fault_3: %revert_checkpoint - // stack: leftover_gas, new_ctx, address, retdest - %stack (leftover_gas, new_ctx, address, retdest) -> (leftover_gas, retdest) + %stack (leftover_gas, new_ctx, address, retdest, success) -> (leftover_gas, retdest, success) %pay_coinbase_and_refund_sender + // stack: leftover_gas', retdest, success %delete_all_touched_addresses %delete_all_selfdestructed_addresses + %stack (leftover_gas, retdest, success) -> (retdest, success, leftover_gas) JUMP contract_creation_fault_3_zero_leftover: %revert_checkpoint - // stack: leftover_gas, new_ctx, address, retdest + // stack: leftover_gas, new_ctx, address, retdest, success %pop3 PUSH 0 // leftover gas + // stack: leftover_gas, retdest, success %pay_coinbase_and_refund_sender %delete_all_touched_addresses %delete_all_selfdestructed_addresses + %stack (leftover_gas, retdest, success) -> (retdest, success, leftover_gas) JUMP contract_creation_fault_4: %revert_checkpoint - // stack: code_size, leftover_gas, new_ctx, address, retdest + // stack: code_size/leftover_gas, leftover_gas/codedeposit_cost, new_ctx, address, retdest, success %pop4 PUSH 0 // leftover gas + // stack: leftover_gas, retdest, success %pay_coinbase_and_refund_sender %delete_all_touched_addresses %delete_all_selfdestructed_addresses + %stack (leftover_gas, retdest, success) -> (retdest, success, leftover_gas) JUMP global invalid_txn: - %jump(txn_loop) + POP + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + PUSH 0 + %jump(txn_loop_after) + +global invalid_txn_1: + %pop2 + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + PUSH 0 + %jump(txn_loop_after) + +global invalid_txn_2: + %pop3 + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + PUSH 0 + %jump(txn_loop_after) + +global invalid_txn_3: + %pop4 + %mload_txn_field(@TXN_FIELD_GAS_LIMIT) + PUSH 0 + %jump(txn_loop_after) diff --git a/evm/src/cpu/kernel/asm/main.asm b/evm/src/cpu/kernel/asm/main.asm index 93503add..39f259e3 100644 --- a/evm/src/cpu/kernel/asm/main.asm +++ b/evm/src/cpu/kernel/asm/main.asm @@ -11,16 +11,33 @@ global hash_initial_tries: %mpt_hash_txn_trie %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_BEFORE) %assert_eq %mpt_hash_receipt_trie %mload_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_BEFORE) %assert_eq -global txn_loop: +global start_txns: + // stack: (empty) + // Last mpt input is txn_nb. + PROVER_INPUT(mpt) + PUSH 0 + // stack: init_used_gas, txn_nb + +txn_loop: // If the prover has no more txns for us to process, halt. PROVER_INPUT(end_of_txns) %jumpi(hash_final_tries) // Call route_txn. When we return, continue the txn loop. - PUSH txn_loop + PUSH txn_loop_after + // stack: retdest, prev_used_gas, txn_nb %jump(route_txn) +global txn_loop_after: + // stack: success, leftover_gas, cur_cum_gas, txn_nb + %process_receipt + // stack: new_cum_gas, txn_nb + SWAP1 %increment SWAP1 + %jump(txn_loop) + global hash_final_tries: + // stack: cum_gas, txn_nb + %pop2 %mpt_hash_state_trie %mload_global_metadata(@GLOBAL_METADATA_STATE_TRIE_DIGEST_AFTER) %assert_eq %mpt_hash_txn_trie %mload_global_metadata(@GLOBAL_METADATA_TXN_TRIE_DIGEST_AFTER) %assert_eq %mpt_hash_receipt_trie %mload_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_DIGEST_AFTER) %assert_eq diff --git a/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm index 07b3e674..596e9d49 100644 --- a/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm +++ b/evm/src/cpu/kernel/asm/mpt/hash/hash_trie_specific.asm @@ -99,8 +99,140 @@ global encode_account: global encode_txn: PANIC // TODO +// We assume a receipt in memory is stored as: +// [payload_len, status, cum_gas_used, bloom, logs_payload_len, num_logs, [logs]]. +// A log is [payload_len, address, num_topics, [topics], data_len, [data]]. +// TODO: support type >0 receipts. global encode_receipt: - PANIC // TODO + // stack: rlp_pos, value_ptr, retdest + // There is a double encoding! What we compute is: + // RLP(RLP(receipt)). + // First encode the wrapper prefix. + DUP2 %mload_trie_data + %rlp_list_len + // stack: rlp_receipt_len, rlp_pos, value_ptr, retdest + SWAP1 %encode_rlp_multi_byte_string_prefix + // stack: rlp_pos, value_ptr, retdest + // Then encode the receipt prefix. + DUP2 %mload_trie_data + // stack: payload_len, rlp_pos, value_ptr, retdest + SWAP1 %encode_rlp_list_prefix + // stack: rlp_pos, value_ptr, retdest + // Encode status. + DUP2 %increment %mload_trie_data + // stack: status, rlp_pos, value_ptr, retdest + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, value_ptr, retdest + // Encode cum_gas_used. + DUP2 %add_const(2) %mload_trie_data + // stack: cum_gas_used, rlp_pos, value_ptr, retdest + SWAP1 %encode_rlp_scalar + // stack: rlp_pos, value_ptr, retdest + // Encode bloom. + PUSH 256 // Bloom length. + DUP3 %add_const(3) PUSH @SEGMENT_TRIE_DATA PUSH 0 // MPT src address. + DUP5 + // stack: rlp_pos, SRC, 256, rlp_pos, value_ptr, retdest + %encode_rlp_string + // stack: rlp_pos, old_rlp_pos, value_ptr, retdest + SWAP1 POP + // stack: rlp_pos, value_ptr, retdest + // Encode logs prefix. + DUP2 %add_const(259) %mload_trie_data + // stack: logs_payload_len, rlp_pos, value_ptr, retdest + SWAP1 %encode_rlp_list_prefix + // stack: rlp_pos, value_ptr, retdest + DUP2 %add_const(261) + // stack: logs_ptr, rlp_pos, value_ptr, retdest + DUP3 %add_const(260) %mload_trie_data + // stack: num_logs, logs_ptr, rlp_pos, value_ptr, retdest + PUSH 0 + +encode_receipt_logs_loop: + // stack: i, num_logs, current_log_ptr, rlp_pos, value_ptr, retdest + DUP2 DUP2 EQ + // stack: i == num_logs, i, num_logs, current_log_ptr, rlp_pos, value_ptr, retdest + %jumpi(encode_receipt_end) + // stack: i, num_logs, current_log_ptr, rlp_pos, value_ptr, retdest + DUP3 DUP5 + // stack: rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + // Encode log prefix. + DUP2 %mload_trie_data + // stack: payload_len, rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + SWAP1 %encode_rlp_list_prefix + // stack: rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + // Encode address. + DUP2 %increment %mload_trie_data + // stack: address, rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + SWAP1 %encode_rlp_160 + // stack: rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + DUP2 %add_const(2) %mload_trie_data + // stack: num_topics, rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + // Encode topics prefix. + DUP1 %mul_const(33) + // stack: topics_payload_len, num_topics, rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + DUP3 %encode_rlp_list_prefix + // stack: new_rlp_pos, num_topics, rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + SWAP2 POP + // stack: num_topics, rlp_pos, current_log_ptr, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + SWAP2 %add_const(3) + // stack: topics_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + PUSH 0 + +encode_receipt_topics_loop: + // stack: j, topics_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + DUP4 DUP2 EQ + // stack: j == num_topics, j, topics_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + %jumpi(encode_receipt_topics_end) + // stack: j, topics_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + DUP2 DUP2 ADD + %mload_trie_data + // stack: current_topic, j, topics_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + DUP4 + // stack: rlp_pos, current_topic, j, topics_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + %encode_rlp_256 + // stack: new_rlp_pos, j, topics_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + SWAP3 POP + // stack: j, topics_ptr, new_rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + %increment + %jump(encode_receipt_topics_loop) + +encode_receipt_topics_end: + // stack: num_topics, topics_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + ADD + // stack: data_len_ptr, rlp_pos, num_topics, i, num_logs, current_log_ptr, old_rlp_pos, value_ptr, retdest + SWAP5 POP + // stack: rlp_pos, num_topics, i, num_logs, data_len_ptr, old_rlp_pos, value_ptr, retdest + SWAP5 POP + // stack: num_topics, i, num_logs, data_len_ptr, rlp_pos, value_ptr, retdest + POP + // stack: i, num_logs, data_len_ptr, rlp_pos, value_ptr, retdest + // Encode data prefix. + DUP3 %mload_trie_data + // stack: data_len, i, num_logs, data_len_ptr, rlp_pos, value_ptr, retdest + DUP4 %increment DUP2 ADD + // stack: next_log_ptr, data_len, i, num_logs, data_len_ptr, rlp_pos, value_ptr, retdest + SWAP4 %increment + // stack: data_ptr, data_len, i, num_logs, next_log_ptr, rlp_pos, value_ptr, retdest + PUSH @SEGMENT_TRIE_DATA PUSH 0 + // stack: SRC, data_len, i, num_logs, next_log_ptr, rlp_pos, value_ptr, retdest + DUP8 + // stack: rlp_pos, SRC, data_len, i, num_logs, next_log_ptr, rlp_pos, value_ptr, retdest + %encode_rlp_string + // stack: new_rlp_pos, i, num_logs, next_log_ptr, rlp_pos, value_ptr, retdest + SWAP4 POP + // stack: i, num_logs, next_log_ptr, new_rlp_pos, value_ptr, retdest + %increment + %jump(encode_receipt_logs_loop) + +encode_receipt_end: + // stack: num_logs, num_logs, current_log_ptr, rlp_pos, value_ptr, retdest + %pop3 + // stack: rlp_pos, value_ptr, retdest + SWAP1 POP + // stack: rlp_pos, retdest + SWAP1 + JUMP global encode_storage_value: // stack: rlp_pos, value_ptr, retdest @@ -114,3 +246,4 @@ global encode_storage_value: // stack: rlp_pos', retdest SWAP1 JUMP + diff --git a/evm/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm index 37d7fda1..457d604f 100644 --- a/evm/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm +++ b/evm/src/cpu/kernel/asm/mpt/insert/insert_trie_specific.asm @@ -22,3 +22,53 @@ mpt_insert_state_trie_save: %jump(mpt_insert_state_trie) %%after: %endmacro + +global mpt_insert_receipt_trie: + // stack: scalar, value_ptr, retdest + %stack (scalar, value_ptr) + -> (scalar, value_ptr, mpt_insert_receipt_trie_save) + // The key is the RLP encoding of scalar. + %scalar_to_rlp + // stack: key, value_ptr, mpt_insert_receipt_trie_save, retdest + DUP1 + %num_bytes %mul_const(2) + // stack: num_nibbles, key, value_ptr, mpt_insert_receipt_trie_save, retdest + %mload_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) + // stack: receipt_root_ptr, num_nibbles, key, value_ptr, mpt_insert_receipt_trie_save, retdest + %jump(mpt_insert) +mpt_insert_receipt_trie_save: + // stack: updated_node_ptr, retdest + %mstore_global_metadata(@GLOBAL_METADATA_RECEIPT_TRIE_ROOT) + JUMP + +%macro mpt_insert_receipt_trie + %stack (key, value_ptr) -> (key, value_ptr, %%after) + %jump(mpt_insert_receipt_trie) +%%after: +%endmacro + +// Pre stack: scalar, retdest +// Post stack: rlp_scalar +// We will make use of %encode_rlp_scalar, which clobbers RlpRaw. +// We're not hashing tries yet, so it's not an issue. +global scalar_to_rlp: + // stack: scalar, retdest + PUSH 0 + // stack: pos, scalar, retdest + %encode_rlp_scalar + // stack: pos', retdest + // Now our rlp_encoding is in RlpRaw in the first pos' cells. + DUP1 // len of the key + PUSH 0 PUSH @SEGMENT_RLP_RAW PUSH 0 // address where we get the key from + %mload_packing + // stack: packed_key, pos', retdest + SWAP1 POP + // stack: key, retdest + SWAP1 + JUMP + +%macro scalar_to_rlp + %stack (scalar) -> (scalar, %%after) + %jump(scalar_to_rlp) +%%after: +%endmacro diff --git a/evm/src/cpu/kernel/asm/mpt/load/load_trie_specific.asm b/evm/src/cpu/kernel/asm/mpt/load/load_trie_specific.asm index 80c6dbbe..e7b79039 100644 --- a/evm/src/cpu/kernel/asm/mpt/load/load_trie_specific.asm +++ b/evm/src/cpu/kernel/asm/mpt/load/load_trie_specific.asm @@ -33,7 +33,89 @@ global mpt_load_txn_trie_value: global mpt_load_receipt_trie_value: // stack: retdest - PANIC // TODO + + // Load payload_len. + PROVER_INPUT(mpt) %append_to_trie_data + // Load status. + PROVER_INPUT(mpt) %append_to_trie_data + // Load cum_gas_used. + PROVER_INPUT(mpt) %append_to_trie_data + // Load bloom. + %rep 256 + PROVER_INPUT(mpt) %append_to_trie_data + %endrep + // Load logs_payload_len. + PROVER_INPUT(mpt) %append_to_trie_data + // Load num_logs. + PROVER_INPUT(mpt) + DUP1 + %append_to_trie_data + // stack: num_logs, retdest + // Load logs. + PUSH 0 + +mpt_load_receipt_trie_value_logs_loop: + // stack: i, num_logs, retdest + DUP2 DUP2 EQ + // stack: i == num_logs, i, num_logs, retdest + %jumpi(mpt_load_receipt_trie_value_end) + // stack: i, num_logs, retdest + // Load log_payload_len. + PROVER_INPUT(mpt) %append_to_trie_data + // Load address. + PROVER_INPUT(mpt) %append_to_trie_data + // Load num_topics. + PROVER_INPUT(mpt) + DUP1 + %append_to_trie_data + // stack: num_topics, i, num_logs, retdest + // Load topics. + PUSH 0 + +mpt_load_receipt_trie_value_topics_loop: + // stack: j, num_topics, i, num_logs, retdest + DUP2 DUP2 EQ + // stack: j == num_topics, j, num_topics, i, num_logs, retdest + %jumpi(mpt_load_receipt_trie_value_topics_end) + // stack: j, num_topics, i, num_logs, retdest + // Load topic. + PROVER_INPUT(mpt) %append_to_trie_data + %increment + %jump(mpt_load_receipt_trie_value_topics_loop) + +mpt_load_receipt_trie_value_topics_end: + // stack: num_topics, num_topics, i, num_logs, retdest + %pop2 + // stack: i, num_logs, retdest + // Load data_len. + PROVER_INPUT(mpt) + DUP1 + %append_to_trie_data + // stack: data_len, i, num_logs, retdest + // Load data. + PUSH 0 + +mpt_load_receipt_trie_value_data_loop: + // stack: j, data_len, i, num_logs, retdest + DUP2 DUP2 EQ + // stack: j == data_len, j, data_len, i, num_logs, retdest + %jumpi(mpt_load_receipt_trie_value_data_end) + // stack: j, data_len, i, num_logs, retdest + // Load data byte. + PROVER_INPUT(mpt) %append_to_trie_data + %increment + %jump(mpt_load_receipt_trie_value_data_loop) + +mpt_load_receipt_trie_value_data_end: + // stack: data_len, data_len, i, num_logs, retdest + %pop2 + %increment + %jump(mpt_load_receipt_trie_value_logs_loop) + +mpt_load_receipt_trie_value_end: + // stack: num_logs, num_logs, retdest + %pop2 + JUMP global mpt_load_storage_trie_value: // stack: retdest diff --git a/evm/src/cpu/kernel/asm/rlp/encode_rlp_string.asm b/evm/src/cpu/kernel/asm/rlp/encode_rlp_string.asm index e094b8c3..1065c612 100644 --- a/evm/src/cpu/kernel/asm/rlp/encode_rlp_string.asm +++ b/evm/src/cpu/kernel/asm/rlp/encode_rlp_string.asm @@ -72,3 +72,9 @@ global encode_rlp_string_large_after_writing_len: %stack (pos3, pos2, ADDR: 3, len, retdest) -> (0, @SEGMENT_RLP_RAW, pos2, ADDR, len, retdest, pos3) %jump(memcpy) + +%macro encode_rlp_string + %stack (pos, ADDR: 3, len) -> (pos, ADDR, len, %%after) + %jump(encode_rlp_string) +%%after: +%endmacro diff --git a/evm/src/cpu/kernel/constants/global_metadata.rs b/evm/src/cpu/kernel/constants/global_metadata.rs index 3b6b80a4..f33fe7a1 100644 --- a/evm/src/cpu/kernel/constants/global_metadata.rs +++ b/evm/src/cpu/kernel/constants/global_metadata.rs @@ -21,36 +21,38 @@ pub(crate) enum GlobalMetadata { ReceiptTrieRoot = 6, // The root digests of each Merkle trie before these transactions. - StateTrieRootDigestBefore = 8, - TransactionTrieRootDigestBefore = 9, - ReceiptTrieRootDigestBefore = 10, + StateTrieRootDigestBefore = 7, + TransactionTrieRootDigestBefore = 8, + ReceiptTrieRootDigestBefore = 9, // The root digests of each Merkle trie after these transactions. - StateTrieRootDigestAfter = 11, - TransactionTrieRootDigestAfter = 12, - ReceiptTrieRootDigestAfter = 13, + StateTrieRootDigestAfter = 10, + TransactionTrieRootDigestAfter = 11, + ReceiptTrieRootDigestAfter = 12, /// The sizes of the `TrieEncodedChild` and `TrieEncodedChildLen` buffers. In other words, the /// next available offset in these buffers. - TrieEncodedChildSize = 14, + TrieEncodedChildSize = 13, // Block metadata. - BlockBeneficiary = 15, - BlockTimestamp = 16, - BlockNumber = 17, - BlockDifficulty = 18, - BlockGasLimit = 19, - BlockChainId = 20, - BlockBaseFee = 21, + BlockBeneficiary = 14, + BlockTimestamp = 15, + BlockNumber = 16, + BlockDifficulty = 17, + BlockGasLimit = 18, + BlockChainId = 19, + BlockBaseFee = 20, /// Gas to refund at the end of the transaction. - RefundCounter = 22, + RefundCounter = 21, /// Length of the addresses access list. - AccessedAddressesLen = 23, + AccessedAddressesLen = 22, /// Length of the storage keys access list. - AccessedStorageKeysLen = 24, + AccessedStorageKeysLen = 23, /// Length of the self-destruct list. - SelfDestructListLen = 25, + SelfDestructListLen = 24, + /// Length of the bloom entry buffer. + BloomEntryLen = 25, /// Length of the journal. JournalLen = 26, @@ -69,10 +71,14 @@ pub(crate) enum GlobalMetadata { ContractCreation = 33, IsPrecompileFromEoa = 34, CallStackDepth = 35, + /// Transaction logs list length + LogsLen = 36, + LogsDataLen = 37, + LogsPayloadLen = 38, } impl GlobalMetadata { - pub(crate) const COUNT: usize = 35; + pub(crate) const COUNT: usize = 39; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -101,6 +107,7 @@ impl GlobalMetadata { Self::AccessedAddressesLen, Self::AccessedStorageKeysLen, Self::SelfDestructListLen, + Self::BloomEntryLen, Self::JournalLen, Self::JournalDataLen, Self::CurrentCheckpoint, @@ -111,6 +118,9 @@ impl GlobalMetadata { Self::ContractCreation, Self::IsPrecompileFromEoa, Self::CallStackDepth, + Self::LogsLen, + Self::LogsDataLen, + Self::LogsPayloadLen, ] } @@ -142,6 +152,7 @@ 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::BloomEntryLen => "GLOBAL_METADATA_BLOOM_ENTRY_LEN", Self::JournalLen => "GLOBAL_METADATA_JOURNAL_LEN", Self::JournalDataLen => "GLOBAL_METADATA_JOURNAL_DATA_LEN", Self::CurrentCheckpoint => "GLOBAL_METADATA_CURRENT_CHECKPOINT", @@ -152,6 +163,9 @@ impl GlobalMetadata { Self::ContractCreation => "GLOBAL_METADATA_CONTRACT_CREATION", Self::IsPrecompileFromEoa => "GLOBAL_METADATA_IS_PRECOMPILE_FROM_EOA", Self::CallStackDepth => "GLOBAL_METADATA_CALL_STACK_DEPTH", + Self::LogsLen => "GLOBAL_METADATA_LOGS_LEN", + Self::LogsDataLen => "GLOBAL_METADATA_LOGS_DATA_LEN", + Self::LogsPayloadLen => "GLOBAL_METADATA_LOGS_PAYLOAD_LEN", } } } diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 7e9482e8..98ea3cc2 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -225,6 +225,10 @@ impl<'a> Interpreter<'a> { .content = memory; } + pub(crate) fn set_memory_segment(&mut self, segment: Segment, memory: Vec) { + self.generation_state.memory.contexts[0].segments[segment as usize].content = memory; + } + pub(crate) fn set_memory_segment_bytes(&mut self, segment: Segment, memory: Vec) { self.generation_state.memory.contexts[0].segments[segment as usize].content = memory.into_iter().map(U256::from).collect(); @@ -1020,7 +1024,8 @@ impl<'a> Interpreter<'a> { fn run_mload_general(&mut self) { let context = self.pop().as_usize(); let segment = Segment::all()[self.pop().as_usize()]; - let offset = self.pop().as_usize(); + let offset_u256 = self.pop(); + let offset = offset_u256.as_usize(); let value = self .generation_state .memory diff --git a/evm/src/cpu/kernel/tests/log.rs b/evm/src/cpu/kernel/tests/log.rs new file mode 100644 index 00000000..08ba7d52 --- /dev/null +++ b/evm/src/cpu/kernel/tests/log.rs @@ -0,0 +1,198 @@ +use anyhow::Result; +use ethereum_types::{Address, U256}; +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; + +#[test] +fn test_log_0() -> Result<()> { + let logs_entry = KERNEL.global_labels["log_n_entry"]; + let address: Address = thread_rng().gen(); + let num_topics = U256::from(0); + let data_len = U256::from(0); + let data_offset = U256::from(0); + + let retdest = 0xDEADBEEFu32.into(); + + let initial_stack = vec![ + retdest, + data_offset, + data_len, + num_topics, + U256::from_big_endian(&address.to_fixed_bytes()), + ]; + + let mut interpreter = Interpreter::new_with_kernel(logs_entry, initial_stack); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, 0.into()); + interpreter.set_global_metadata_field(GlobalMetadata::LogsDataLen, 0.into()); + + interpreter.run()?; + + // The address is encoded in 1+20 bytes. There are no topics or data, so each is encoded in 1 byte. This leads to a payload of 23. + let payload_len = 23; + assert_eq!( + interpreter.get_memory_segment(Segment::LogsData), + [ + payload_len.into(), + U256::from_big_endian(&address.to_fixed_bytes()), + 0.into(), + 0.into(), + ] + ); + Ok(()) +} + +#[test] +fn test_log_2() -> Result<()> { + let logs_entry = KERNEL.global_labels["log_n_entry"]; + let address: Address = thread_rng().gen(); + let num_topics = U256::from(2); + let topics = vec![4.into(), 5.into()]; + let data_len = U256::from(3); + let data_offset = U256::from(0); + + let memory = vec![10.into(), 20.into(), 30.into()]; + + let retdest = 0xDEADBEEFu32.into(); + + let initial_stack = vec![ + retdest, + data_offset, + data_len, + topics[1], + topics[0], + num_topics, + U256::from_big_endian(&address.to_fixed_bytes()), + ]; + + let mut interpreter = Interpreter::new_with_kernel(logs_entry, initial_stack); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, 2.into()); + interpreter.set_global_metadata_field(GlobalMetadata::LogsDataLen, 5.into()); + + interpreter.set_memory_segment(Segment::MainMemory, memory); + + interpreter.run()?; + assert_eq!( + interpreter.get_memory_segment(Segment::Logs), + [0.into(), 0.into(), 5.into(),] + ); + + // The data has length 3 bytes, and is encoded in 4 bytes. Each of the two topics is encoded in 1+32 bytes. The prefix for the topics list requires 2 bytes. The address is encoded in 1+20 bytes. Overall, we have a logs payload length of 93 bytes. + let payload_len = 93; + assert_eq!( + interpreter.get_memory_segment(Segment::LogsData), + [ + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + payload_len.into(), + U256::from_big_endian(&address.to_fixed_bytes()), + 2.into(), + 4.into(), + 5.into(), + 3.into(), + 10.into(), + 20.into(), + 30.into(), + ] + ); + Ok(()) +} + +#[test] +fn test_log_4() -> Result<()> { + let logs_entry = KERNEL.global_labels["log_n_entry"]; + let address: Address = thread_rng().gen(); + let num_topics = U256::from(4); + let topics = vec![45.into(), 46.into(), 47.into(), 48.into()]; + let data_len = U256::from(1); + let data_offset = U256::from(2); + + let memory = vec![0.into(), 0.into(), 123.into()]; + + let retdest = 0xDEADBEEFu32.into(); + + let initial_stack = vec![ + retdest, + data_offset, + data_len, + topics[3], + topics[2], + topics[1], + topics[0], + num_topics, + U256::from_big_endian(&address.to_fixed_bytes()), + ]; + + let mut interpreter = Interpreter::new_with_kernel(logs_entry, initial_stack); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, 2.into()); + interpreter.set_global_metadata_field(GlobalMetadata::LogsDataLen, 5.into()); + + interpreter.set_memory_segment(Segment::MainMemory, memory); + + interpreter.run()?; + assert_eq!( + interpreter.get_memory_segment(Segment::Logs), + [0.into(), 0.into(), 5.into(),] + ); + + // The data is of length 1 byte, and is encoded in 1 byte. Each of the four topics is encoded in 1+32 bytes. The topics list is prefixed by 2 bytes. The address is encoded in 1+20 bytes. Overall, this leads to a log payload length of 156. + let payload_len = 156; + assert_eq!( + interpreter.get_memory_segment(Segment::LogsData), + [ + 0.into(), + 0.into(), + 0.into(), + 0.into(), + 0.into(), + payload_len.into(), + U256::from_big_endian(&address.to_fixed_bytes()), + 4.into(), + 45.into(), + 46.into(), + 47.into(), + 48.into(), + 1.into(), + 123.into(), + ] + ); + Ok(()) +} + +#[test] +fn test_log_5() -> Result<()> { + let logs_entry = KERNEL.global_labels["log_n_entry"]; + let address: Address = thread_rng().gen(); + let num_topics = U256::from(5); + let topics = vec![1.into(), 2.into(), 3.into(), 4.into(), 5.into()]; + let data_len = U256::from(0); + let data_offset = U256::from(0); + + let retdest = 0xDEADBEEFu32.into(); + + let initial_stack = vec![ + retdest, + data_offset, + data_len, + topics[4], + topics[3], + topics[2], + topics[1], + topics[0], + num_topics, + U256::from_big_endian(&address.to_fixed_bytes()), + ]; + + let mut interpreter = Interpreter::new_with_kernel(logs_entry, initial_stack); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, 0.into()); + interpreter.set_global_metadata_field(GlobalMetadata::LogsDataLen, 0.into()); + + assert!(interpreter.run().is_err()); + Ok(()) +} diff --git a/evm/src/cpu/kernel/tests/mod.rs b/evm/src/cpu/kernel/tests/mod.rs index 8f691161..938a60bd 100644 --- a/evm/src/cpu/kernel/tests/mod.rs +++ b/evm/src/cpu/kernel/tests/mod.rs @@ -8,8 +8,10 @@ mod core; mod ecc; mod exp; mod hash; +mod log; mod mpt; mod packing; +mod receipt; mod rlp; mod signed_syscalls; mod transaction_parsing; diff --git a/evm/src/cpu/kernel/tests/receipt.rs b/evm/src/cpu/kernel/tests/receipt.rs new file mode 100644 index 00000000..3096c29d --- /dev/null +++ b/evm/src/cpu/kernel/tests/receipt.rs @@ -0,0 +1,595 @@ +use anyhow::Result; +use ethereum_types::{Address, U256}; +use hex_literal::hex; +use keccak_hash::keccak; +use rand::{thread_rng, Rng}; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::constants::global_metadata::GlobalMetadata; +use crate::cpu::kernel::constants::txn_fields::NormalizedTxnField; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::generation::mpt::{all_mpt_prover_inputs_reversed, LegacyReceiptRlp, LogRlp}; +use crate::memory::segments::Segment; + +#[test] +fn test_process_receipt() -> Result<()> { + /* Tests process_receipt, which: + - computes the cumulative gas + - computes the bloom filter + - inserts the receipt data in MPT_TRIE_DATA + - inserts a node in receipt_trie + - resets the bloom filter to 0 for the next transaction. */ + let process_receipt = KERNEL.global_labels["process_receipt"]; + let success = U256::from(1); + let leftover_gas = U256::from(4000); + let prev_cum_gas = U256::from(1000); + let retdest = 0xDEADBEEFu32.into(); + + // Log. + let address: Address = thread_rng().gen(); + let num_topics = 1; + + let mut topic = vec![0_u8; 32]; + topic[31] = 4; + + // Compute the expected Bloom filter. + let test_logs_list = vec![(address.to_fixed_bytes().to_vec(), vec![topic])]; + let expected_bloom = logs_bloom_bytes_fn(test_logs_list).to_vec(); + + // Set memory. + let initial_stack = vec![retdest, 0.into(), prev_cum_gas, leftover_gas, success]; + let mut interpreter = Interpreter::new_with_kernel(process_receipt, initial_stack); + interpreter.set_memory_segment( + Segment::LogsData, + vec![ + 56.into(), // payload len + U256::from_big_endian(&address.to_fixed_bytes()), // address + num_topics.into(), // num_topics + 4.into(), // topic + 0.into(), // data_len + ], + ); + interpreter.set_txn_field(NormalizedTxnField::GasLimit, U256::from(5000)); + interpreter.set_memory_segment(Segment::TxnBloom, vec![0.into(); 256]); + interpreter.set_memory_segment(Segment::BlockBloom, vec![0.into(); 256]); + interpreter.set_memory_segment(Segment::Logs, vec![0.into()]); + interpreter.set_global_metadata_field(GlobalMetadata::LogsPayloadLen, 58.into()); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, U256::from(1)); + interpreter.set_global_metadata_field(GlobalMetadata::ReceiptTrieRoot, 500.into()); + interpreter.run()?; + + 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()]; + expected_trie_data.extend( + expected_bloom + .into_iter() + .map(|elt| elt.into()) + .collect::>(), + ); + expected_trie_data.push(58.into()); // logs_payload_len + expected_trie_data.push(1.into()); // num_logs + expected_trie_data.extend(vec![ + 56.into(), // payload len + U256::from_big_endian(&address.to_fixed_bytes()), // address + num_topics.into(), // num_topics + 4.into(), // topic + 0.into(), // data_len + ]); + + assert_eq!( + expected_trie_data, + segment_read[0..expected_trie_data.len()] + ); + + Ok(()) +} + +/// Values taken from the block 1000000 of Goerli: https://goerli.etherscan.io/txs?block=1000000 +#[test] +fn test_receipt_encoding() -> Result<()> { + // Initialize interpreter. + let success = U256::from(1); + + let retdest = 0xDEADBEEFu32.into(); + let num_topics = 3; + + let encode_receipt = KERNEL.global_labels["encode_receipt"]; + + // Logs and receipt in encodable form. + let log_1 = LogRlp { + address: hex!("7ef66b77759e12Caf3dDB3E4AFF524E577C59D8D").into(), + topics: vec![ + hex!("8a22ee899102a366ac8ad0495127319cb1ff2403cfae855f83a89cda1266674d").into(), + hex!("0000000000000000000000000000000000000000000000000000000000000004").into(), + hex!("00000000000000000000000000000000000000000000000000000000004920ea").into(), + ], + data: hex!("a814f7df6a2203dc0e472e8828be95957c6b329fee8e2b1bb6f044c1eb4fc243") + .to_vec() + .into(), + }; + + let receipt_1 = LegacyReceiptRlp { + status: true, + cum_gas_used: 0x02dcb6u64.into(), + bloom: hex!("00000000000000000000000000000000000000000000000000800000000000000040000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000008000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000400000000000000000000000000000002000040000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000008000000000000000000000000").to_vec().into(), + logs: vec![log_1], + }; + // Get the expected RLP encoding. + let expected_rlp = rlp::encode(&rlp::encode(&receipt_1)); + + let initial_stack = vec![retdest, 0.into(), 0.into()]; + let mut interpreter = Interpreter::new_with_kernel(encode_receipt, initial_stack); + + // Write data to memory. + let expected_bloom_bytes = vec![ + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 0x80, 00, 00, 00, 00, 00, 00, 00, 0x40, 00, 00, 00, 00, 0x10, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x02, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 0x08, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 0x01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x01, 00, 00, 00, 0x40, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 0x20, 00, 0x04, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x80, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x08, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + ]; + let expected_bloom: Vec = expected_bloom_bytes + .into_iter() + .map(|elt| elt.into()) + .collect(); + + let addr = U256::from([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7e, 0xf6, 0x6b, 0x77, 0x75, 0x9e, 0x12, 0xca, 0xf3, + 0xdd, 0xb3, 0xe4, 0xaf, 0xf5, 0x24, 0xe5, 0x77, 0xc5, 0x9d, 0x8d, + ]); + + let topic1 = U256::from([ + 0x8a, 0x22, 0xee, 0x89, 0x91, 0x02, 0xa3, 0x66, 0xac, 0x8a, 0xd0, 0x49, 0x51, 0x27, 0x31, + 0x9c, 0xb1, 0xff, 0x24, 0x03, 0xcf, 0xae, 0x85, 0x5f, 0x83, 0xa8, 0x9c, 0xda, 0x12, 0x66, + 0x67, 0x4d, + ]); + + let topic2 = 4.into(); + let topic3 = 0x4920ea.into(); + + let mut logs = vec![ + 155.into(), // unused + addr, + num_topics.into(), // num_topics + topic1, // topic1 + topic2, // topic2 + topic3, // topic3 + 32.into(), // data length + ]; + let cur_data = hex!("a814f7df6a2203dc0e472e8828be95957c6b329fee8e2b1bb6f044c1eb4fc243") + .iter() + .copied() + .map(U256::from); + logs.extend(cur_data); + + let mut receipt = vec![423.into(), success, receipt_1.cum_gas_used]; + receipt.extend(expected_bloom.clone()); + receipt.push(157.into()); // logs_payload_len + receipt.push(1.into()); // num_logs + receipt.extend(logs.clone()); + interpreter.set_memory_segment(Segment::LogsData, logs); + + interpreter.set_memory_segment(Segment::TxnBloom, expected_bloom); + + interpreter.set_memory_segment(Segment::Logs, vec![0.into()]); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, 1.into()); + interpreter.set_global_metadata_field(GlobalMetadata::LogsPayloadLen, 157.into()); + interpreter.set_memory_segment(Segment::TrieData, receipt); + + interpreter.run()?; + let rlp_pos = interpreter.pop(); + + let rlp_read: Vec = interpreter.get_rlp_memory(); + + assert_eq!(rlp_pos.as_usize(), expected_rlp.len()); + for i in 0..rlp_read.len() { + assert_eq!(rlp_read[i], expected_rlp[i]); + } + + Ok(()) +} + +/// Values taken from the block 1000000 of Goerli: https://goerli.etherscan.io/txs?block=1000000 +#[test] +fn test_receipt_bloom_filter() -> Result<()> { + let logs_bloom = KERNEL.global_labels["logs_bloom"]; + + let num_topics = 3; + + // Expected bloom + let first_bloom_bytes = vec![ + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 0x80, 00, 00, 00, 00, 00, 00, 00, 0x40, 00, 00, 00, 00, 0x50, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x02, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x08, 00, 0x08, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x50, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x10, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x20, 00, 00, 00, 00, 00, 0x08, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + ]; + + let retdest = 0xDEADBEEFu32.into(); + + let addr = U256::from([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7e, 0xf6, 0x6b, 0x77, 0x75, 0x9e, 0x12, 0xca, 0xf3, + 0xdd, 0xb3, 0xe4, 0xaf, 0xf5, 0x24, 0xe5, 0x77, 0xc5, 0x9d, 0x8d, + ]); + + let topic1 = U256::from([ + 0x8a, 0x22, 0xee, 0x89, 0x91, 0x02, 0xa3, 0x66, 0xac, 0x8a, 0xd0, 0x49, 0x51, 0x27, 0x31, + 0x9c, 0xb1, 0xff, 0x24, 0x03, 0xcf, 0xae, 0x85, 0x5f, 0x83, 0xa8, 0x9c, 0xda, 0x12, 0x66, + 0x67, 0x4d, + ]); + + let topic02 = 0x2a.into(); + let topic03 = 0xbd9fe6.into(); + + // Set logs memory and initialize TxnBloom and BlockBloom segments. + let initial_stack = vec![retdest]; + + let mut interpreter = Interpreter::new_with_kernel(logs_bloom, initial_stack); + let mut logs = vec![ + 0.into(), // unused + addr, + num_topics.into(), // num_topics + topic1, // topic1 + topic02, // topic2 + topic03, // topic3 + 32.into(), // data_len + ]; + let cur_data = hex!("a814f7df6a2203dc0e472e8828be95957c6b329fee8e2b1bb6f044c1eb4fc243") + .iter() + .copied() + .map(U256::from); + logs.extend(cur_data); + // The Bloom filter initialization is required for this test to ensure we have the correct length for the filters. Otherwise, some trailing zeroes could be missing. + interpreter.set_memory_segment(Segment::TxnBloom, vec![0.into(); 256]); // Initialize transaction Bloom filter. + interpreter.set_memory_segment(Segment::BlockBloom, vec![0.into(); 256]); // Initialize block Bloom filter. + interpreter.set_memory_segment(Segment::LogsData, logs); + interpreter.set_memory_segment(Segment::Logs, vec![0.into()]); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, U256::from(1)); + interpreter.run()?; + + // Second transaction. + let loaded_bloom_u256 = interpreter.get_memory_segment(Segment::TxnBloom); + let loaded_bloom: Vec = loaded_bloom_u256 + .into_iter() + .map(|elt| elt.0[0] as u8) + .collect(); + + assert_eq!(first_bloom_bytes, loaded_bloom); + let topic12 = 0x4.into(); + let topic13 = 0x4920ea.into(); + let mut logs2 = vec![ + 0.into(), // unused + addr, + num_topics.into(), // num_topics + topic1, // topic1 + topic12, // topic2 + topic13, // topic3 + 32.into(), // data_len + ]; + let cur_data = hex!("a814f7df6a2203dc0e472e8828be95957c6b329fee8e2b1bb6f044c1eb4fc243") + .iter() + .copied() + .map(U256::from); + logs2.extend(cur_data); + + interpreter.push(retdest); + interpreter.generation_state.registers.program_counter = logs_bloom; + interpreter.set_memory_segment(Segment::TxnBloom, vec![0.into(); 256]); // Initialize transaction Bloom filter. + interpreter.set_memory_segment(Segment::LogsData, logs2); + interpreter.set_memory_segment(Segment::Logs, vec![0.into()]); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, U256::from(1)); + interpreter.run()?; + + let second_bloom_bytes = vec![ + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 0x80, 00, 00, 00, 00, 00, 00, 00, 0x40, 00, 00, 00, 00, 0x10, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x02, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 0x08, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 0x01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x01, 00, 00, 00, 0x40, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 0x20, 00, 0x04, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x80, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x08, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + ]; + + let second_loaded_bloom_u256 = interpreter.get_memory_segment(Segment::TxnBloom); + let second_loaded_bloom: Vec = second_loaded_bloom_u256 + .into_iter() + .map(|elt| elt.0[0] as u8) + .collect(); + + assert_eq!(second_bloom_bytes, second_loaded_bloom); + + // Check the final block Bloom. + let block_bloom = hex!("00000000000000000000000000000000000000000000000000800000000000000040000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000008000000000000000000000000000000000000000001000000080008000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000500000000000000000000000000000002000040000000000000000000000000000000000000000000000008000000000000000000000100000000000000000000000000020000000000008000000000000000000000000").to_vec(); + let loaded_block_bloom: Vec = interpreter + .get_memory_segment(Segment::BlockBloom) + .into_iter() + .map(|elt| elt.0[0] as u8) + .collect(); + + assert_eq!(block_bloom, loaded_block_bloom); + Ok(()) +} + +#[test] +fn test_mpt_insert_receipt() -> Result<()> { + // This test simulates a receipt processing to test `mpt_insert_receipt_trie`. + // For this, we need to set the data correctly in memory. + // In TrieData, we need to insert a receipt of the form: + // `[payload_len, status, cum_gas_used, bloom, logs_payload_len, num_logs, [logs]]`. + // We also need to set TrieDataSize correctly. + + let retdest = 0xDEADBEEFu32.into(); + let trie_inputs = Default::default(); + let load_all_mpts = KERNEL.global_labels["load_all_mpts"]; + let mpt_insert = KERNEL.global_labels["mpt_insert_receipt_trie"]; + let num_topics = 3; // Both transactions have the same number of topics. + let payload_len = 423; // Total payload length for each receipt. + let logs_payload_len = 157; // Payload length for all logs. + let log_payload_len = 155; // Payload length for one log. + let num_logs = 1; + + // Receipt_0: + let status_0 = 1; + let cum_gas_used_0 = 0x016e5b; + let logs_bloom_0_bytes = vec![ + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 0x80, 00, 00, 00, 00, 00, 00, 00, 0x40, 00, 00, 00, 00, 0x50, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x02, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x08, 00, 0x08, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x50, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x10, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x20, 00, 00, 00, 00, 00, 0x08, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + ]; + + // Logs_0: + let logs_bloom_0: Vec = logs_bloom_0_bytes + .into_iter() + .map(|elt| elt.into()) + .collect(); + + let addr = U256::from([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7e, 0xf6, 0x6b, 0x77, 0x75, 0x9e, 0x12, 0xca, 0xf3, + 0xdd, 0xb3, 0xe4, 0xaf, 0xf5, 0x24, 0xe5, 0x77, 0xc5, 0x9d, 0x8d, + ]); + + // The first topic is shared by the two transactions. + let topic1 = U256::from([ + 0x8a, 0x22, 0xee, 0x89, 0x91, 0x02, 0xa3, 0x66, 0xac, 0x8a, 0xd0, 0x49, 0x51, 0x27, 0x31, + 0x9c, 0xb1, 0xff, 0x24, 0x03, 0xcf, 0xae, 0x85, 0x5f, 0x83, 0xa8, 0x9c, 0xda, 0x12, 0x66, + 0x67, 0x4d, + ]); + + let topic02 = 0x2a.into(); + let topic03 = 0xbd9fe6.into(); + + let mut logs_0 = vec![ + log_payload_len.into(), // payload_len + addr, + num_topics.into(), // num_topics + topic1, // topic1 + topic02, // topic2 + topic03, // topic3 + 32.into(), // data_len + ]; + let cur_data = hex!("f7af1cc94b1aef2e0fa15f1b4baefa86eb60e78fa4bd082372a0a446d197fb58") + .iter() + .copied() + .map(U256::from); + logs_0.extend(cur_data); + + let mut receipt: Vec = vec![423.into(), status_0.into(), cum_gas_used_0.into()]; + receipt.extend(logs_bloom_0); + receipt.push(logs_payload_len.into()); // logs_payload_len + receipt.push(num_logs.into()); // num_logs + receipt.extend(logs_0.clone()); + + // First, we load all mpts. + let initial_stack = vec![retdest]; + + let mut interpreter = Interpreter::new_with_kernel(load_all_mpts, initial_stack); + interpreter.generation_state.mpt_prover_inputs = all_mpt_prover_inputs_reversed(&trie_inputs); + interpreter.run()?; + + // If TrieData is empty, we need to push 0 because the first value is always 0. + let mut cur_trie_data = interpreter.get_memory_segment(Segment::TrieData); + if cur_trie_data.is_empty() { + cur_trie_data.push(0.into()); + } + + // stack: transaction_nb, value_ptr, retdest + let initial_stack = vec![retdest, cur_trie_data.len().into(), 0.into()]; + for i in 0..initial_stack.len() { + interpreter.push(initial_stack[i]); + } + + interpreter.generation_state.registers.program_counter = mpt_insert; + + // Set memory. + cur_trie_data.extend(receipt); + interpreter.set_memory_segment(Segment::TrieData, cur_trie_data.clone()); + interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, cur_trie_data.len().into()); + // First insertion. + interpreter.run()?; + + // receipt_1: + let status_1 = 1; + let cum_gas_used_1 = 0x02dcb6; + let logs_bloom_1_bytes = vec![ + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 0x80, 00, 00, 00, 00, 00, 00, 00, 0x40, 00, 00, 00, 00, 0x10, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x02, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 0x08, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 0x01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x01, 00, 00, 00, 0x40, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 0x20, 00, 0x04, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x80, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x08, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + ]; + + // Logs_1: + let logs_bloom_1: Vec = logs_bloom_1_bytes + .into_iter() + .map(|elt| elt.into()) + .collect(); + + let topic12 = 4.into(); + let topic13 = 0x4920ea.into(); + + let mut logs_1 = vec![ + log_payload_len.into(), // payload length + addr, + num_topics.into(), // nb topics + topic1, // topic1 + topic12, // topic2 + topic13, // topic3 + 32.into(), // data length + ]; + let cur_data = hex!("a814f7df6a2203dc0e472e8828be95957c6b329fee8e2b1bb6f044c1eb4fc243") + .iter() + .copied() + .map(U256::from); + logs_1.extend(cur_data); + + let mut receipt_1: Vec = vec![payload_len.into(), status_1.into(), cum_gas_used_1.into()]; + receipt_1.extend(logs_bloom_1); + receipt_1.push(logs_payload_len.into()); // logs payload len + receipt_1.push(num_logs.into()); // nb logs + receipt_1.extend(logs_1.clone()); + + // Get updated TrieData segment. + cur_trie_data = interpreter.get_memory_segment(Segment::TrieData); + let initial_stack2 = vec![retdest, cur_trie_data.len().into(), 1.into()]; + for i in 0..initial_stack2.len() { + interpreter.push(initial_stack2[i]); + } + cur_trie_data.extend(receipt_1); + + // Set memory. + interpreter.generation_state.registers.program_counter = mpt_insert; + interpreter.set_memory_segment(Segment::TrieData, cur_trie_data.clone()); + interpreter.set_global_metadata_field(GlobalMetadata::TrieDataSize, cur_trie_data.len().into()); + interpreter.run()?; + + // Finally, check that the hashes correspond. + let mpt_hash_receipt = KERNEL.global_labels["mpt_hash_receipt_trie"]; + interpreter.generation_state.registers.program_counter = mpt_hash_receipt; + interpreter.push(retdest); + interpreter.run()?; + assert_eq!( + interpreter.stack()[0], + U256::from(hex!( + "da46cdd329bfedace32da95f2b344d314bc6f55f027d65f9f4ac04ee425e1f98" + )) + ); + Ok(()) +} + +#[test] +fn test_bloom_two_logs() -> Result<()> { + // Tests the Bloom filter computation with two logs in one transaction. + + // address + let to = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x09, 0x5e, 0x7b, 0xae, 0xa6, 0xa6, 0xc7, 0xc4, 0xc2, + 0xdf, 0xeb, 0x97, 0x7e, 0xfa, 0xc3, 0x26, 0xaf, 0x55, 0x2d, 0x87, + ]; + + let retdest = 0xDEADBEEFu32.into(); + let logs_bloom = KERNEL.global_labels["logs_bloom"]; + + let initial_stack = vec![retdest]; + + // Set memory. + let logs = vec![ + 0.into(), // unused + to.into(), // address + 0.into(), // num_topics + 0.into(), // data_len, + 0.into(), // unused: rlp + to.into(), + 2.into(), // num_topics + 0x62.into(), + 0x63.into(), + 5.into(), + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xa1, + 0xb2, 0xc3, 0xd4, 0xe5, + ] + .into(), + ]; + let mut interpreter = Interpreter::new_with_kernel(logs_bloom, initial_stack); + interpreter.set_memory_segment(Segment::TxnBloom, vec![0.into(); 256]); // Initialize transaction Bloom filter. + interpreter.set_memory_segment(Segment::BlockBloom, vec![0.into(); 256]); // Initialize block Bloom filter. + interpreter.set_memory_segment(Segment::LogsData, logs); + interpreter.set_memory_segment(Segment::Logs, vec![0.into(), 4.into()]); + interpreter.set_global_metadata_field(GlobalMetadata::LogsLen, U256::from(2)); + interpreter.run()?; + + let loaded_bloom_bytes: Vec = interpreter + .get_memory_segment(Segment::TxnBloom) + .into_iter() + .map(|elt| elt.0[0] as u8) + .collect(); + + let expected = hex!("00000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000004000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000400000000000040000000000000000000000000002000000000000000000000000000").to_vec(); + + assert_eq!(expected, loaded_bloom_bytes); + Ok(()) +} + +pub fn logs_bloom_bytes_fn(logs_list: Vec<(Vec, Vec>)>) -> [u8; 256] { + // The first element of logs_list. + let mut bloom = [0_u8; 256]; + + for log in logs_list { + let cur_addr = log.0; + let topics = log.1; + + add_to_bloom(&mut bloom, &cur_addr); + for topic in topics { + add_to_bloom(&mut bloom, &topic); + } + } + bloom +} + +fn add_to_bloom(bloom: &mut [u8; 256], bloom_entry: &[u8]) { + let bloom_hash = keccak(bloom_entry).to_fixed_bytes(); + + for idx in 0..3 { + let bit_pair = u16::from_be_bytes(bloom_hash[2 * idx..2 * (idx + 1)].try_into().unwrap()); + let bit_to_set = 0x07FF - (bit_pair & 0x07FF); + let byte_index = bit_to_set / 8; + let bit_value = 1 << (7 - bit_to_set % 8); + bloom[byte_index as usize] |= bit_value; + } +} diff --git a/evm/src/generation/mpt.rs b/evm/src/generation/mpt.rs index 420984dd..8860024f 100644 --- a/evm/src/generation/mpt.rs +++ b/evm/src/generation/mpt.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; use std::ops::Deref; +use bytes::Bytes; use eth_trie_utils::nibbles::Nibbles; use eth_trie_utils::partial_trie::{HashedPartialTrie, PartialTrie}; -use ethereum_types::{BigEndianHash, H256, U256, U512}; +use ethereum_types::{Address, BigEndianHash, H256, U256, U512}; use keccak_hash::keccak; +use rlp::PayloadInfo; use rlp_derive::{RlpDecodable, RlpEncodable}; use crate::cpu::kernel::constants::trie_type::PartialTrieType; @@ -30,6 +32,34 @@ impl Default for AccountRlp { } } +#[derive(RlpEncodable, RlpDecodable, Debug)] +pub struct LegacyTransactionRlp { + pub nonce: U256, + pub gas_price: U256, + pub gas: U256, + pub to: Address, + pub value: U256, + pub data: Bytes, + pub v: U256, + pub r: U256, + pub s: U256, +} + +#[derive(RlpEncodable, RlpDecodable, Debug)] +pub struct LogRlp { + pub address: Address, + pub topics: Vec, + pub data: Bytes, +} + +#[derive(RlpEncodable, RlpDecodable, Debug)] +pub struct LegacyReceiptRlp { + pub status: bool, + pub cum_gas_used: U256, + pub bloom: Bytes, + pub logs: Vec, +} + pub(crate) fn all_mpt_prover_inputs_reversed(trie_inputs: &TrieInputs) -> Vec { let mut inputs = all_mpt_prover_inputs(trie_inputs); inputs.reverse(); @@ -60,11 +90,43 @@ pub(crate) fn all_mpt_prover_inputs(trie_inputs: &TrieInputs) -> Vec { rlp::decode_list(rlp) }); - mpt_prover_inputs(&trie_inputs.receipts_trie, &mut prover_inputs, &|_rlp| { - // TODO: Decode receipt RLP. - vec![] + mpt_prover_inputs(&trie_inputs.receipts_trie, &mut prover_inputs, &|rlp| { + let payload_info = PayloadInfo::from(rlp).unwrap(); + let decoded_receipt: LegacyReceiptRlp = rlp::decode(rlp).unwrap(); + let mut parsed_receipt = Vec::new(); + + parsed_receipt.push(payload_info.value_len.into()); // payload_len of the entire receipt + parsed_receipt.push((decoded_receipt.status as u8).into()); + parsed_receipt.push(decoded_receipt.cum_gas_used); + parsed_receipt.extend(decoded_receipt.bloom.iter().map(|byte| U256::from(*byte))); + let encoded_logs = rlp::encode_list(&decoded_receipt.logs); + let logs_payload_info = PayloadInfo::from(&encoded_logs).unwrap(); + parsed_receipt.push(logs_payload_info.value_len.into()); // payload_len of all the logs + parsed_receipt.push(decoded_receipt.logs.len().into()); + + for log in decoded_receipt.logs { + let encoded_log = rlp::encode(&log); + let log_payload_info = PayloadInfo::from(&encoded_log).unwrap(); + parsed_receipt.push(log_payload_info.value_len.into()); // payload of one log + parsed_receipt.push(U256::from_big_endian(&log.address.to_fixed_bytes())); + parsed_receipt.push(log.topics.len().into()); + parsed_receipt.extend(log.topics.iter().map(|topic| U256::from(topic.as_bytes()))); + parsed_receipt.push(log.data.len().into()); + parsed_receipt.extend(log.data.iter().map(|byte| U256::from(*byte))); + } + + parsed_receipt }); + // Temporary! The actual number of transactions in the trie cannot be known if the trie + // contains hash nodes. + let num_transactions = trie_inputs + .transactions_trie + .values() + .collect::>() + .len(); + prover_inputs.push(num_transactions.into()); + prover_inputs } diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index 054943b1..e56d635d 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -49,18 +49,25 @@ pub enum Segment { AccessedStorageKeys = 24, /// List of addresses that have called SELFDESTRUCT in the current transaction. SelfDestructList = 25, + /// Contains the bloom filter of a transaction. + TxnBloom = 26, + /// Contains the bloom filter of a block. + BlockBloom = 27, + /// List of log pointers pointing to the LogsData segment. + Logs = 28, + LogsData = 29, /// Journal of state changes. List of pointers to `JournalData`. Length in `GlobalMetadata`. - Journal = 26, - JournalData = 27, - JournalCheckpoints = 28, + Journal = 30, + JournalData = 31, + JournalCheckpoints = 32, /// List of addresses that have been touched in the current transaction. - TouchedAddresses = 29, + TouchedAddresses = 33, /// List of checkpoints for the current context. Length in `ContextMetadata`. - ContextCheckpoints = 30, + ContextCheckpoints = 34, } impl Segment { - pub(crate) const COUNT: usize = 31; + pub(crate) const COUNT: usize = 35; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -90,6 +97,10 @@ impl Segment { Self::AccessedAddresses, Self::AccessedStorageKeys, Self::SelfDestructList, + Self::TxnBloom, + Self::BlockBloom, + Self::Logs, + Self::LogsData, Self::Journal, Self::JournalData, Self::JournalCheckpoints, @@ -127,6 +138,10 @@ impl Segment { Segment::AccessedAddresses => "SEGMENT_ACCESSED_ADDRESSES", Segment::AccessedStorageKeys => "SEGMENT_ACCESSED_STORAGE_KEYS", Segment::SelfDestructList => "SEGMENT_SELFDESTRUCT_LIST", + Segment::TxnBloom => "SEGMENT_TXN_BLOOM", + Segment::BlockBloom => "SEGMENT_BLOCK_BLOOM", + Segment::Logs => "SEGMENT_LOGS", + Segment::LogsData => "SEGMENT_LOGS_DATA", Segment::Journal => "SEGMENT_JOURNAL", Segment::JournalData => "SEGMENT_JOURNAL_DATA", Segment::JournalCheckpoints => "SEGMENT_JOURNAL_CHECKPOINTS", @@ -164,6 +179,10 @@ impl Segment { Segment::AccessedAddresses => 256, Segment::AccessedStorageKeys => 256, Segment::SelfDestructList => 256, + Segment::TxnBloom => 8, + Segment::BlockBloom => 8, + Segment::Logs => 256, + Segment::LogsData => 256, Segment::Journal => 256, Segment::JournalData => 256, Segment::JournalCheckpoints => 256, diff --git a/evm/src/proof.rs b/evm/src/proof.rs index cd73d847..ca6dbbfc 100644 --- a/evm/src/proof.rs +++ b/evm/src/proof.rs @@ -71,6 +71,7 @@ pub struct BlockMetadata { pub block_gaslimit: U256, pub block_chain_id: U256, pub block_base_fee: U256, + pub block_bloom: [U256; 8], } /// Memory values which are public. @@ -112,6 +113,7 @@ impl PublicValuesTarget { block_gaslimit, block_chain_id, block_base_fee, + block_bloom, } = self.block_metadata; buffer.write_target_vec(&block_beneficiary)?; @@ -121,6 +123,7 @@ impl PublicValuesTarget { buffer.write_target(block_gaslimit)?; buffer.write_target(block_chain_id)?; buffer.write_target_vec(&block_base_fee)?; + buffer.write_target_vec(&block_bloom)?; Ok(()) } @@ -146,6 +149,7 @@ impl PublicValuesTarget { block_gaslimit: buffer.read_target()?, block_chain_id: buffer.read_target()?, block_base_fee: buffer.read_target_vec()?.try_into().unwrap(), + block_bloom: buffer.read_target_vec()?.try_into().unwrap(), }; Ok(Self { @@ -265,10 +269,11 @@ pub struct BlockMetadataTarget { pub block_gaslimit: Target, pub block_chain_id: Target, pub block_base_fee: [Target; 2], + pub block_bloom: [Target; 64], } impl BlockMetadataTarget { - const SIZE: usize = 12; + const SIZE: usize = 76; pub fn from_public_inputs(pis: &[Target]) -> Self { let block_beneficiary = pis[0..5].try_into().unwrap(); @@ -278,6 +283,7 @@ impl BlockMetadataTarget { let block_gaslimit = pis[8]; let block_chain_id = pis[9]; let block_base_fee = pis[10..12].try_into().unwrap(); + let block_bloom = pis[12..76].try_into().unwrap(); Self { block_beneficiary, @@ -287,6 +293,7 @@ impl BlockMetadataTarget { block_gaslimit, block_chain_id, block_base_fee, + block_bloom, } } @@ -312,6 +319,9 @@ impl BlockMetadataTarget { block_base_fee: core::array::from_fn(|i| { builder.select(condition, bm0.block_base_fee[i], bm1.block_base_fee[i]) }), + block_bloom: core::array::from_fn(|i| { + builder.select(condition, bm0.block_bloom[i], bm1.block_bloom[i]) + }), } } @@ -331,6 +341,9 @@ impl BlockMetadataTarget { for i in 0..2 { builder.connect(bm0.block_base_fee[i], bm1.block_base_fee[i]) } + for i in 0..64 { + builder.connect(bm0.block_bloom[i], bm1.block_bloom[i]) + } } } diff --git a/evm/src/recursive_verifier.rs b/evm/src/recursive_verifier.rs index b0f382f0..296cbfb2 100644 --- a/evm/src/recursive_verifier.rs +++ b/evm/src/recursive_verifier.rs @@ -683,6 +683,7 @@ pub(crate) fn add_virtual_block_metadata, const D: let block_gaslimit = builder.add_virtual_public_input(); let block_chain_id = builder.add_virtual_public_input(); let block_base_fee = builder.add_virtual_public_input_arr(); + let block_bloom = builder.add_virtual_public_input_arr(); BlockMetadataTarget { block_beneficiary, block_timestamp, @@ -691,6 +692,7 @@ pub(crate) fn add_virtual_block_metadata, const D: block_gaslimit, block_chain_id, block_base_fee, + block_bloom, } } @@ -892,4 +894,9 @@ pub(crate) fn set_block_metadata_target( block_metadata_target.block_base_fee[1], F::from_canonical_u32((block_metadata.block_base_fee.as_u64() >> 32) as u32), ); + let mut block_bloom_limbs = [F::ZERO; 64]; + for (i, limbs) in block_bloom_limbs.chunks_exact_mut(8).enumerate() { + limbs.copy_from_slice(&u256_limbs(block_metadata.block_bloom[i])); + } + witness.set_target_arr(&block_metadata_target.block_bloom, &block_bloom_limbs); } diff --git a/evm/src/witness/memory.rs b/evm/src/witness/memory.rs index 127aac33..62e6a2fe 100644 --- a/evm/src/witness/memory.rs +++ b/evm/src/witness/memory.rs @@ -202,12 +202,20 @@ impl Default for MemoryState { } } -#[derive(Clone, Default, Debug)] +#[derive(Clone, Debug)] pub(crate) struct MemoryContextState { /// The content of each memory segment. pub(crate) segments: [MemorySegmentState; Segment::COUNT], } +impl Default for MemoryContextState { + fn default() -> Self { + Self { + segments: std::array::from_fn(|_| MemorySegmentState::default()), + } + } +} + #[derive(Clone, Default, Debug)] pub(crate) struct MemorySegmentState { pub(crate) content: Vec, diff --git a/evm/tests/add11_yml.rs b/evm/tests/add11_yml.rs index f8b02a10..6ed5d155 100644 --- a/evm/tests/add11_yml.rs +++ b/evm/tests/add11_yml.rs @@ -85,6 +85,7 @@ fn add11_yml() -> anyhow::Result<()> { block_gaslimit: 0xff112233u32.into(), block_chain_id: 1.into(), block_base_fee: 0xa.into(), + block_bloom: [0.into(); 8], }; let mut contract_code = HashMap::new(); diff --git a/evm/tests/log_opcode.rs b/evm/tests/log_opcode.rs new file mode 100644 index 00000000..160e0960 --- /dev/null +++ b/evm/tests/log_opcode.rs @@ -0,0 +1,507 @@ +#![allow(clippy::upper_case_acronyms)] + +use std::collections::HashMap; +use std::str::FromStr; +use std::time::Duration; + +use bytes::Bytes; +use env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; +use eth_trie_utils::nibbles::Nibbles; +use eth_trie_utils::partial_trie::{HashedPartialTrie, PartialTrie}; +use ethereum_types::Address; +use hex_literal::hex; +use keccak_hash::keccak; +use plonky2::field::goldilocks_field::GoldilocksField; +use plonky2::plonk::config::PoseidonGoldilocksConfig; +use plonky2::util::timing::TimingTree; +use plonky2_evm::all_stark::AllStark; +use plonky2_evm::config::StarkConfig; +use plonky2_evm::generation::mpt::{AccountRlp, LegacyReceiptRlp, LegacyTransactionRlp, LogRlp}; +use plonky2_evm::generation::{GenerationInputs, TrieInputs}; +use plonky2_evm::proof::{BlockMetadata, TrieRoots}; +use plonky2_evm::prover::prove; +use plonky2_evm::verifier::verify_proof; +use plonky2_evm::Node; + +type F = GoldilocksField; +const D: usize = 2; +type C = PoseidonGoldilocksConfig; + +/// Variation of `add11_yml` testing LOG opcodes. +#[test] +#[ignore] // Too slow to run on CI. +fn test_log_opcodes() -> anyhow::Result<()> { + init_logger(); + + let all_stark = AllStark::::default(); + let config = StarkConfig::standard_fast_config(); + + let beneficiary = hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"); + let sender = hex!("af1276cbb260bb13deddb4209ae99ae6e497f446"); + // Private key: DCDFF53B4F013DBCDC717F89FE3BF4D8B10512AAE282B48E01D7530470382701 + let to = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"); + + let beneficiary_state_key = keccak(beneficiary); + let sender_state_key = keccak(sender); + let to_hashed = keccak(to); + + let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); + let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); + let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); + + // For the first code transaction code, we consider two LOG opcodes. The first deals with 0 topics and empty data. The second deals with two topics, and data of length 5, stored in memory. + let code = [ + 0x64, 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0x60, 0x0, 0x52, // MSTORE(0x0, 0xA1B2C3D4E5) + 0x60, 0x0, 0x60, 0x0, 0xA0, // LOG0(0x0, 0x0) + 0x60, 99, 0x60, 98, 0x60, 5, 0x60, 27, 0xA2, // LOG2(27, 5, 98, 99) + 0x00, + ]; + println!("contract: {:02x?}", code); + let code_gas = 3 + 3 + 3 // PUSHs and MSTORE + + 3 + 3 + 375 // PUSHs and LOG0 + + 3 + 3 + 3 + 3 + 375 + 375*2 + 8*5 + 3// PUSHs, LOG2 and memory expansion + ; + let gas_used = 21_000 + code_gas; + + let code_hash = keccak(code); + + // Set accounts before the transaction. + let beneficiary_account_before = AccountRlp { + nonce: 1.into(), + ..AccountRlp::default() + }; + + let sender_balance_before = 5000000000000000u64; + let sender_account_before = AccountRlp { + balance: sender_balance_before.into(), + ..AccountRlp::default() + }; + let to_account_before = AccountRlp { + balance: 9000000000u64.into(), + code_hash, + ..AccountRlp::default() + }; + + // Initialize the state trie with three accounts. + let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + state_trie_before.insert( + beneficiary_nibbles, + rlp::encode(&beneficiary_account_before).to_vec(), + ); + state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); + state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + + // We now add two receipts with logs and data. This updates the receipt trie as well. + let log_0 = LogRlp { + address: hex!("7ef66b77759e12Caf3dDB3E4AFF524E577C59D8D").into(), + topics: vec![ + hex!("8a22ee899102a366ac8ad0495127319cb1ff2403cfae855f83a89cda1266674d").into(), + hex!("000000000000000000000000000000000000000000000000000000000000002a").into(), + hex!("0000000000000000000000000000000000000000000000000000000000bd9fe6").into(), + ], + data: hex!("f7af1cc94b1aef2e0fa15f1b4baefa86eb60e78fa4bd082372a0a446d197fb58") + .to_vec() + .into(), + }; + + let receipt_0 = LegacyReceiptRlp { + status: true, + cum_gas_used: 0x016e5bu64.into(), + bloom: hex!("00000000000000000000000000000000000000000000000000800000000000000040000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000080008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000020000000000008000000000000000000000000").to_vec().into(), + logs: vec![log_0], + }; + + // Insert the first receipt into the initial receipt trie. + let mut receipts_trie = HashedPartialTrie::from(Node::Empty); + receipts_trie.insert( + Nibbles::from_str("0x1337").unwrap(), + rlp::encode(&receipt_0).to_vec(), + ); + + let tries_before = TrieInputs { + state_trie: state_trie_before, + transactions_trie: Node::Empty.into(), + receipts_trie: receipts_trie.clone(), + storage_tries: vec![(to_hashed, Node::Empty.into())], + }; + + // Prove a transaction which carries out two LOG opcodes. + let txn_gas_price = 10; + let txn = hex!("f860800a830186a094095e7baea6a6c7c4c2dfeb977efac326af552d87808026a0c3040cb042c541f9440771879b6bbf3f91464b265431de87eea1ec3206350eb8a046f5f3d06b8816f19f24ee919fd84bfb736db71df10a72fba4495f479e96f678"); + + let block_metadata = BlockMetadata { + block_beneficiary: Address::from(beneficiary), + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_gaslimit: 0xffffffffu32.into(), + block_chain_id: 1.into(), + block_base_fee: 0xa.into(), + block_bloom: [0.into(); 8], + }; + + let mut contract_code = HashMap::new(); + contract_code.insert(keccak(vec![]), vec![]); + contract_code.insert(code_hash, code.to_vec()); + + // Update the state and receipt tries after the transaction, so that we have the correct expected tries: + // Update accounts + let beneficiary_account_after = AccountRlp { + nonce: 1.into(), + ..AccountRlp::default() + }; + + let sender_balance_after = sender_balance_before - gas_used * txn_gas_price; + let sender_account_after = AccountRlp { + balance: sender_balance_after.into(), + nonce: 1.into(), + ..AccountRlp::default() + }; + let to_account_after = AccountRlp { + balance: 9000000000u64.into(), + code_hash, + ..AccountRlp::default() + }; + + // Update the receipt trie. + let first_log = LogRlp { + address: to.into(), + topics: vec![], + data: Bytes::new(), + }; + + let second_log = LogRlp { + address: to.into(), + topics: vec![ + hex!("0000000000000000000000000000000000000000000000000000000000000062").into(), // dec: 98 + hex!("0000000000000000000000000000000000000000000000000000000000000063").into(), // dec: 99 + ], + data: hex!("a1b2c3d4e5").to_vec().into(), + }; + + let receipt = LegacyReceiptRlp { + status: true, + cum_gas_used: gas_used.into(), + bloom: hex!("00000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000004000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000400000000000040000000000000000000000000002000000000000000000000000000").to_vec().into(), + logs: vec![first_log, second_log], + }; + + let receipt_nibbles = Nibbles::from_str("0x80").unwrap(); // RLP(0) = 0x80 + + receipts_trie.insert(receipt_nibbles, rlp::encode(&receipt).to_vec()); + + // Update the state trie. + let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); + expected_state_trie_after.insert( + beneficiary_nibbles, + rlp::encode(&beneficiary_account_after).to_vec(), + ); + expected_state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); + expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + + let trie_roots_after = TrieRoots { + state_root: expected_state_trie_after.hash(), + transactions_root: HashedPartialTrie::from(Node::Empty).hash(), + receipts_root: receipts_trie.hash(), + }; + let inputs = GenerationInputs { + signed_txns: vec![txn.to_vec()], + tries: tries_before, + trie_roots_after, + contract_code, + block_metadata, + addresses: vec![], + }; + + let mut timing = TimingTree::new("prove", log::Level::Debug); + let proof = prove::(&all_stark, &config, inputs, &mut timing)?; + timing.filter(Duration::from_millis(100)).print(); + + // Assert that the proof leads to the correct state and receipt roots. + assert_eq!( + proof.public_values.trie_roots_after.state_root, + expected_state_trie_after.hash() + ); + + assert_eq!( + proof.public_values.trie_roots_after.receipts_root, + receipts_trie.hash() + ); + + verify_proof(&all_stark, proof, &config) +} + +/// Values taken from the block 1000000 of Goerli: https://goerli.etherscan.io/txs?block=1000000 +#[test] +fn test_txn_and_receipt_trie_hash() -> anyhow::Result<()> { + // This test checks that inserting into the transaction and receipt `HashedPartialTrie`s works as expected. + let mut example_txn_trie = HashedPartialTrie::from(Node::Empty); + + // We consider two transactions, with one log each. + let transaction_0 = LegacyTransactionRlp { + nonce: 157823u64.into(), + gas_price: 1000000000u64.into(), + gas: 250000u64.into(), + to: hex!("7ef66b77759e12Caf3dDB3E4AFF524E577C59D8D").into(), + value: 0u64.into(), + data: hex!("e9c6c176000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000bd9fe6f7af1cc94b1aef2e0fa15f1b4baefa86eb60e78fa4bd082372a0a446d197fb58") + .to_vec() + .into(), + v: 0x1c.into(), + r: hex!("d0eeac4841caf7a894dd79e6e633efc2380553cdf8b786d1aa0b8a8dee0266f4").into(), + s: hex!("740710eed9696c663510b7fb71a553112551121595a54ec6d2ec0afcec72a973").into(), + }; + + // Insert the first transaction into the transaction trie. + example_txn_trie.insert( + Nibbles::from_str("0x80").unwrap(), // RLP(0) = 0x80 + rlp::encode(&transaction_0).to_vec(), + ); + + let transaction_1 = LegacyTransactionRlp { + nonce: 157824u64.into(), + gas_price: 1000000000u64.into(), + gas: 250000u64.into(), + to: hex!("7ef66b77759e12Caf3dDB3E4AFF524E577C59D8D").into(), + value: 0u64.into(), + data: hex!("e9c6c176000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000004920eaa814f7df6a2203dc0e472e8828be95957c6b329fee8e2b1bb6f044c1eb4fc243") + .to_vec() + .into(), + v: 0x1b.into(), + r: hex!("a3ff39967683fc684dc7b857d6f62723e78804a14b091a058ad95cc1b8a0281f").into(), + s: hex!("51b156e05f21f499fa1ae47ebf536b15a237208f1d4a62e33956b6b03cf47742").into(), + }; + + // Insert the second transaction into the transaction trie. + example_txn_trie.insert( + Nibbles::from_str("0x01").unwrap(), + rlp::encode(&transaction_1).to_vec(), + ); + + // Receipts: + let mut example_receipt_trie = HashedPartialTrie::from(Node::Empty); + + let log_0 = LogRlp { + address: hex!("7ef66b77759e12Caf3dDB3E4AFF524E577C59D8D").into(), + topics: vec![ + hex!("8a22ee899102a366ac8ad0495127319cb1ff2403cfae855f83a89cda1266674d").into(), + hex!("000000000000000000000000000000000000000000000000000000000000002a").into(), + hex!("0000000000000000000000000000000000000000000000000000000000bd9fe6").into(), + ], + data: hex!("f7af1cc94b1aef2e0fa15f1b4baefa86eb60e78fa4bd082372a0a446d197fb58") + .to_vec() + .into(), + }; + + let receipt_0 = LegacyReceiptRlp { + status: true, + cum_gas_used: 0x016e5bu64.into(), + bloom: hex!("00000000000000000000000000000000000000000000000000800000000000000040000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000080008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000020000000000008000000000000000000000000").to_vec().into(), + logs: vec![log_0], + }; + + // Insert the first receipt into the receipt trie. + example_receipt_trie.insert( + Nibbles::from_str("0x80").unwrap(), // RLP(0) is 0x80 + rlp::encode(&receipt_0).to_vec(), + ); + + let log_1 = LogRlp { + address: hex!("7ef66b77759e12Caf3dDB3E4AFF524E577C59D8D").into(), + topics: vec![ + hex!("8a22ee899102a366ac8ad0495127319cb1ff2403cfae855f83a89cda1266674d").into(), + hex!("0000000000000000000000000000000000000000000000000000000000000004").into(), + hex!("00000000000000000000000000000000000000000000000000000000004920ea").into(), + ], + data: hex!("a814f7df6a2203dc0e472e8828be95957c6b329fee8e2b1bb6f044c1eb4fc243") + .to_vec() + .into(), + }; + + let receipt_1 = LegacyReceiptRlp { + status: true, + cum_gas_used: 0x02dcb6u64.into(), + bloom: hex!("00000000000000000000000000000000000000000000000000800000000000000040000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000008000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000400000000000000000000000000000002000040000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000008000000000000000000000000").to_vec().into(), + logs: vec![log_1], + }; + + // Insert the second receipt into the receipt trie. + example_receipt_trie.insert( + Nibbles::from_str("0x01").unwrap(), + rlp::encode(&receipt_1).to_vec(), + ); + + // Check that the trie hashes are correct. + assert_eq!( + example_txn_trie.hash(), + hex!("3ab7120d12e1fc07303508542602beb7eecfe8f262b83fd71eefe7d6205242ce").into() + ); + + assert_eq!( + example_receipt_trie.hash(), + hex!("da46cdd329bfedace32da95f2b344d314bc6f55f027d65f9f4ac04ee425e1f98").into() + ); + + Ok(()) +} + +#[test] +#[ignore] // Too slow to run on CI. +fn test_two_txn() -> anyhow::Result<()> { + init_logger(); + + let all_stark = AllStark::::default(); + let config = StarkConfig::standard_fast_config(); + + let beneficiary = hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"); + let sender = hex!("af1276cbb260bb13deddb4209ae99ae6e497f446"); + // Private key: DCDFF53B4F013DBCDC717F89FE3BF4D8B10512AAE282B48E01D7530470382701 + let to = hex!("095e7baea6a6c7c4c2dfeb977efac326af552d87"); + + let beneficiary_state_key = keccak(beneficiary); + let sender_state_key = keccak(sender); + let to_hashed = keccak(to); + + let beneficiary_nibbles = Nibbles::from_bytes_be(beneficiary_state_key.as_bytes()).unwrap(); + let sender_nibbles = Nibbles::from_bytes_be(sender_state_key.as_bytes()).unwrap(); + let to_nibbles = Nibbles::from_bytes_be(to_hashed.as_bytes()).unwrap(); + + // Set accounts before the transaction. + let beneficiary_account_before = AccountRlp { + nonce: 1.into(), + ..AccountRlp::default() + }; + + let sender_balance_before = 50000000000000000u64; + let sender_account_before = AccountRlp { + balance: sender_balance_before.into(), + ..AccountRlp::default() + }; + let to_account_before = AccountRlp { + ..AccountRlp::default() + }; + + // Initialize the state trie with three accounts. + let mut state_trie_before = HashedPartialTrie::from(Node::Empty); + state_trie_before.insert( + beneficiary_nibbles, + rlp::encode(&beneficiary_account_before).to_vec(), + ); + state_trie_before.insert(sender_nibbles, rlp::encode(&sender_account_before).to_vec()); + state_trie_before.insert(to_nibbles, rlp::encode(&to_account_before).to_vec()); + + let tries_before = TrieInputs { + state_trie: state_trie_before, + transactions_trie: Node::Empty.into(), + receipts_trie: Node::Empty.into(), + storage_tries: vec![(to_hashed, Node::Empty.into())], + }; + + // Prove two simple transfers. + let gas_price = 10; + let txn_value = 0x11c37937e08000u64; + let txn_0 = hex!("f866800a82520894095e7baea6a6c7c4c2dfeb977efac326af552d878711c37937e080008026a01fcd0ce88ac7600698a771f206df24b70e67981b6f107bd7c1c24ea94f113bcba00d87cc5c7afc2988e4ff200b5a0c7016b0d5498bbc692065ca983fcbbfe02555"); + let txn_1 = hex!("f866010a82520894095e7baea6a6c7c4c2dfeb977efac326af552d878711c37937e080008026a0d8123f5f537bd3a67283f67eb136f7accdfc4ef012cfbfd3fb1d0ac7fd01b96fa004666d9feef90a1eb568570374dd19977d4da231b289d769e6f95105c06fd672"); + + let block_metadata = BlockMetadata { + block_beneficiary: Address::from(beneficiary), + block_timestamp: 0x03e8.into(), + block_number: 1.into(), + block_difficulty: 0x020000.into(), + block_gaslimit: 0xffffffffu32.into(), + block_chain_id: 1.into(), + block_base_fee: 0xa.into(), + block_bloom: [0.into(); 8], + }; + + let mut contract_code = HashMap::new(); + contract_code.insert(keccak(vec![]), vec![]); + + // Update accounts + let beneficiary_account_after = AccountRlp { + nonce: 1.into(), + ..AccountRlp::default() + }; + + let sender_balance_after = sender_balance_before - gas_price * 21000 * 2 - txn_value * 2; + let sender_account_after = AccountRlp { + balance: sender_balance_after.into(), + nonce: 2.into(), + ..AccountRlp::default() + }; + let to_account_after = AccountRlp { + balance: (2 * txn_value).into(), + ..AccountRlp::default() + }; + + // Update the state trie. + let mut expected_state_trie_after = HashedPartialTrie::from(Node::Empty); + expected_state_trie_after.insert( + beneficiary_nibbles, + rlp::encode(&beneficiary_account_after).to_vec(), + ); + expected_state_trie_after.insert(sender_nibbles, rlp::encode(&sender_account_after).to_vec()); + expected_state_trie_after.insert(to_nibbles, rlp::encode(&to_account_after).to_vec()); + + // Compute new receipt trie. + let mut receipts_trie = HashedPartialTrie::from(Node::Empty); + + let receipt_0 = LegacyReceiptRlp { + status: true, + cum_gas_used: 21000u64.into(), + bloom: [0x00; 256].to_vec().into(), + logs: vec![], + }; + + let receipt_1 = LegacyReceiptRlp { + status: true, + cum_gas_used: 42000u64.into(), + bloom: [0x00; 256].to_vec().into(), + logs: vec![], + }; + + receipts_trie.insert( + Nibbles::from_str("0x80").unwrap(), + rlp::encode(&receipt_0).to_vec(), + ); + + receipts_trie.insert( + Nibbles::from_str("0x01").unwrap(), + rlp::encode(&receipt_1).to_vec(), + ); + + let trie_roots_after = TrieRoots { + state_root: expected_state_trie_after.hash(), + transactions_root: HashedPartialTrie::from(Node::Empty).hash(), + receipts_root: receipts_trie.hash(), + }; + let inputs = GenerationInputs { + signed_txns: vec![txn_0.to_vec(), txn_1.to_vec()], + tries: tries_before, + trie_roots_after, + contract_code, + block_metadata, + addresses: vec![], + }; + + let mut timing = TimingTree::new("prove", log::Level::Debug); + let proof = prove::(&all_stark, &config, inputs, &mut timing)?; + timing.filter(Duration::from_millis(100)).print(); + + // Assert trie roots. + assert_eq!( + proof.public_values.trie_roots_after.state_root, + expected_state_trie_after.hash() + ); + + assert_eq!( + proof.public_values.trie_roots_after.receipts_root, + receipts_trie.hash() + ); + + verify_proof(&all_stark, proof, &config) +} + +fn init_logger() { + let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); +} diff --git a/evm/tests/simple_transfer.rs b/evm/tests/simple_transfer.rs index 13303adc..1666d2f9 100644 --- a/evm/tests/simple_transfer.rs +++ b/evm/tests/simple_transfer.rs @@ -73,6 +73,7 @@ fn test_simple_transfer() -> anyhow::Result<()> { block_gaslimit: 0xff112233u32.into(), block_chain_id: 1.into(), block_base_fee: 0xa.into(), + block_bloom: [0.into(); 8], }; let mut contract_code = HashMap::new();