diff --git a/evm/src/cpu/kernel/aggregator.rs b/evm/src/cpu/kernel/aggregator.rs index ec42f5c4..1cf61255 100644 --- a/evm/src/cpu/kernel/aggregator.rs +++ b/evm/src/cpu/kernel/aggregator.rs @@ -9,6 +9,7 @@ use once_cell::sync::Lazy; use super::assembler::{assemble, Kernel}; use crate::cpu::kernel::parser::parse; +use crate::cpu::kernel::txn_fields::NormalizedTxnField; use crate::memory::segments::Segment; pub static KERNEL: Lazy = Lazy::new(combined_kernel); @@ -24,6 +25,9 @@ pub fn evm_constants() -> HashMap { for segment in Segment::all() { c.insert(segment.var_name().into(), (segment as u32).into()); } + for txn_field in NormalizedTxnField::all() { + c.insert(txn_field.var_name().into(), (txn_field as u32).into()); + } c } @@ -43,8 +47,16 @@ pub(crate) fn combined_kernel() -> Kernel { include_str!("asm/secp256k1/lift_x.asm"), include_str!("asm/secp256k1/inverse_scalar.asm"), include_str!("asm/ecrecover.asm"), + include_str!("asm/rlp/encode.asm"), + include_str!("asm/rlp/decode.asm"), + include_str!("asm/rlp/read_to_memory.asm"), include_str!("asm/storage_read.asm"), include_str!("asm/storage_write.asm"), + include_str!("asm/transactions/process_normalized.asm"), + include_str!("asm/transactions/router.asm"), + include_str!("asm/transactions/type_0.asm"), + include_str!("asm/transactions/type_1.asm"), + include_str!("asm/transactions/type_2.asm"), ]; let parsed_files = files.iter().map(|f| parse(f)).collect_vec(); diff --git a/evm/src/cpu/kernel/asm/basic_macros.asm b/evm/src/cpu/kernel/asm/basic_macros.asm index 20f8958c..e266b2cb 100644 --- a/evm/src/cpu/kernel/asm/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/basic_macros.asm @@ -78,10 +78,22 @@ // stack: c, input, ... SWAP1 // stack: input, c, ... - SUB + DIV // stack: input / c, ... %endmacro +// Slightly inefficient as we need to swap the inputs. +// Consider avoiding this in performance-critical code. +%macro mod_const(c) + // stack: input, ... + PUSH $c + // stack: c, input, ... + SWAP1 + // stack: input, c, ... + MOD + // stack: input % c, ... +%endmacro + %macro shl_const(c) // stack: input, ... PUSH $c diff --git a/evm/src/cpu/kernel/asm/rlp/decode.asm b/evm/src/cpu/kernel/asm/rlp/decode.asm new file mode 100644 index 00000000..dde85d05 --- /dev/null +++ b/evm/src/cpu/kernel/asm/rlp/decode.asm @@ -0,0 +1,145 @@ +// Note: currently, these methods do not check that RLP input is in canonical +// form; for example a single byte could be encoded with the length-of-length +// form. Technically an EVM must perform these checks, but we aren't really +// concerned with it in our setting. An attacker who corrupted consensus could +// prove a non-canonical state, but this would just temporarily stall the bridge +// until a fix was deployed. We are more concerned with preventing any theft of +// assets. + +// Parse the length of a bytestring from RLP memory. The next len bytes after +// pos' will contain the string. +// +// Pre stack: pos, retdest +// Post stack: pos', len +global decode_rlp_string_len: + JUMPDEST + // stack: pos, retdest + DUP1 + %mload_current(@SEGMENT_RLP_RAW) + // stack: first_byte, pos, retdest + DUP1 + %gt_const(0xb6) + // stack: first_byte >= 0xb7, first_byte, pos, retdest + %jumpi(decode_rlp_string_len_large) + // stack: first_byte, pos, retdest + DUP1 + %gt_const(0x7f) + // stack: first_byte >= 0x80, first_byte, pos, retdest + %jumpi(decode_rlp_string_len_medium) +decode_rlp_string_len_small: + // String is a single byte in the range [0x00, 0x7f]. + %stack (first_byte, pos, retdest) -> (retdest, pos, 1) + JUMP +decode_rlp_string_len_medium: + // String is 0-55 bytes long. First byte contains the len. + // stack: first_byte, pos, retdest + %sub_const(0x80) + // stack: len, pos, retdest + SWAP1 + %add_const(1) + // stack: pos', len, retdest +decode_rlp_string_len_large: + // String is >55 bytes long. First byte contains the len of the len. + // stack: first_byte, pos, retdest + %sub_const(0xb7) + // stack: len_of_len, pos, retdest + SWAP1 + %add_const(1) + // stack: pos', len_of_len, retdest + %jump(decode_int_given_len) + +// Parse a scalar from RLP memory. +// Pre stack: pos, retdest +// Post stack: pos', scalar +// +// Scalars are variable-length, but this method assumes a max length of 32 +// bytes, so that the result can be returned as a single word on the stack. +// As per the spec, scalars must not have leading zeros. +global decode_rlp_scalar: + JUMPDEST + // stack: pos, retdest + PUSH decode_int_given_len + // stack: decode_int_given_len, pos, retdest + SWAP1 + // stack: pos, decode_int_given_len, retdest + // decode_rlp_string_len will return to decode_int_given_len, at which point + // the stack will contain (pos', len, retdest), which are the proper args + // to decode_int_given_len. + %jump(decode_rlp_string_len) + +// Parse the length of an RLP list from memory. +// Pre stack: pos, retdest +// Post stack: pos', len +global decode_rlp_list_len: + JUMPDEST + // stack: pos, retdest + DUP1 + %mload_current(@SEGMENT_RLP_RAW) + // stack: first_byte, pos, retdest + SWAP1 + %add_const(1) // increment pos + SWAP1 + // stack: first_byte, pos', retdest + // If first_byte is >= 0xf7, it's a > 55 byte list, and + // first_byte - 0xf7 is the length of the length. + DUP1 + %gt_const(0xf6) // GT is native while GE is not, so compare to 0xf6 instead + // stack: first_byte >= 0xf7, first_byte, pos', retdest + %jumpi(decode_rlp_list_len_big) +decode_rlp_list_len_small: + // The list length is first_byte - 0xc0. + // stack: first_byte, pos', retdest + %sub_const(0xc0) + // stack: len, pos', retdest + %stack (len, pos, retdest) -> (retdest, pos, len) + JUMP +decode_rlp_list_len_big: + JUMPDEST + // The length of the length is first_byte - 0xf7. + // stack: first_byte, pos', retdest + %sub_const(0xf7) + // stack: len_of_len, pos', retdest + SWAP1 + // stack: pos', len_of_len, retdest + %jump(decode_int_given_len) + +// Parse an integer of the given length. It is assumed that the integer will +// fit in a single (256-bit) word on the stack. +// Pre stack: pos, len, retdest +// Post stack: pos', int +decode_int_given_len: + JUMPDEST + %stack (pos, len, retdest) -> (pos, len, pos, retdest) + ADD + // stack: end_pos, pos, retdest + SWAP1 + // stack: pos, end_pos, retdest + PUSH 0 // initial accumulator state + // stack: acc, pos, end_pos, retdest +decode_int_given_len_loop: + JUMPDEST + // stack: acc, pos, end_pos, retdest + DUP3 + DUP3 + ISZERO + // stack: pos == end_pos, acc, pos, end_pos, retdest + %jumpi(decode_int_given_len_finish) + // stack: acc, pos, end_pos, retdest + %shl_const(8) + // stack: acc << 8, pos, end_pos, retdest + DUP2 + // stack: pos, acc << 8, pos, end_pos, retdest + %mload_current(@SEGMENT_RLP_RAW) + // stack: byte, acc << 8, pos, end_pos, retdest + ADD + // stack: acc', pos, end_pos, retdest + // Increment pos. + SWAP1 + %add_const(1) + SWAP1 + // stack: acc', pos', end_pos, retdest + %jump(decode_int_given_len_loop) +decode_int_given_len_finish: + JUMPDEST + %stack (acc, pos, end_pos, retdest) -> (retdest, pos, acc) + JUMP diff --git a/evm/src/cpu/kernel/asm/rlp/encode.asm b/evm/src/cpu/kernel/asm/rlp/encode.asm new file mode 100644 index 00000000..b2446c37 --- /dev/null +++ b/evm/src/cpu/kernel/asm/rlp/encode.asm @@ -0,0 +1,17 @@ +// RLP-encode a scalar, i.e. a variable-length integer. +// Pre stack: pos, scalar +// Post stack: (empty) +global encode_rlp_scalar: + PANIC // TODO: implement + +// RLP-encode a fixed-length 160-bit string. Assumes string < 2^160. +// Pre stack: pos, string +// Post stack: (empty) +global encode_rlp_160: + PANIC // TODO: implement + +// RLP-encode a fixed-length 256-bit string. +// Pre stack: pos, string +// Post stack: (empty) +global encode_rlp_256: + PANIC // TODO: implement diff --git a/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm new file mode 100644 index 00000000..1a84c710 --- /dev/null +++ b/evm/src/cpu/kernel/asm/rlp/read_to_memory.asm @@ -0,0 +1,39 @@ +// Read RLP data from the prover's tape, and save it to the SEGMENT_RLP_RAW +// segment of memory. + +// Pre stack: retdest +// Post stack: (empty) + +global read_rlp_to_memory: + JUMPDEST + // stack: retdest + PROVER_INPUT // Read the RLP blob length from the prover tape. + // stack: len, retdest + PUSH 0 // initial position + // stack: pos, len, retdest + +read_rlp_to_memory_loop: + JUMPDEST + // stack: pos, len, retdest + DUP2 + DUP2 + EQ + // stack: pos == len, pos, len, retdest + %jumpi(read_rlp_to_memory_finish) + // stack: pos, len, retdest + PROVER_INPUT + // stack: byte, pos, len, retdest + DUP1 + // stack: pos, byte, pos, len, retdest + %mstore_current(@SEGMENT_RLP_RAW) + // stack: pos, len, retdest + %add_const(1) + // stack: pos', len, retdest + %jump(read_rlp_to_memory_loop) + +read_rlp_to_memory_finish: + JUMPDEST + // stack: pos, len, retdest + %pop2 + // stack: retdest + JUMP diff --git a/evm/src/cpu/kernel/asm/transactions/process_normalized.asm b/evm/src/cpu/kernel/asm/transactions/process_normalized.asm new file mode 100644 index 00000000..d99041b0 --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/process_normalized.asm @@ -0,0 +1,5 @@ +// After the transaction data has been parsed into a normalized set of fields +// (see TxnField), this routine processes the transaction. + +global process_normalized_txn: + // TODO diff --git a/evm/src/cpu/kernel/asm/transactions/router.asm b/evm/src/cpu/kernel/asm/transactions/router.asm new file mode 100644 index 00000000..01a65fec --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/router.asm @@ -0,0 +1,38 @@ +// This is the entry point of transaction processing. We load the transaction +// RLP data into memory, check the transaction type, then based on the type we +// jump to the appropriate transaction parsing method. + +global route_txn: + JUMPDEST + // stack: (empty) + // 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: + JUMPDEST + // stack: (empty) + + // 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. + // Type 0 (legacy) transactions have no such prefix, but their RLP will have a + // first byte >= 0xc0, so there is no overlap. + + PUSH 0 + %mload_current(@SEGMENT_RLP_RAW) + %eq_const(1) + // stack: first_byte == 1 + %jumpi(process_type_1_txn) + // stack: (empty) + + PUSH 0 + %mload_current(@SEGMENT_RLP_RAW) + %eq_const(2) + // stack: first_byte == 2 + %jumpi(process_type_2_txn) + // stack: (empty) + + // 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) diff --git a/evm/src/cpu/kernel/asm/transactions/type_0.asm b/evm/src/cpu/kernel/asm/transactions/type_0.asm new file mode 100644 index 00000000..3a217fda --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/type_0.asm @@ -0,0 +1,189 @@ +// Type 0 transactions, aka legacy transaction, have the format +// rlp([nonce, gas_price, gas_limit, destination, amount, data, v, r, s]) +// +// The field v was originally encoded as +// 27 + y_parity +// but as of EIP 155 it can also be encoded as +// 35 + 2 * chain_id + y_parity +// +// If a chain_id is present in v, the signed data is +// keccak256(rlp([nonce, gas_price, gas_limit, destination, amount, data, chain_id, 0, 0])) +// otherwise, it is +// keccak256(rlp([nonce, gas_price, gas_limit, destination, amount, data])) + +global process_type_0_txn: + JUMPDEST + // stack: (empty) + PUSH process_txn_with_len + PUSH 0 // initial pos + // stack: pos, process_txn_with_len + %jump(decode_rlp_list_len) + +process_txn_with_len: + // We don't actually need the length. + %stack (pos, len) -> (pos) + + PUSH store_nonce + SWAP1 + // stack: pos, store_nonce + %jump(decode_rlp_scalar) + +store_nonce: + %stack (pos, nonce) -> (@TXN_FIELD_NONCE, nonce, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + PUSH store_gas_price + SWAP1 + // stack: pos, store_gas_price + %jump(decode_rlp_scalar) + +store_gas_price: + // 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, gas_price) -> (@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS, gas_price, + @TXN_FIELD_MAX_FEE_PER_GAS, gas_price, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + PUSH store_gas_limit + SWAP1 + // stack: pos, store_gas_limit + %jump(decode_rlp_scalar) + +store_gas_limit: + %stack (pos, gas_limit) -> (@TXN_FIELD_GAS_LIMIT, gas_limit, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // Peak at the RLP to see if the next byte is zero. + // If so, there is no destination field, so skip the store_destination step. + // stack: pos + DUP1 + %mload_current(@SEGMENT_RLP_RAW) + ISZERO + // stack: destination_empty, pos + %jumpi(parse_amount) + + // If we got here, there is a destination field. + PUSH store_destination + SWAP1 + // stack: pos, store_destination + %jump(decode_rlp_scalar) + +store_destination: + %stack (pos, destination) -> (@TXN_FIELD_DESTINATION, destination, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + // stack: pos + +parse_amount: + // stack: pos + PUSH store_amount + SWAP1 + // stack: pos, store_amount + %jump(decode_rlp_scalar) + +store_amount: + %stack (pos, amount) -> (@TXN_FIELD_AMOUNT, amount, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + PUSH store_data_len + SWAP1 + // stack: pos, store_data_len + %jump(decode_rlp_string_len) + +store_data_len: + %stack (pos, data_len) -> (@TXN_FIELD_DATA_LEN, data_len, pos, data_len, pos, data_len) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + // stack: pos, data_len, pos, data_len + ADD + // stack: new_pos, pos, data_len + + // Memcpy the txn data from @SEGMENT_RLP_RAW to @SEGMENT_TXN_DATA. + PUSH parse_v + %stack (parse_v, new_pos, old_pos, data_len) -> (old_pos, data_len, parse_v, new_pos) + PUSH @SEGMENT_RLP_RAW + GET_CONTEXT + PUSH 0 + PUSH @SEGMENT_TXN_DATA + GET_CONTEXT + // stack: DST, SRC, data_len, parse_v, new_pos + %jump(memcpy) + +parse_v: + // stack: pos + PUSH process_v + SWAP1 + // stack: pos, process_v + %jump(decode_rlp_scalar) + +process_v: + // stack: pos, v + SWAP1 + // stack: v, pos + DUP1 + %gt_const(28) + // stack: v > 28, v, pos + %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 + %sub_const(27) + %stack (y_parity, pos) -> (@TXN_FIELD_Y_PARITY, y_parity, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + %jump(parse_r) + +process_v_new_style: + // stack: v, pos + // 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) -> (@TXN_FIELD_CHAIN_ID_PRESENT, 1, v, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: v, pos + %sub_const(35) + DUP1 + // stack: v - 35, v - 35, pos + %div_const(2) + // stack: chain_id, v - 35, pos + PUSH @TXN_FIELD_CHAIN_ID + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: v - 35, pos + %mod_const(2) + // stack: y_parity, pos + PUSH @TXN_FIELD_Y_PARITY + %mstore_current(@SEGMENT_NORMALIZED_TXN) + +parse_r: + // stack: pos + PUSH store_r + SWAP1 + // stack: pos, store_r + %jump(decode_rlp_scalar) + +store_r: + %stack (pos, r) -> (@TXN_FIELD_R, r, pos) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + + // stack: pos + PUSH store_s + SWAP1 + // stack: pos, store_s + %jump(decode_rlp_scalar) + +store_s: + %stack (pos, s) -> (@TXN_FIELD_S, s) + %mstore_current(@SEGMENT_NORMALIZED_TXN) + // stack: (empty) + + // TODO: Write the signed txn data to memory, where it can be hashed and + // checked against the signature. + + %jump(process_normalized_txn) diff --git a/evm/src/cpu/kernel/asm/transactions/type_1.asm b/evm/src/cpu/kernel/asm/transactions/type_1.asm new file mode 100644 index 00000000..5b9d2cdf --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/type_1.asm @@ -0,0 +1,12 @@ +// Type 1 transactions, introduced by EIP 2930, have the format +// 0x01 || rlp([chain_id, nonce, gas_price, gas_limit, to, value, data, +// access_list, y_parity, r, s]) +// +// The signed data is +// keccak256(0x01 || rlp([chain_id, nonce, gas_price, gas_limit, to, value, +// data, access_list])) + +global process_type_1_txn: + JUMPDEST + // stack: (empty) + 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 new file mode 100644 index 00000000..bdde4aa6 --- /dev/null +++ b/evm/src/cpu/kernel/asm/transactions/type_2.asm @@ -0,0 +1,14 @@ +// Type 2 transactions, introduced by EIP 1559, have the format +// 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, +// gas_limit, destination, amount, data, access_list, y_parity, +// r, s]) +// +// The signed data is +// keccak256(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, +// max_fee_per_gas, gas_limit, destination, amount, +// data, access_list])) + +global process_type_2_txn: + JUMPDEST + // stack: (empty) + PANIC // TODO: Unfinished diff --git a/evm/src/cpu/kernel/mod.rs b/evm/src/cpu/kernel/mod.rs index 1f13a042..1d545260 100644 --- a/evm/src/cpu/kernel/mod.rs +++ b/evm/src/cpu/kernel/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod keccak_util; mod opcodes; mod parser; mod stack_manipulation; +mod txn_fields; #[cfg(test)] mod interpreter; diff --git a/evm/src/cpu/kernel/txn_fields.rs b/evm/src/cpu/kernel/txn_fields.rs new file mode 100644 index 00000000..4dc8bfbb --- /dev/null +++ b/evm/src/cpu/kernel/txn_fields.rs @@ -0,0 +1,59 @@ +/// These are normalized transaction fields, i.e. not specific to any transaction type. +#[allow(dead_code)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] +pub(crate) enum NormalizedTxnField { + /// Whether a chain ID was present in the txn data. Type 0 transaction with v=27 or v=28 have + /// no chain ID. This affects what fields get signed. + ChainIdPresent = 0, + ChainId = 1, + Nonce = 2, + MaxPriorityFeePerGas = 3, + MaxFeePerGas = 4, + GasLimit = 5, + Destination = 6, + Amount = 7, + /// The length of the data field. The data itself is stored in another segment. + DataLen = 8, + YParity = 9, + R = 10, + S = 11, +} + +impl NormalizedTxnField { + pub(crate) const COUNT: usize = 12; + + pub(crate) fn all() -> [Self; Self::COUNT] { + [ + Self::ChainIdPresent, + Self::ChainId, + Self::Nonce, + Self::MaxPriorityFeePerGas, + Self::MaxFeePerGas, + Self::GasLimit, + Self::Destination, + Self::Amount, + Self::DataLen, + Self::YParity, + Self::R, + Self::S, + ] + } + + /// The variable name that gets passed into kernel assembly code. + pub(crate) fn var_name(&self) -> &'static str { + match self { + NormalizedTxnField::ChainIdPresent => "TXN_FIELD_CHAIN_ID_PRESENT", + NormalizedTxnField::ChainId => "TXN_FIELD_CHAIN_ID", + NormalizedTxnField::Nonce => "TXN_FIELD_NONCE", + NormalizedTxnField::MaxPriorityFeePerGas => "TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS", + NormalizedTxnField::MaxFeePerGas => "TXN_FIELD_MAX_FEE_PER_GAS", + NormalizedTxnField::GasLimit => "TXN_FIELD_GAS_LIMIT", + NormalizedTxnField::Destination => "TXN_FIELD_DESTINATION", + NormalizedTxnField::Amount => "TXN_FIELD_AMOUNT", + NormalizedTxnField::DataLen => "TXN_FIELD_DATA_LEN", + NormalizedTxnField::YParity => "TXN_FIELD_Y_PARITY", + NormalizedTxnField::R => "TXN_FIELD_R", + NormalizedTxnField::S => "TXN_FIELD_S", + } + } +} diff --git a/evm/src/memory/segments.rs b/evm/src/memory/segments.rs index f6a67dc8..56b56f5b 100644 --- a/evm/src/memory/segments.rs +++ b/evm/src/memory/segments.rs @@ -1,4 +1,4 @@ -#[allow(dead_code)] // TODO: Not all segments are used yet. +#[allow(dead_code)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug)] pub(crate) enum Segment { /// Contains EVM bytecode. @@ -17,14 +17,16 @@ pub(crate) enum Segment { /// General purpose kernel memory, used by various kernel functions. /// In general, calling a helper function can result in this memory being clobbered. KernelGeneral = 6, - /// Contains transaction data (after it's parsed and converted to a standard format). - TxnData = 7, + /// Contains normalized transaction fields; see `TxnField`. + TxnFields = 7, + /// Contains the data field of a transaction. + TxnData = 8, /// Raw RLP data. - RlpRaw = 8, + RlpRaw = 9, } impl Segment { - pub(crate) const COUNT: usize = 9; + pub(crate) const COUNT: usize = 10; pub(crate) fn all() -> [Self; Self::COUNT] { [ @@ -35,6 +37,7 @@ impl Segment { Self::Returndata, Self::Metadata, Self::KernelGeneral, + Self::TxnFields, Self::TxnData, Self::RlpRaw, ] @@ -50,6 +53,7 @@ impl Segment { Segment::Returndata => "SEGMENT_RETURNDATA", Segment::Metadata => "SEGMENT_METADATA", Segment::KernelGeneral => "SEGMENT_KERNEL_GENERAL", + Segment::TxnFields => "SEGMENT_NORMALIZED_TXN", Segment::TxnData => "SEGMENT_TXN_DATA", Segment::RlpRaw => "SEGMENT_RLP_RAW", }