Misc EVM fixes

This commit is contained in:
Daniel Lubarov 2023-02-25 07:59:51 -08:00
parent 57ea64e303
commit c558eedd65
17 changed files with 331 additions and 98 deletions

View File

@ -93,6 +93,7 @@ pub(crate) fn combined_kernel() -> Kernel {
include_str!("asm/rlp/num_bytes.asm"),
include_str!("asm/rlp/read_to_memory.asm"),
include_str!("asm/shift.asm"),
include_str!("asm/transactions/common_decoding.asm"),
include_str!("asm/transactions/router.asm"),
include_str!("asm/transactions/type_0.asm"),
include_str!("asm/transactions/type_1.asm"),

View File

@ -36,20 +36,43 @@ global extcodehash:
%%after:
%endmacro
global sys_extcodesize:
// stack: kexit_info, address
SWAP1
// stack: address, kexit_info
%extcodesize
// stack: code_size, kexit_info
SWAP1
EXIT_KERNEL
global extcodesize:
// stack: address, retdest
%extcodesize
// stack: extcodesize(address), retdest
SWAP1 JUMP
%macro codecopy
// stack: dest_offset, offset, size, retdest
// stack: dest_offset, offset, size
%address
// stack: address, dest_offset, offset, size, retdest
%jump(extcodecopy)
%extcodecopy
%endmacro
%macro extcodecopy
// stack: address, dest_offset, offset, size
%stack (dest_offset, offset, size) -> (dest_offset, offset, size, %%after)
%jump(extcodecopy)
%%after:
%endmacro
// Pre stack: kexit_info, address, dest_offset, offset, size
// Post stack: (empty)
global sys_extcodecopy:
%stack (kexit_info, address, dest_offset, offset, size)
-> (address, dest_offset, offset, size, kexit_info)
%extcodecopy
// stack: kexit_info
EXIT_KERNEL
// Pre stack: address, dest_offset, offset, size, retdest
// Post stack: (empty)
global extcodecopy:

View File

@ -1,8 +1,10 @@
// Handlers for call-like operations, namely CALL, CALLCODE, STATICCALL and DELEGATECALL.
// TODO: Take kexit_info
// Creates a new sub context and executes the code of the given account.
global call:
// stack: gas, address, value, args_offset, args_size, ret_offset, ret_size, retdest
// stack: kexit_info, gas, address, value, args_offset, args_size, ret_offset, ret_size
%address
%stack (self, gas, address, value)
// These are (static, should_transfer_value, value, sender, address, code_addr, gas)

View File

@ -29,6 +29,7 @@ global validate:
// TODO: Assert nonce is correct.
// TODO: Assert sender has no code.
// TODO: Assert sender balance >= gas_limit * gas_price + value.
// TODO: Assert chain ID matches block metadata?
// stack: retdest
global buy_gas:
@ -109,7 +110,7 @@ global process_message_txn_insufficient_balance:
PANIC // TODO
global process_message_txn_return:
// TODO: Return leftover gas?
// TODO: Since there was no code to execute, do we still return leftover gas?
JUMP
global process_message_txn_code_loaded:
@ -159,9 +160,15 @@ global process_message_txn_code_loaded:
MSTORE_GENERAL
// stack: new_ctx, retdest
// TODO: Populate CALLDATA
// Set the new context's gas limit.
%mload_txn_field(@TXN_FIELD_GAS_LIMIT)
PUSH @CTX_METADATA_GAS_LIMIT
PUSH @SEGMENT_CONTEXT_METADATA
DUP4 // new_ctx
MSTORE_GENERAL
// stack: new_ctx, retdest
// TODO: Save parent gas and set child gas
// TODO: Copy TXN_DATA to CALLDATA
// Now, switch to the new context and go to usermode with PC=0.
SET_CONTEXT
@ -171,6 +178,5 @@ global process_message_txn_code_loaded:
global process_message_txn_after_call:
// stack: success, retdest
// TODO: Return leftover gas? Or handled by termination instructions?
POP // Pop success for now. Will go into the receipt when we support that.
JUMP

View File

@ -34,11 +34,11 @@ global sys_codesize:
global sys_codecopy:
PANIC
global sys_gasprice:
PANIC
global sys_extcodesize:
PANIC
global sys_extcodecopy:
PANIC
// stack: kexit_info
%mload_txn_field(@TXN_FIELD_COMPUTED_FEE_PER_GAS)
// stack: gas_price, kexit_info
SWAP1
EXIT_KERNEL
global sys_returndatasize:
PANIC
global sys_returndatacopy:
@ -54,17 +54,28 @@ global sys_timestamp:
global sys_number:
PANIC
global sys_prevrandao:
// TODO: What semantics will this have for Edge?
PANIC
global sys_gaslimit:
// TODO: Return the block's gas limit.
PANIC
global sys_chainid:
PANIC
// TODO: Return the block's chain ID instead of the txn's, even though they should match.
// stack: kexit_info
%mload_txn_field(@TXN_FIELD_CHAIN_ID)
// stack: chain_id, kexit_info
SWAP1
EXIT_KERNEL
global sys_selfbalance:
PANIC
global sys_basefee:
PANIC
global sys_msize:
PANIC
// stack: kexit_info
%mload_context_metadata(@CTX_METADATA_MSIZE)
// stack: msize, kexit_info
SWAP1
EXIT_KERNEL
global sys_gas:
PANIC
global sys_log0:

View File

@ -2,28 +2,36 @@
// RETURN, SELFDESTRUCT, REVERT, and exceptions such as stack underflow.
global sys_stop:
// stack: kexit_info
%refund_leftover_gas
// stack: (empty)
// TODO: Set parent context's CTX_METADATA_RETURNDATA_SIZE to 0.
// TODO: Refund unused gas to parent.
PUSH 1 // success
%jump(terminate_common)
global sys_return:
// stack: kexit_info
%refund_leftover_gas
// stack: (empty)
// TODO: Set parent context's CTX_METADATA_RETURNDATA_SIZE.
// TODO: Copy returned memory to parent context's RETURNDATA (but not if we're returning from a constructor?)
// TODO: Copy returned memory to parent context's memory (as specified in their call instruction)
// TODO: Refund unused gas to parent.
PUSH 1 // success
%jump(terminate_common)
global sys_selfdestruct:
// stack: kexit_info
%consume_gas_const(@GAS_SELFDESTRUCT)
%refund_leftover_gas
// stack: (empty)
// TODO: Destroy account.
// TODO: Refund unused gas to parent.
PUSH 1 // success
%jump(terminate_common)
global sys_revert:
// TODO: Refund unused gas to parent.
// stack: kexit_info
%refund_leftover_gas
// stack: (empty)
// TODO: Revert state changes.
PUSH 0 // success
%jump(terminate_common)
@ -68,3 +76,14 @@ terminate_common:
// stack: parent_pc, success
JUMP
%macro refund_leftover_gas
// stack: kexit_info
%shr_const(192)
// stack: gas_used
%mload_context_metadata(@CTX_METADATA_GAS_LIMIT)
SUB
// stack: leftover_gas
POP // TODO: Refund to caller.
// stack: (empty)
%endmacro

View File

@ -4,6 +4,12 @@
// Post stack: (empty)
global sys_sstore:
// TODO: Assuming a cold zero -> nonzero write for now.
PUSH @GAS_COLDSLOAD
PUSH @GAS_SSET
ADD
%charge_gas
%stack (kexit_info, slot, value) -> (slot, value, kexit_info)
// TODO: If value = 0, delete the key instead of inserting 0.
// stack: slot, value, kexit_info

View File

@ -0,0 +1,139 @@
// Store chain ID = 1. Used for non-legacy txns which always have a chain ID.
%macro store_chain_id_present_true
PUSH 1
%mstore_txn_field(@TXN_FIELD_CHAIN_ID_PRESENT)
%endmacro
// Decode the chain ID and store it.
%macro decode_and_store_chain_id
// stack: pos
%decode_rlp_scalar
%stack (pos, chain_id) -> (chain_id, pos)
%mstore_txn_field(@TXN_FIELD_CHAIN_ID)
// stack: pos
%endmacro
// Decode the nonce and store it.
%macro decode_and_store_nonce
// stack: pos
%decode_rlp_scalar
%stack (pos, nonce) -> (nonce, pos)
%mstore_txn_field(@TXN_FIELD_NONCE)
// stack: pos
%endmacro
// Decode the gas price and, since this is for legacy txns, store it as both
// TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS and TXN_FIELD_MAX_FEE_PER_GAS.
%macro decode_and_store_gas_price_legacy
// stack: pos
%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)
// stack: pos
%endmacro
// Decode the max priority fee and store it.
%macro decode_and_store_max_priority_fee
// stack: pos
%decode_rlp_scalar
%stack (pos, gas_price) -> (gas_price, pos)
%mstore_txn_field(@TXN_FIELD_MAX_PRIORITY_FEE_PER_GAS)
// stack: pos
%endmacro
// Decode the max fee and store it.
%macro decode_and_store_max_fee
// stack: pos
%decode_rlp_scalar
%stack (pos, gas_price) -> (gas_price, pos)
%mstore_txn_field(@TXN_FIELD_MAX_FEE_PER_GAS)
// stack: pos
%endmacro
// Decode the gas limit and store it.
%macro decode_and_store_gas_limit
// stack: pos
%decode_rlp_scalar
%stack (pos, gas_limit) -> (gas_limit, pos)
%mstore_txn_field(@TXN_FIELD_GAS_LIMIT)
// stack: pos
%endmacro
// Decode the "to" field and store it.
%macro decode_and_store_to
// stack: pos
%decode_rlp_scalar
%stack (pos, to) -> (to, pos)
%mstore_txn_field(@TXN_FIELD_TO)
// stack: pos
%endmacro
// Decode the "value" field and store it.
%macro decode_and_store_value
// stack: pos
%decode_rlp_scalar
%stack (pos, value) -> (value, pos)
%mstore_txn_field(@TXN_FIELD_VALUE)
// stack: pos
%endmacro
// Decode the calldata field, store its length in @TXN_FIELD_DATA_LEN, and copy it to @SEGMENT_TXN_DATA.
%macro decode_and_store_data
// stack: pos
// Decode the data length, store it, and compute new_pos after any data.
%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
ADD
// stack: new_pos, old_pos, data_len
// Memcpy the txn data from @SEGMENT_RLP_RAW to @SEGMENT_TXN_DATA.
%stack (new_pos, old_pos, data_len) -> (old_pos, data_len, %%after, new_pos)
PUSH @SEGMENT_RLP_RAW
GET_CONTEXT
PUSH 0
PUSH @SEGMENT_TXN_DATA
GET_CONTEXT
// stack: DST, SRC, data_len, %%after, new_pos
%jump(memcpy)
%%after:
// stack: new_pos
%endmacro
%macro decode_and_store_access_list
// stack: pos
%decode_rlp_list_len
%stack (pos, len) -> (len, pos)
%jumpi(todo_access_lists_not_supported_yet)
// stack: pos
%endmacro
%macro decode_and_store_y_parity
// stack: pos
%decode_rlp_scalar
%stack (pos, y_parity) -> (y_parity, pos)
%mstore_txn_field(@TXN_FIELD_Y_PARITY)
// stack: pos
%endmacro
%macro decode_and_store_r
// stack: pos
%decode_rlp_scalar
%stack (pos, r) -> (r, pos)
%mstore_txn_field(@TXN_FIELD_R)
// stack: pos
%endmacro
%macro decode_and_store_s
// stack: pos
%decode_rlp_scalar
%stack (pos, s) -> (s, pos)
%mstore_txn_field(@TXN_FIELD_S)
// stack: pos
%endmacro
global todo_access_lists_not_supported_yet:
PANIC

View File

@ -19,61 +19,16 @@ global process_type_0_txn:
// We don't actually need the length.
%stack (pos, len) -> (pos)
// Decode the nonce and store it.
// stack: pos, retdest
%decode_rlp_scalar
%stack (pos, nonce) -> (nonce, pos)
%mstore_txn_field(@TXN_FIELD_NONCE)
// Decode the gas price and store it.
// 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.
%decode_and_store_nonce
%decode_and_store_gas_price_legacy
%decode_and_store_gas_limit
%decode_and_store_to
%decode_and_store_value
%decode_and_store_data
// 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, 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, retdest
%decode_rlp_scalar
%stack (pos, to) -> (to, pos)
%mstore_txn_field(@TXN_FIELD_TO)
// Decode the value field and store it.
// 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, 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, retdest
ADD
// stack: new_pos, pos, data_len, retdest
// 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, retdest
%jump(memcpy)
parse_v:
// Parse the "v" field.
// stack: pos, retdest
%decode_rlp_scalar
// stack: pos, v, retdest
@ -93,7 +48,7 @@ parse_v:
%mstore_txn_field(@TXN_FIELD_Y_PARITY)
// stack: pos, retdest
%jump(parse_r)
%jump(decode_r_and_s)
process_v_new_style:
// stack: v, pos, retdest
@ -115,16 +70,12 @@ process_v_new_style:
// stack: y_parity, pos, retdest
%mstore_txn_field(@TXN_FIELD_Y_PARITY)
parse_r:
decode_r_and_s:
// stack: pos, retdest
%decode_rlp_scalar
%stack (pos, r) -> (r, pos)
%mstore_txn_field(@TXN_FIELD_R)
%decode_and_store_r
%decode_and_store_s
// stack: pos, retdest
%decode_rlp_scalar
%stack (pos, s) -> (s)
%mstore_txn_field(@TXN_FIELD_S)
POP
// stack: retdest
type_0_compute_signed_data:

View File

@ -8,4 +8,29 @@
global process_type_1_txn:
// stack: retdest
PANIC // TODO: Unfinished
PUSH 1 // initial pos, skipping over the 0x01 byte
// stack: pos, retdest
%decode_rlp_list_len
// We don't actually need the length.
%stack (pos, len) -> (pos)
%store_chain_id_present_true
%decode_and_store_chain_id
%decode_and_store_nonce
%decode_and_store_gas_price_legacy
%decode_and_store_gas_limit
%decode_and_store_to
%decode_and_store_value
%decode_and_store_data
%decode_and_store_access_list
%decode_and_store_y_parity
%decode_and_store_r
%decode_and_store_s
// stack: pos, retdest
POP
// stack: retdest
// TODO: Check signature.
%jump(process_normalized_txn)

View File

@ -9,4 +9,31 @@
global process_type_2_txn:
// stack: retdest
PANIC // TODO: Unfinished
PUSH 1 // initial pos, skipping over the 0x02 byte
// stack: pos, retdest
%decode_rlp_list_len
// We don't actually need the length.
%stack (pos, len) -> (pos)
// stack: pos, retdest
%store_chain_id_present_true
%decode_and_store_chain_id
%decode_and_store_nonce
%decode_and_store_max_priority_fee
%decode_and_store_max_fee
%decode_and_store_gas_limit
%decode_and_store_to
%decode_and_store_value
%decode_and_store_data
%decode_and_store_access_list
%decode_and_store_y_parity
%decode_and_store_r
%decode_and_store_s
// stack: pos, retdest
POP
// stack: retdest
// TODO: Check signature.
%jump(process_normalized_txn)

View File

@ -26,10 +26,12 @@ pub(crate) enum ContextMetadata {
/// Size of the active main memory.
MSize = 10,
StackSize = 11,
/// The gas limit for this call (not the entire transaction).
GasLimit = 12,
}
impl ContextMetadata {
pub(crate) const COUNT: usize = 12;
pub(crate) const COUNT: usize = 13;
pub(crate) fn all() -> [Self; Self::COUNT] {
[
@ -45,6 +47,7 @@ impl ContextMetadata {
Self::StateTrieCheckpointPointer,
Self::MSize,
Self::StackSize,
Self::GasLimit,
]
}
@ -63,6 +66,7 @@ impl ContextMetadata {
ContextMetadata::StateTrieCheckpointPointer => "CTX_METADATA_STATE_TRIE_CHECKPOINT_PTR",
ContextMetadata::MSize => "CTX_METADATA_MSIZE",
ContextMetadata::StackSize => "CTX_METADATA_STACK_SIZE",
ContextMetadata::GasLimit => "CTX_METADATA_GAS_LIMIT",
}
}
}

View File

@ -67,12 +67,12 @@ pub(crate) fn generate_traces<F: RichField + Extendable<D>, const D: usize>(
inputs: GenerationInputs,
config: &StarkConfig,
timing: &mut TimingTree,
) -> ([Vec<PolynomialValues<F>>; NUM_TABLES], PublicValues) {
) -> anyhow::Result<([Vec<PolynomialValues<F>>; NUM_TABLES], PublicValues)> {
let mut state = GenerationState::<F>::new(inputs.clone(), &KERNEL.code);
generate_bootstrap_kernel::<F>(&mut state);
timed!(timing, "simulate CPU", simulate_cpu(&mut state));
timed!(timing, "simulate CPU", simulate_cpu(&mut state)?);
log::info!(
"Trace lengths (before padding): {:?}",
@ -109,10 +109,12 @@ pub(crate) fn generate_traces<F: RichField + Extendable<D>, const D: usize>(
"convert trace data to tables",
state.traces.into_tables(all_stark, config, timing)
);
(tables, public_values)
Ok((tables, public_values))
}
fn simulate_cpu<F: RichField + Extendable<D>, const D: usize>(state: &mut GenerationState<F>) {
fn simulate_cpu<F: RichField + Extendable<D>, const D: usize>(
state: &mut GenerationState<F>,
) -> anyhow::Result<()> {
let halt_pc0 = KERNEL.global_labels["halt_pc0"];
let halt_pc1 = KERNEL.global_labels["halt_pc1"];
@ -126,11 +128,13 @@ fn simulate_cpu<F: RichField + Extendable<D>, const D: usize>(state: &mut Genera
}
already_in_halt_loop |= in_halt_loop;
transition(state);
transition(state)?;
if already_in_halt_loop && state.traces.clock().is_power_of_two() {
log::info!("CPU trace padded to {} cycles", state.traces.clock());
break;
}
}
Ok(())
}

View File

@ -33,6 +33,15 @@ pub(crate) struct GenerationState<F: Field> {
impl<F: Field> GenerationState<F> {
pub(crate) fn new(inputs: GenerationInputs, kernel_code: &[u8]) -> Self {
log::debug!("Input signed_txns: {:?}", &inputs.signed_txns);
log::debug!("Input state_trie: {:?}", &inputs.tries.state_trie);
log::debug!(
"Input transactions_trie: {:?}",
&inputs.tries.transactions_trie
);
log::debug!("Input receipts_trie: {:?}", &inputs.tries.receipts_trie);
log::debug!("Input storage_tries: {:?}", &inputs.tries.storage_tries);
log::debug!("Input contract_code: {:?}", &inputs.contract_code);
let mpt_prover_inputs = all_mpt_prover_inputs_reversed(&inputs.tries);
let rlp_prover_inputs = all_rlp_prover_inputs_reversed(&inputs.signed_txns);

View File

@ -60,7 +60,7 @@ where
let (traces, public_values) = timed!(
timing,
"generate all traces",
generate_traces(all_stark, inputs, config, timing)
generate_traces(all_stark, inputs, config, timing)?
);
prove_with_traces(all_stark, config, traces, public_values, timing)
}

View File

@ -7,4 +7,5 @@ pub enum ProgramError {
InvalidJumpDestination,
InvalidJumpiDestination,
StackOverflow,
KernelPanic,
}

View File

@ -1,3 +1,4 @@
use anyhow::bail;
use itertools::Itertools;
use log::log_enabled;
use plonky2::field::types::Field;
@ -117,10 +118,13 @@ fn decode(registers: RegistersState, opcode: u8) -> Result<Operation, ProgramErr
(0xa2, _) => Ok(Operation::Syscall(opcode)),
(0xa3, _) => Ok(Operation::Syscall(opcode)),
(0xa4, _) => Ok(Operation::Syscall(opcode)),
(0xa5, _) => panic!(
"Kernel panic at {}",
KERNEL.offset_name(registers.program_counter)
),
(0xa5, _) => {
log::warn!(
"Kernel panic at {}",
KERNEL.offset_name(registers.program_counter)
);
Err(ProgramError::KernelPanic)
}
(0xf0, _) => Ok(Operation::Syscall(opcode)),
(0xf1, _) => Ok(Operation::Syscall(opcode)),
(0xf2, _) => Ok(Operation::Syscall(opcode)),
@ -288,11 +292,11 @@ fn log_kernel_instruction<F: Field>(state: &mut GenerationState<F>, op: Operatio
assert!(pc < KERNEL.code.len(), "Kernel PC is out of range: {}", pc);
}
fn handle_error<F: Field>(_state: &mut GenerationState<F>) {
todo!("generation for exception handling is not implemented");
fn handle_error<F: Field>(_state: &mut GenerationState<F>) -> anyhow::Result<()> {
bail!("TODO: generation for exception handling is not implemented");
}
pub(crate) fn transition<F: Field>(state: &mut GenerationState<F>) {
pub(crate) fn transition<F: Field>(state: &mut GenerationState<F>) -> anyhow::Result<()> {
let checkpoint = state.checkpoint();
let result = try_perform_instruction(state);
@ -301,11 +305,12 @@ pub(crate) fn transition<F: Field>(state: &mut GenerationState<F>) {
state
.memory
.apply_ops(state.traces.mem_ops_since(checkpoint.traces));
Ok(())
}
Err(e) => {
if state.registers.is_kernel {
let offset_name = KERNEL.offset_name(state.registers.program_counter);
panic!("exception in kernel mode at {}: {:?}", offset_name, e);
bail!("exception in kernel mode at {}: {:?}", offset_name, e);
}
state.rollback(checkpoint);
handle_error(state)