From c721155e23a66c0c141019d64aaf19cffc5c6450 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Thu, 29 Sep 2022 23:09:32 -0700 Subject: [PATCH] Main function, txn processing loop --- evm/src/all_stark.rs | 6 +-- evm/src/cpu/control_flow.rs | 14 ++--- evm/src/cpu/kernel/aggregator.rs | 1 + evm/src/cpu/kernel/asm/keccak.asm | 8 --- evm/src/cpu/kernel/asm/main.asm | 8 +++ evm/src/cpu/kernel/asm/rlp/read_to_memory.asm | 4 +- .../cpu/kernel/asm/transactions/router.asm | 14 ++--- .../cpu/kernel/asm/transactions/type_0.asm | 52 +++++++++---------- .../cpu/kernel/asm/transactions/type_1.asm | 2 +- .../cpu/kernel/asm/transactions/type_2.asm | 2 +- .../transaction_parsing/parse_type_0_txn.rs | 3 +- evm/src/generation/mod.rs | 1 + evm/src/generation/prover_input.rs | 19 +++++++ evm/src/generation/rlp.rs | 18 +++++++ evm/src/generation/state.rs | 9 ++++ 15 files changed, 105 insertions(+), 56 deletions(-) delete mode 100644 evm/src/cpu/kernel/asm/keccak.asm create mode 100644 evm/src/cpu/kernel/asm/main.asm create mode 100644 evm/src/generation/rlp.rs diff --git a/evm/src/all_stark.rs b/evm/src/all_stark.rs index 2786c36a..0c2516e5 100644 --- a/evm/src/all_stark.rs +++ b/evm/src/all_stark.rs @@ -338,7 +338,7 @@ mod tests { row.opcode_bits = bits_from_opcode(0x5b); row.is_cpu_cycle = F::ONE; row.is_kernel_mode = F::ONE; - row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"]); + row.program_counter = F::from_canonical_usize(KERNEL.global_labels["main"]); cpu_stark.generate(row.borrow_mut()); cpu_trace_rows.push(row.into()); } @@ -377,8 +377,8 @@ mod tests { row.is_cpu_cycle = F::ONE; row.is_kernel_mode = F::ONE; - // Since these are the first cycle rows, we must start with PC=route_txn then increment. - row.program_counter = F::from_canonical_usize(KERNEL.global_labels["route_txn"] + i); + // Since these are the first cycle rows, we must start with PC=main then increment. + row.program_counter = F::from_canonical_usize(KERNEL.global_labels["main"] + i); row.opcode_bits = bits_from_opcode( if logic_trace[logic::columns::IS_AND].values[i] != F::ZERO { 0x16 diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index 4ab9b91a..3856726c 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -69,12 +69,12 @@ pub fn eval_packed_generic( ); // If a non-CPU cycle row is followed by a CPU cycle row, then: - // - the `program_counter` of the CPU cycle row is `route_txn` (the entry point of our kernel), + // - the `program_counter` of the CPU cycle row is `main` (the entry point of our kernel), // - execution is in kernel mode, and // - the stack is empty. let is_last_noncpu_cycle = (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle; let pc_diff = - nv.program_counter - P::Scalar::from_canonical_usize(KERNEL.global_labels["route_txn"]); + nv.program_counter - P::Scalar::from_canonical_usize(KERNEL.global_labels["main"]); yield_constr.constraint_transition(is_last_noncpu_cycle * pc_diff); yield_constr.constraint_transition(is_last_noncpu_cycle * (nv.is_kernel_mode - P::ONES)); yield_constr.constraint_transition(is_last_noncpu_cycle * nv.stack_len); @@ -118,18 +118,18 @@ pub fn eval_ext_circuit, const D: usize>( } // If a non-CPU cycle row is followed by a CPU cycle row, then: - // - the `program_counter` of the CPU cycle row is `route_txn` (the entry point of our kernel), + // - the `program_counter` of the CPU cycle row is `main` (the entry point of our kernel), // - execution is in kernel mode, and // - the stack is empty. { let is_last_noncpu_cycle = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle); - // Start at `route_txn`. - let route_txn = builder.constant_extension(F::Extension::from_canonical_usize( - KERNEL.global_labels["route_txn"], + // Start at `main`. + let main = builder.constant_extension(F::Extension::from_canonical_usize( + KERNEL.global_labels["main"], )); - let pc_diff = builder.sub_extension(nv.program_counter, route_txn); + let pc_diff = builder.sub_extension(nv.program_counter, main); let pc_constr = builder.mul_extension(is_last_noncpu_cycle, pc_diff); yield_constr.constraint_transition(builder, pc_constr); diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index 5d025ac2..efaa621d 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -33,6 +33,7 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/curve/secp256k1/moddiv.asm"), include_str!("asm/exp.asm"), include_str!("asm/halt.asm"), + include_str!("asm/main.asm"), include_str!("asm/memory/core.asm"), include_str!("asm/memory/memcpy.asm"), include_str!("asm/memory/metadata.asm"), diff --git a/evm/src/cpu/kernel/asm/keccak.asm b/evm/src/cpu/kernel/asm/keccak.asm deleted file mode 100644 index d464bb6a..00000000 --- a/evm/src/cpu/kernel/asm/keccak.asm +++ /dev/null @@ -1,8 +0,0 @@ -// Computes the Keccak256 hash of some arbitrary bytes in memory. -// The given memory values should be in the range of a byte. -// -// Pre stack: ADDR, len, retdest -// Post stack: hash -global keccak_general: - // stack: ADDR, len - // TODO diff --git a/evm/src/cpu/kernel/asm/main.asm b/evm/src/cpu/kernel/asm/main.asm new file mode 100644 index 00000000..d0402f17 --- /dev/null +++ b/evm/src/cpu/kernel/asm/main.asm @@ -0,0 +1,8 @@ +global main: + // If the prover has no more txns for us to process, halt. + PROVER_INPUT(end_of_txns) + %jumpi(halt) + + // Call route_txn, returning to main to continue the loop. + PUSH main + %jump(route_txn) diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm index db474b9b..189edd1d 100644 --- a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -6,7 +6,7 @@ global read_rlp_to_memory: // stack: retdest - PROVER_INPUT // Read the RLP blob length from the prover tape. + PROVER_INPUT(rlp) // Read the RLP blob length from the prover tape. // stack: len, retdest PUSH 0 // initial position // stack: pos, len, retdest @@ -19,7 +19,7 @@ read_rlp_to_memory_loop: // stack: pos == len, pos, len, retdest %jumpi(read_rlp_to_memory_finish) // stack: pos, len, retdest - PROVER_INPUT + PROVER_INPUT(rlp) // stack: byte, pos, len, retdest DUP2 // stack: pos, byte, pos, len, retdest diff --git a/evm/src/cpu/kernel/asm/transactions/router.asm b/evm/src/cpu/kernel/asm/transactions/router.asm index 47a899c9..974fed99 100644 --- a/evm/src/cpu/kernel/asm/transactions/router.asm +++ b/evm/src/cpu/kernel/asm/transactions/router.asm @@ -3,14 +3,14 @@ // jump to the appropriate transaction parsing method. global route_txn: - // stack: (empty) + // stack: retdest // First load transaction data into memory, where it will be parsed. PUSH read_txn_from_memory %jump(read_rlp_to_memory) // At this point, the raw txn data is in memory. read_txn_from_memory: - // stack: (empty) + // stack: retdest // We will peak at the first byte to determine what type of transaction this is. // Note that type 1 and 2 transactions have a first byte of 1 and 2, respectively. @@ -20,17 +20,17 @@ read_txn_from_memory: PUSH 0 %mload_current(@SEGMENT_RLP_RAW) %eq_const(1) - // stack: first_byte == 1 + // stack: first_byte == 1, retdest %jumpi(process_type_1_txn) - // stack: (empty) + // stack: retdest PUSH 0 %mload_current(@SEGMENT_RLP_RAW) %eq_const(2) - // stack: first_byte == 2 + // stack: first_byte == 2, retdest %jumpi(process_type_2_txn) - // stack: (empty) + // stack: retdest // At this point, since it's not a type 1 or 2 transaction, // it must be a legacy (aka type 0) transaction. - %jump(process_type_2_txn) + %jump(process_type_0_txn) diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm index 3f258624..7bc7a399 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_0.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -12,15 +12,15 @@ // keccak256(rlp([nonce, gas_price, gas_limit, to, value, data])) global process_type_0_txn: - // stack: (empty) + // stack: retdest PUSH 0 // initial pos - // stack: pos + // stack: pos, retdest %decode_rlp_list_len // We don't actually need the length. %stack (pos, len) -> (pos) // Decode the nonce and store it. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, nonce) -> (nonce, pos) %mstore_txn_field(@TXN_FIELD_NONCE) @@ -29,38 +29,38 @@ global process_type_0_txn: // For legacy transactions, we set both the // TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS and TXN_FIELD_MAX_FEE_PER_GAS // fields to gas_price. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, gas_price) -> (gas_price, gas_price, pos) %mstore_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS) %mstore_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS) // Decode the gas limit and store it. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, gas_limit) -> (gas_limit, pos) %mstore_txn_field(@TXN_FIELD_GAS_LIMIT) // Decode the "to" field and store it. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, to) -> (to, pos) %mstore_txn_field(@TXN_FIELD_TO) // Decode the value field and store it. - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, value) -> (value, pos) %mstore_txn_field(@TXN_FIELD_VALUE) // Decode the data length, store it, and compute new_pos after any data. - // stack: pos + // stack: pos, retdest %decode_rlp_string_len %stack (pos, data_len) -> (data_len, pos, data_len, pos, data_len) %mstore_txn_field(@TXN_FIELD_DATA_LEN) - // stack: pos, data_len, pos, data_len + // stack: pos, data_len, pos, data_len, retdest ADD - // stack: new_pos, pos, data_len + // stack: new_pos, pos, data_len, retdest // Memcpy the txn data from @SEGMENT_RLP_RAW to @SEGMENT_TXN_DATA. PUSH parse_v @@ -70,62 +70,62 @@ global process_type_0_txn: PUSH 0 PUSH @SEGMENT_TXN_DATA GET_CONTEXT - // stack: DST, SRC, data_len, parse_v, new_pos + // stack: DST, SRC, data_len, parse_v, new_pos, retdest %jump(memcpy) parse_v: - // stack: pos + // stack: pos, retdest %decode_rlp_scalar - // stack: pos, v + // stack: pos, v, retdest SWAP1 - // stack: v, pos + // stack: v, pos, retdest DUP1 %gt_const(28) - // stack: v > 28, v, pos + // stack: v > 28, v, pos, retdest %jumpi(process_v_new_style) // We have an old style v, so y_parity = v - 27. // No chain ID is present, so we can leave TXN_FIELD_CHAIN_ID_PRESENT and // TXN_FIELD_CHAIN_ID with their default values of zero. - // stack: v, pos + // stack: v, pos, retdest %sub_const(27) %stack (y_parity, pos) -> (y_parity, pos) %mstore_txn_field(@TXN_FIELD_Y_PARITY) - // stack: pos + // stack: pos, retdest %jump(parse_r) process_v_new_style: - // stack: v, pos + // stack: v, pos, retdest // We have a new style v, so chain_id_present = 1, // chain_id = (v - 35) / 2, and y_parity = (v - 35) % 2. %stack (v, pos) -> (1, v, pos) %mstore_txn_field(@TXN_FIELD_CHAIN_ID_PRESENT) - // stack: v, pos + // stack: v, pos, retdest %sub_const(35) DUP1 - // stack: v - 35, v - 35, pos + // stack: v - 35, v - 35, pos, retdest %div_const(2) - // stack: chain_id, v - 35, pos + // stack: chain_id, v - 35, pos, retdest %mstore_txn_field(@TXN_FIELD_CHAIN_ID) - // stack: v - 35, pos + // stack: v - 35, pos, retdest %mod_const(2) - // stack: y_parity, pos + // stack: y_parity, pos, retdest %mstore_txn_field(@TXN_FIELD_Y_PARITY) parse_r: - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, r) -> (r, pos) %mstore_txn_field(@TXN_FIELD_R) - // stack: pos + // stack: pos, retdest %decode_rlp_scalar %stack (pos, s) -> (s) %mstore_txn_field(@TXN_FIELD_S) - // stack: (empty) + // stack: retdest // TODO: Write the signed txn data to memory, where it can be hashed and // checked against the signature. diff --git a/evm/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm index 9d45c1e4..8c7fcaae 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_1.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_1.asm @@ -7,5 +7,5 @@ // data, access_list])) global process_type_1_txn: - // stack: (empty) + // stack: retdest PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/asm/transactions/type_2.asm b/evm/src/cpu/kernel/asm/transactions/type_2.asm index b2a862c1..f1ff18d8 100644 --- a/evm/src/cpu/kernel/asm/transactions/type_2.asm +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -8,5 +8,5 @@ // access_list])) global process_type_2_txn: - // stack: (empty) + // stack: retdest PANIC // TODO: Unfinished 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 c01474ce..53a3d282 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 @@ -12,7 +12,8 @@ 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![]); + let retaddr = 0xDEADBEEFu32.into(); + let mut interpreter = Interpreter::new_with_kernel(process_type_0_txn, vec![retaddr]); // When we reach process_normalized_txn, we're done with parsing and normalizing. // Processing normalized transactions is outside the scope of this test. diff --git a/evm/src/generation/mod.rs b/evm/src/generation/mod.rs index 511aa009..2d0a9f7c 100644 --- a/evm/src/generation/mod.rs +++ b/evm/src/generation/mod.rs @@ -23,6 +23,7 @@ use crate::util::trace_rows_to_poly_values; pub(crate) mod memory; pub(crate) mod mpt; pub(crate) mod prover_input; +pub(crate) mod rlp; pub(crate) mod state; #[derive(Clone, Debug, Deserialize, Serialize, Default)] diff --git a/evm/src/generation/prover_input.rs b/evm/src/generation/prover_input.rs index c6051a4d..d5d7df7c 100644 --- a/evm/src/generation/prover_input.rs +++ b/evm/src/generation/prover_input.rs @@ -24,12 +24,24 @@ impl GenerationState { #[allow(unused)] // TODO: Should be used soon. pub(crate) fn prover_input(&mut self, stack: &[U256], input_fn: &ProverInputFn) -> U256 { match input_fn.0[0].as_str() { + "end_of_txns" => self.run_end_of_txns(), "ff" => self.run_ff(stack, input_fn), "mpt" => self.run_mpt(), + "rlp" => self.run_rlp(), _ => panic!("Unrecognized prover input function."), } } + fn run_end_of_txns(&mut self) -> U256 { + let end = self.next_txn_index == self.inputs.signed_txns.len(); + if end { + U256::one() + } else { + self.next_txn_index += 1; + U256::zero() + } + } + /// Finite field operations. fn run_ff(&self, stack: &[U256], input_fn: &ProverInputFn) -> U256 { let field = EvmField::from_str(input_fn.0[1].as_str()).unwrap(); @@ -44,6 +56,13 @@ impl GenerationState { .pop() .unwrap_or_else(|| panic!("Out of MPT data")) } + + /// RLP data. + fn run_rlp(&mut self) -> U256 { + self.rlp_prover_inputs + .pop() + .unwrap_or_else(|| panic!("Out of RLP data")) + } } enum EvmField { diff --git a/evm/src/generation/rlp.rs b/evm/src/generation/rlp.rs new file mode 100644 index 00000000..f28272a2 --- /dev/null +++ b/evm/src/generation/rlp.rs @@ -0,0 +1,18 @@ +use ethereum_types::U256; + +pub(crate) fn all_rlp_prover_inputs_reversed(signed_txns: &[Vec]) -> Vec { + let mut inputs = all_rlp_prover_inputs(signed_txns); + inputs.reverse(); + inputs +} + +fn all_rlp_prover_inputs(signed_txns: &[Vec]) -> Vec { + let mut prover_inputs = vec![]; + for txn in signed_txns { + prover_inputs.push(txn.len().into()); + for &byte in txn { + prover_inputs.push(byte.into()); + } + } + prover_inputs +} diff --git a/evm/src/generation/state.rs b/evm/src/generation/state.rs index b8ae9735..17d63018 100644 --- a/evm/src/generation/state.rs +++ b/evm/src/generation/state.rs @@ -7,6 +7,7 @@ use tiny_keccak::keccakf; use crate::cpu::columns::{CpuColumnsView, NUM_CPU_COLUMNS}; use crate::generation::memory::MemoryState; use crate::generation::mpt::all_mpt_prover_inputs_reversed; +use crate::generation::rlp::all_rlp_prover_inputs_reversed; use crate::generation::GenerationInputs; use crate::keccak_memory::keccak_memory_stark::KeccakMemoryOp; use crate::memory::memory_stark::MemoryOp; @@ -19,6 +20,7 @@ use crate::{keccak, logic}; pub(crate) struct GenerationState { #[allow(unused)] // TODO: Should be used soon. pub(crate) inputs: GenerationInputs, + pub(crate) next_txn_index: usize, pub(crate) cpu_rows: Vec<[F; NUM_CPU_COLUMNS]>, pub(crate) current_cpu_row: CpuColumnsView, @@ -32,14 +34,20 @@ pub(crate) struct GenerationState { /// Prover inputs containing MPT data, in reverse order so that the next input can be obtained /// via `pop()`. pub(crate) mpt_prover_inputs: Vec, + + /// Prover inputs containing RLP data, in reverse order so that the next input can be obtained + /// via `pop()`. + pub(crate) rlp_prover_inputs: Vec, } impl GenerationState { pub(crate) fn new(inputs: GenerationInputs) -> Self { let mpt_prover_inputs = all_mpt_prover_inputs_reversed(&inputs.tries); + let rlp_prover_inputs = all_rlp_prover_inputs_reversed(&inputs.signed_txns); Self { inputs, + next_txn_index: 0, cpu_rows: vec![], current_cpu_row: [F::ZERO; NUM_CPU_COLUMNS].into(), current_context: 0, @@ -48,6 +56,7 @@ impl GenerationState { keccak_memory_inputs: vec![], logic_ops: vec![], mpt_prover_inputs, + rlp_prover_inputs, } }