From 36187937060d467eb27d0b37876d21e10c8e1e22 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 1 Aug 2022 11:00:12 -0700 Subject: [PATCH 1/4] Test for parsing type 0 transactions I made a couple related changes to `Interpreter`: - Only enforce the `JUMPDEST` rule if we're in kernel mode. - Convenience methods for dealing with the RLP and txn field segments of memory. --- evm/src/cpu/kernel/interpreter.rs | 43 ++++++++++++------ evm/src/cpu/kernel/tests/mod.rs | 1 + evm/src/cpu/kernel/tests/rlp.rs | 39 ++++++---------- evm/src/cpu/kernel/tests/type_0_txn.rs | 63 ++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 38 deletions(-) create mode 100644 evm/src/cpu/kernel/tests/type_0_txn.rs diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 6a9d31fa..3b753f0a 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -7,11 +7,12 @@ use keccak_hash::keccak; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::assembler::Kernel; use crate::cpu::kernel::prover_input::ProverInputFn; +use crate::cpu::kernel::txn_fields::NormalizedTxnField; use crate::generation::memory::{MemoryContextState, MemorySegmentState}; use crate::memory::segments::Segment; /// Halt interpreter execution whenever a jump to this offset is done. -const HALT_OFFSET: usize = 0xdeadbeef; +const DEFAULT_HALT_OFFSET: usize = 0xdeadbeef; #[derive(Debug)] pub(crate) struct InterpreterMemory { @@ -49,12 +50,14 @@ impl InterpreterMemory { } pub struct Interpreter<'a> { + kernel_mode: bool, jumpdests: Vec, offset: usize, context: usize, pub(crate) memory: InterpreterMemory, prover_inputs_map: &'a HashMap, prover_inputs: Vec, + pub(crate) halt_offsets: Vec, running: bool, } @@ -100,12 +103,14 @@ impl<'a> Interpreter<'a> { prover_inputs: &'a HashMap, ) -> Self { Self { + kernel_mode: true, jumpdests: find_jumpdests(code), offset: initial_offset, memory: InterpreterMemory::with_code_and_stack(code, initial_stack), prover_inputs_map: prover_inputs, prover_inputs: Vec::new(), context: 0, + halt_offsets: vec![DEFAULT_HALT_OFFSET], running: true, } } @@ -128,6 +133,15 @@ impl<'a> Interpreter<'a> { .collect::>() } + pub(crate) fn get_txn_field(&self, field: NormalizedTxnField) -> U256 { + self.memory.context_memory[0].segments[Segment::TxnFields as usize].content[field as usize] + } + + pub(crate) fn set_rlp_memory(&mut self, rlp: Vec) { + self.memory.context_memory[0].segments[Segment::RlpRaw as usize].content = + rlp.into_iter().map(U256::from).collect(); + } + fn incr(&mut self, n: usize) { self.offset += n; } @@ -435,24 +449,27 @@ impl<'a> Interpreter<'a> { fn run_jump(&mut self) { let x = self.pop().as_usize(); - self.offset = x; - if self.offset == HALT_OFFSET { - self.running = false; - } else if self.jumpdests.binary_search(&self.offset).is_err() { - panic!("Destination is not a JUMPDEST."); - } + self.jump_to(x); } fn run_jumpi(&mut self) { let x = self.pop().as_usize(); let b = self.pop(); if !b.is_zero() { - self.offset = x; - if self.offset == HALT_OFFSET { - self.running = false; - } else if self.jumpdests.binary_search(&self.offset).is_err() { - panic!("Destination is not a JUMPDEST."); - } + self.jump_to(x); + } + } + + fn jump_to(&mut self, offset: usize) { + // The JUMPDEST rule is not enforced in kernel mode. + if !self.kernel_mode && self.jumpdests.binary_search(&offset).is_err() { + panic!("Destination is not a JUMPDEST."); + } + + self.offset = offset; + + if self.halt_offsets.contains(&offset) { + self.running = false; } } diff --git a/evm/src/cpu/kernel/tests/mod.rs b/evm/src/cpu/kernel/tests/mod.rs index 73eb3ada..e912388b 100644 --- a/evm/src/cpu/kernel/tests/mod.rs +++ b/evm/src/cpu/kernel/tests/mod.rs @@ -2,6 +2,7 @@ mod curve_ops; mod ecrecover; mod exp; mod rlp; +mod type_0_txn; use std::str::FromStr; diff --git a/evm/src/cpu/kernel/tests/rlp.rs b/evm/src/cpu/kernel/tests/rlp.rs index e74213da..fd790dcb 100644 --- a/evm/src/cpu/kernel/tests/rlp.rs +++ b/evm/src/cpu/kernel/tests/rlp.rs @@ -15,7 +15,7 @@ fn test_decode_rlp_string_len_short() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(decode_rlp_string_len, initial_stack); // A couple dummy bytes, followed by "0x70" which is its own encoding. - set_rlp_memory(&mut interpreter, vec![123, 234, 0x70]); + interpreter.set_rlp_memory(vec![123, 234, 0x70]); interpreter.run()?; let expected_stack = vec![1.into(), 2.into()]; // len, pos @@ -32,7 +32,7 @@ fn test_decode_rlp_string_len_medium() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(decode_rlp_string_len, initial_stack); // A couple dummy bytes, followed by the RLP encoding of "1 2 3 4 5". - set_rlp_memory(&mut interpreter, vec![123, 234, 0x85, 1, 2, 3, 4, 5]); + interpreter.set_rlp_memory(vec![123, 234, 0x85, 1, 2, 3, 4, 5]); interpreter.run()?; let expected_stack = vec![5.into(), 3.into()]; // len, pos @@ -49,14 +49,11 @@ fn test_decode_rlp_string_len_long() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(decode_rlp_string_len, initial_stack); // The RLP encoding of the string "1 2 3 ... 56". - set_rlp_memory( - &mut interpreter, - vec![ - 123, 234, 0xb8, 56, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - ], - ); + interpreter.set_rlp_memory(vec![ + 123, 234, 0xb8, 56, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + ]); interpreter.run()?; let expected_stack = vec![56.into(), 4.into()]; // len, pos @@ -73,7 +70,7 @@ fn test_decode_rlp_list_len_short() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(decode_rlp_list_len, initial_stack); // The RLP encoding of [1, 2, [3, 4]]. - set_rlp_memory(&mut interpreter, vec![0xc5, 1, 2, 0xc2, 3, 4]); + interpreter.set_rlp_memory(vec![0xc5, 1, 2, 0xc2, 3, 4]); interpreter.run()?; let expected_stack = vec![5.into(), 1.into()]; // len, pos @@ -90,14 +87,11 @@ fn test_decode_rlp_list_len_long() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(decode_rlp_list_len, initial_stack); // The RLP encoding of [1, ..., 56]. - set_rlp_memory( - &mut interpreter, - vec![ - 0xf8, 56, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - ], - ); + interpreter.set_rlp_memory(vec![ + 0xf8, 56, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + ]); interpreter.run()?; let expected_stack = vec![56.into(), 2.into()]; // len, pos @@ -114,7 +108,7 @@ fn test_decode_rlp_scalar() -> Result<()> { let mut interpreter = Interpreter::new_with_kernel(decode_rlp_scalar, initial_stack); // The RLP encoding of "12 34 56". - set_rlp_memory(&mut interpreter, vec![0x83, 0x12, 0x34, 0x56]); + interpreter.set_rlp_memory(vec![0x83, 0x12, 0x34, 0x56]); interpreter.run()?; let expected_stack = vec![0x123456.into(), 4.into()]; // scalar, pos @@ -122,8 +116,3 @@ fn test_decode_rlp_scalar() -> Result<()> { Ok(()) } - -fn set_rlp_memory(interpreter: &mut Interpreter, rlp: Vec) { - interpreter.memory.context_memory[0].segments[Segment::RlpRaw as usize].content = - rlp.into_iter().map(U256::from).collect(); -} diff --git a/evm/src/cpu/kernel/tests/type_0_txn.rs b/evm/src/cpu/kernel/tests/type_0_txn.rs new file mode 100644 index 00000000..437795e9 --- /dev/null +++ b/evm/src/cpu/kernel/tests/type_0_txn.rs @@ -0,0 +1,63 @@ +use std::str::FromStr; + +use anyhow::Result; +use ethereum_types::U256; +use hex_literal::hex; +use NormalizedTxnField::*; + +use crate::cpu::kernel::aggregator::KERNEL; +use crate::cpu::kernel::interpreter::Interpreter; +use crate::cpu::kernel::tests::rlp::set_rlp_memory; +use crate::cpu::kernel::txn_fields::NormalizedTxnField; + +#[test] +fn process_type_0_txn() -> Result<()> { + let process_type_0_txn = KERNEL.global_labels["process_type_0_txn"]; + let process_normalized_txn = KERNEL.global_labels["process_normalized_txn"]; + + let mut interpreter = Interpreter::new_with_kernel(process_type_0_txn, vec![]); + + // When we reach process_normalized_txn, we're done with parsing and normalizing. + // Processing normalized transactions is outside the scope of this test. + interpreter.halt_offsets.push(process_normalized_txn); + + // Generated with py-evm: + // unsigned_txn = chain.create_unsigned_transaction( + // nonce=5, + // gas_price=10, + // gas=22_000, + // to=constants.ZERO_ADDRESS, + // value=100, + // data=b'\x42\x42', + // ) + // my_txn = unsigned_txn.as_signed_transaction(my_sk) + // rlp.encode(my_txn) + interpreter.set_rlp_memory(hex!("f861050a8255f0940000000000000000000000000000000000000000648242421ca07c5c61ed975ebd286f6b027b8c504842e50a47d318e1e801719dd744fe93e6c6a01e7b5119b57dd54e175ff2f055c91f3ab1b53eba0b2c184f347cdff0e745aca2").to_vec()); + + interpreter.run()?; + + assert_eq!(interpreter.get_txn_field(ChainIdPresent), 0.into()); + assert_eq!(interpreter.get_txn_field(ChainId), 0.into()); + assert_eq!(interpreter.get_txn_field(Nonce), 5.into()); + assert_eq!(interpreter.get_txn_field(MaxPriorityFeePerGas), 10.into()); + assert_eq!(interpreter.get_txn_field(MaxPriorityFeePerGas), 10.into()); + assert_eq!(interpreter.get_txn_field(MaxFeePerGas), 10.into()); + assert_eq!(interpreter.get_txn_field(To), 0.into()); + assert_eq!(interpreter.get_txn_field(Value), 100.into()); + assert_eq!(interpreter.get_txn_field(DataLen), 2.into()); + assert_eq!(interpreter.get_txn_field(YParity), 1.into()); + assert_eq!( + interpreter.get_txn_field(R), + U256::from_big_endian(&hex!( + "7c5c61ed975ebd286f6b027b8c504842e50a47d318e1e801719dd744fe93e6c6" + )) + ); + assert_eq!( + interpreter.get_txn_field(S), + U256::from_big_endian(&hex!( + "1e7b5119b57dd54e175ff2f055c91f3ab1b53eba0b2c184f347cdff0e745aca2" + )) + ); + + Ok(()) +} From b737aeaf0325e161f81acd46d321ae5d4cc0e448 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 1 Aug 2022 11:13:33 -0700 Subject: [PATCH 2/4] Tweak py-evm code --- evm/src/cpu/kernel/tests/type_0_txn.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/evm/src/cpu/kernel/tests/type_0_txn.rs b/evm/src/cpu/kernel/tests/type_0_txn.rs index 437795e9..01e26423 100644 --- a/evm/src/cpu/kernel/tests/type_0_txn.rs +++ b/evm/src/cpu/kernel/tests/type_0_txn.rs @@ -22,16 +22,20 @@ fn process_type_0_txn() -> Result<()> { interpreter.halt_offsets.push(process_normalized_txn); // Generated with py-evm: + // import eth, eth_keys, eth_utils, rlp + // genesis_params = { 'difficulty': eth.constants.GENESIS_DIFFICULTY } + // chain = eth.chains.mainnet.MainnetChain.from_genesis(eth.db.atomic.AtomicDB(), genesis_params, {}) // unsigned_txn = chain.create_unsigned_transaction( // nonce=5, // gas_price=10, // gas=22_000, - // to=constants.ZERO_ADDRESS, + // to=eth.constants.ZERO_ADDRESS, // value=100, // data=b'\x42\x42', // ) - // my_txn = unsigned_txn.as_signed_transaction(my_sk) - // rlp.encode(my_txn) + // sk = eth_keys.keys.PrivateKey(eth_utils.decode_hex('4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318')) + // signed_txn = unsigned_txn.as_signed_transaction(sk) + // rlp.encode(signed_txn).hex() interpreter.set_rlp_memory(hex!("f861050a8255f0940000000000000000000000000000000000000000648242421ca07c5c61ed975ebd286f6b027b8c504842e50a47d318e1e801719dd744fe93e6c6a01e7b5119b57dd54e175ff2f055c91f3ab1b53eba0b2c184f347cdff0e745aca2").to_vec()); interpreter.run()?; From 94c9b1b09c1c5f3d02ac4d8f7ed42f63892268c2 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Mon, 1 Aug 2022 11:20:56 -0700 Subject: [PATCH 3/4] Misc --- evm/src/cpu/kernel/tests/mod.rs | 2 +- evm/src/cpu/kernel/tests/rlp.rs | 1 - evm/src/cpu/kernel/tests/transaction_parsing/mod.rs | 1 + .../{type_0_txn.rs => transaction_parsing/parse_type_0_txn.rs} | 3 --- 4 files changed, 2 insertions(+), 5 deletions(-) create mode 100644 evm/src/cpu/kernel/tests/transaction_parsing/mod.rs rename evm/src/cpu/kernel/tests/{type_0_txn.rs => transaction_parsing/parse_type_0_txn.rs} (97%) diff --git a/evm/src/cpu/kernel/tests/mod.rs b/evm/src/cpu/kernel/tests/mod.rs index e912388b..ab92c5a0 100644 --- a/evm/src/cpu/kernel/tests/mod.rs +++ b/evm/src/cpu/kernel/tests/mod.rs @@ -2,7 +2,7 @@ mod curve_ops; mod ecrecover; mod exp; mod rlp; -mod type_0_txn; +mod transaction_parsing; use std::str::FromStr; diff --git a/evm/src/cpu/kernel/tests/rlp.rs b/evm/src/cpu/kernel/tests/rlp.rs index fd790dcb..55dfc7e8 100644 --- a/evm/src/cpu/kernel/tests/rlp.rs +++ b/evm/src/cpu/kernel/tests/rlp.rs @@ -5,7 +5,6 @@ use ethereum_types::U256; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::interpreter::Interpreter; -use crate::memory::segments::Segment; #[test] fn test_decode_rlp_string_len_short() -> Result<()> { diff --git a/evm/src/cpu/kernel/tests/transaction_parsing/mod.rs b/evm/src/cpu/kernel/tests/transaction_parsing/mod.rs new file mode 100644 index 00000000..fb50625f --- /dev/null +++ b/evm/src/cpu/kernel/tests/transaction_parsing/mod.rs @@ -0,0 +1 @@ +mod parse_type_0_txn; diff --git a/evm/src/cpu/kernel/tests/type_0_txn.rs b/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs similarity index 97% rename from evm/src/cpu/kernel/tests/type_0_txn.rs rename to evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs index 01e26423..8b725479 100644 --- a/evm/src/cpu/kernel/tests/type_0_txn.rs +++ b/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use anyhow::Result; use ethereum_types::U256; use hex_literal::hex; @@ -7,7 +5,6 @@ use NormalizedTxnField::*; use crate::cpu::kernel::aggregator::KERNEL; use crate::cpu::kernel::interpreter::Interpreter; -use crate::cpu::kernel::tests::rlp::set_rlp_memory; use crate::cpu::kernel::txn_fields::NormalizedTxnField; #[test] From 215be25cf8e1364092ea35d285c9bea1188a21ed Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Tue, 2 Aug 2022 09:08:24 -0700 Subject: [PATCH 4/4] Feedback --- evm/src/cpu/kernel/interpreter.rs | 4 ++++ .../cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/evm/src/cpu/kernel/interpreter.rs b/evm/src/cpu/kernel/interpreter.rs index 3b753f0a..6a5b794f 100644 --- a/evm/src/cpu/kernel/interpreter.rs +++ b/evm/src/cpu/kernel/interpreter.rs @@ -137,6 +137,10 @@ impl<'a> Interpreter<'a> { self.memory.context_memory[0].segments[Segment::TxnFields as usize].content[field as usize] } + pub(crate) fn get_txn_data(&self) -> &[U256] { + &self.memory.context_memory[0].segments[Segment::TxnData as usize].content + } + pub(crate) fn set_rlp_memory(&mut self, rlp: Vec) { self.memory.context_memory[0].segments[Segment::RlpRaw as usize].content = rlp.into_iter().map(U256::from).collect(); diff --git a/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs b/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs index 8b725479..c01474ce 100644 --- a/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs +++ b/evm/src/cpu/kernel/tests/transaction_parsing/parse_type_0_txn.rs @@ -46,6 +46,7 @@ fn process_type_0_txn() -> Result<()> { assert_eq!(interpreter.get_txn_field(To), 0.into()); assert_eq!(interpreter.get_txn_field(Value), 100.into()); assert_eq!(interpreter.get_txn_field(DataLen), 2.into()); + assert_eq!(interpreter.get_txn_data(), &[0x42.into(), 0x42.into()]); assert_eq!(interpreter.get_txn_field(YParity), 1.into()); assert_eq!( interpreter.get_txn_field(R),