Error handling

This commit is contained in:
Jacqueline Nabaglo 2023-06-02 15:51:26 -07:00
parent beefc91d73
commit b7220428b3
18 changed files with 627 additions and 130 deletions

View File

@ -7,6 +7,8 @@ use std::mem::{size_of, transmute};
#[derive(Clone, Copy)]
pub(crate) union CpuGeneralColumnsView<T: Copy> {
arithmetic: CpuArithmeticView<T>,
exception: CpuExceptionView<T>,
exit_kernel: CpuExitKernelView<T>,
logic: CpuLogicView<T>,
jumps: CpuJumpsView<T>,
shift: CpuShiftView<T>,
@ -23,6 +25,26 @@ impl<T: Copy> CpuGeneralColumnsView<T> {
unsafe { &mut self.arithmetic }
}
// SAFETY: Each view is a valid interpretation of the underlying array.
pub(crate) fn exception(&self) -> &CpuExceptionView<T> {
unsafe { &self.exception }
}
// SAFETY: Each view is a valid interpretation of the underlying array.
pub(crate) fn exception_mut(&mut self) -> &mut CpuExceptionView<T> {
unsafe { &mut self.exception }
}
// SAFETY: Each view is a valid interpretation of the underlying array.
pub(crate) fn exit_kernel(&self) -> &CpuExitKernelView<T> {
unsafe { &self.exit_kernel }
}
// SAFETY: Each view is a valid interpretation of the underlying array.
pub(crate) fn exit_kernel_mut(&mut self) -> &mut CpuExitKernelView<T> {
unsafe { &mut self.exit_kernel }
}
// SAFETY: Each view is a valid interpretation of the underlying array.
pub(crate) fn logic(&self) -> &CpuLogicView<T> {
unsafe { &self.logic }
@ -89,6 +111,17 @@ pub(crate) struct CpuArithmeticView<T: Copy> {
tmp: T, // temporary, to suppress errors
}
#[derive(Copy, Clone)]
pub(crate) struct CpuExceptionView<T: Copy> {
// Exception code as little-endian bits.
pub(crate) exc_code_bits: [T; 3],
}
#[derive(Copy, Clone)]
pub(crate) struct CpuExitKernelView<T: Copy> {
pub(crate) stack_len_check_aux: T,
}
#[derive(Copy, Clone)]
pub(crate) struct CpuLogicView<T: Copy> {
// Pseudoinverse of `(input0 - input1)`. Used prove that they are unequal. Assumes 32-bit limbs.

View File

@ -47,12 +47,13 @@ pub struct OpsColumnsView<T: Copy> {
// TODO: combine GET_CONTEXT and SET_CONTEXT into one flag
pub get_context: T,
pub set_context: T,
pub exit_kernel: T,
pub exit_kernel: T, // Note: This column must be 0 when is_cpu_cycle = 0.
// TODO: combine MLOAD_GENERAL and MSTORE_GENERAL into one flag
pub mload_general: T,
pub mstore_general: T,
pub syscall: T, // Note: This column must be 0 when is_cpu_cycle = 0.
pub syscall: T, // Note: This column must be 0 when is_cpu_cycle = 0.
pub exception: T, // Note: This column must be 0 when is_cpu_cycle = 0.
}
// `u8` is guaranteed to have a `size_of` of 1.

View File

@ -45,6 +45,7 @@ const NATIVE_INSTRUCTIONS: [usize; 31] = [
COL_MAP.op.mload_general,
COL_MAP.op.mstore_general,
// not SYSCALL (performs a jump)
// not exceptions (also jump)
];
pub(crate) fn get_halt_pcs<F: Field>() -> (F, F) {

View File

@ -13,8 +13,8 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer
use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS};
use crate::cpu::membus::NUM_GP_CHANNELS;
use crate::cpu::{
bootstrap_kernel, contextops, control_flow, decode, dup_swap, gas, jumps, membus, memio,
modfp254, pc, shift, simple_logic, stack, stack_bounds, syscalls,
bootstrap_kernel, contextops, control_flow, decode, dup_swap, exceptions, gas, jumps, membus,
memio, modfp254, pc, shift, simple_logic, stack, stack_bounds, syscalls,
};
use crate::cross_table_lookup::{Column, TableWithColumns};
use crate::memory::segments::Segment;
@ -190,6 +190,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D
control_flow::eval_packed_generic(local_values, next_values, yield_constr);
decode::eval_packed_generic(local_values, yield_constr);
dup_swap::eval_packed(local_values, yield_constr);
exceptions::eval_packed(local_values, next_values, yield_constr);
gas::eval_packed(local_values, next_values, yield_constr);
jumps::eval_packed(local_values, next_values, yield_constr);
membus::eval_packed(local_values, yield_constr);
@ -216,6 +217,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D
control_flow::eval_ext_circuit(builder, local_values, next_values, yield_constr);
decode::eval_ext_circuit(builder, local_values, yield_constr);
dup_swap::eval_ext_circuit(builder, local_values, yield_constr);
exceptions::eval_ext_circuit(builder, local_values, next_values, yield_constr);
gas::eval_ext_circuit(builder, local_values, next_values, yield_constr);
jumps::eval_ext_circuit(builder, local_values, next_values, yield_constr);
membus::eval_ext_circuit(builder, local_values, yield_constr);

View File

@ -62,33 +62,6 @@ const OPCODES: [(u8, usize, bool, usize); 36] = [
(0xfc, 0, true, COL_MAP.op.mstore_general),
];
/// Bitfield of invalid opcodes, in little-endian order.
pub(crate) const fn invalid_opcodes_user() -> [u8; 32] {
let mut res = [u8::MAX; 32]; // Start with all opcodes marked invalid.
let mut i = 0;
while i < OPCODES.len() {
let (block_start, lb_block_len, kernel_only, _) = OPCODES[i];
i += 1;
if kernel_only {
continue;
}
let block_len = 1 << lb_block_len;
let block_start = block_start as usize;
let block_end = block_start + block_len;
let mut j = block_start;
while j < block_end {
let byte = j / u8::BITS as usize;
let bit = j % u8::BITS as usize;
res[byte] &= !(1 << bit); // Mark opcode as invalid by zeroing the bit.
j += 1;
}
}
res
}
pub fn generate<F: RichField>(lv: &mut CpuColumnsView<F>) {
let cycle_filter = lv.is_cpu_cycle;
if cycle_filter == F::ZERO {

View File

@ -56,6 +56,7 @@ const SIMPLE_OPCODES: OpsColumnsView<Option<u32>> = OpsColumnsView {
mload_general: KERNEL_ONLY_INSTR,
mstore_general: KERNEL_ONLY_INSTR,
syscall: None,
exception: None,
};
fn eval_packed_accumulate<P: PackedField>(

View File

@ -15,7 +15,7 @@ pub fn eval_packed_exit_kernel<P: PackedField>(
yield_constr: &mut ConstraintConsumer<P>,
) {
let input = lv.mem_channels[0].value;
let filter = lv.is_cpu_cycle * lv.op.exit_kernel;
let filter = lv.op.exit_kernel;
// If we are executing `EXIT_KERNEL` then we simply restore the program counter, kernel mode
// flag, and gas counter. The middle 4 (32-bit) limbs are ignored (this is not part of the spec,
@ -25,6 +25,19 @@ pub fn eval_packed_exit_kernel<P: PackedField>(
yield_constr.constraint_transition(filter * (input[6] - nv.gas));
// High limb of gas must be 0 for convenient detection of overflow.
yield_constr.constraint(filter * input[7]);
// Check that the syscall did not cause a stack overflow.
// A standard EVM instruction increases the length of the user's stack by at most one. We assume
// that the stack length was valid when we entered the syscall (i.e. it was <= 1024). If the
// syscall caused the stack to overflow, then the stack length is 1025 upon exit into userspace;
// that corresponds to a stack length of 1026 before `EXIT_KERNEL` is executed.
// This constraint ensures that the current stack length is not 1026 if we're returning to user
// mode. If we're remaining in kernel mode upon return from this syscall, the RHS will be 0, so
// the constraint is trivially satisfied by setting `stack_len_check_aux = 0`.
let offset_stack_len = lv.stack_len - P::Scalar::from_canonical_u32(1026);
let lhs = offset_stack_len * lv.general.exit_kernel().stack_len_check_aux;
let rhs = P::ONES - input[1];
yield_constr.constraint(filter * (lhs - rhs));
}
pub fn eval_ext_circuit_exit_kernel<F: RichField + Extendable<D>, const D: usize>(
@ -34,7 +47,7 @@ pub fn eval_ext_circuit_exit_kernel<F: RichField + Extendable<D>, const D: usize
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
let input = lv.mem_channels[0].value;
let filter = builder.mul_extension(lv.is_cpu_cycle, lv.op.exit_kernel);
let filter = lv.op.exit_kernel;
// If we are executing `EXIT_KERNEL` then we simply restore the program counter and kernel mode
// flag. The top 6 (32-bit) limbs are ignored (this is not part of the spec, but we trust the
@ -58,6 +71,16 @@ pub fn eval_ext_circuit_exit_kernel<F: RichField + Extendable<D>, const D: usize
let constr = builder.mul_extension(filter, input[7]);
yield_constr.constraint(builder, constr);
}
// Check that the syscall did not cause a stack overflow.
// See detailed comments in the packed implementation.
{
let stack_len_check_aux = lv.general.exit_kernel().stack_len_check_aux;
let lhs = builder.arithmetic_extension(F::ONE, -F::from_canonical_u32(1026), lv.stack_len, stack_len_check_aux, stack_len_check_aux);
let constr = builder.add_extension(lhs, input[1]);
let constr = builder.mul_sub_extension(filter, constr, filter);
yield_constr.constraint(builder, constr);
}
}
pub fn eval_packed_jump_jumpi<P: PackedField>(

View File

@ -29,9 +29,9 @@ pub(crate) fn combined_kernel() -> Kernel {
include_str!("asm/core/create.asm"),
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/gas.asm"),
include_str!("asm/core/intrinsic_gas.asm"),
include_str!("asm/core/invalid.asm"),
include_str!("asm/core/jumpdest_analysis.asm"),
include_str!("asm/core/nonce.asm"),
include_str!("asm/core/process_txn.asm"),

View File

@ -0,0 +1,290 @@
// These exception codes are arbitary and assigned by us.
global exception_jumptable:
// exception 0: out of gas
JUMPTABLE exc_out_of_gas
// exception 1: invalid opcode
JUMPTABLE exc_invalid_opcode
// exception 2: stack underflow
JUMPTABLE exc_stack_underflow
// exception 3: invalid jump destination
JUMPTABLE exc_invalid_jump_destination
// exception 4: invalid jumpi destination
JUMPTABLE exc_invalid_jumpi_destination
// exception 5: stack overflow
JUMPTABLE exc_stack_overflow
// exceptions 6 and 7: unused
JUMPTABLE panic
JUMPTABLE panic
global exc_out_of_gas:
// TODO
%jump(fault_exception)
global exc_invalid_opcode:
// stack: trap_info
// check if the opcode that triggered this trap is _actually_ invalid
%opcode_from_exp_trap_info
PUSH @INVALID_OPCODES_USER
// stack: invalid_opcodes_user, opcode
SWAP1
// stack: opcode, invalid_opcodes_user
SHR
%mod_const(2)
// stack: opcode_is_invalid
// if the opcode is indeed invalid, then perform an exceptional exit
%jumpi(fault_exception)
// otherwise, panic because this trap should not have been entered
PANIC
global exc_stack_underflow:
// stack: trap_info
%opcode_from_exp_trap_info
// stack: opcode
%add_const(min_stack_len_for_opcode)
%mload_kernel_code
// stack: min_stack_length
%stack_length
// stack: user_stack_length + 1, min_stack_length
GT
// stack: user_stack_length >= min_stack_length
%jumpi(panic)
%jump(fault_exception)
// Debugging note: this will underflow if entered without at least one item on the stack (in
// addition to trap_info). This is expected; it means that the exc_stack_underflow handler should
// have been used instead.
global exc_invalid_jump_destination:
// stack: trap_info, jump_dest
// check that the triggering opcode is indeed JUMP
%opcode_from_exp_trap_info
// stack: opcode, jump_dest
%eq_const(0x56)
// if it's JUMP, then verify that we're actually jumping to an invalid address
%jumpi(invalid_jump_jumpi_destination_common)
// otherwise, panic
PANIC
// Debugging note: this will underflow if entered without at least two items on the stack (in
// addition to trap_info). This is expected; it means that the exc_stack_underflow handler should
// have been used instead.
global exc_invalid_jumpi_destination:
// stack: trap_info, jump_dest, condition
// check that the triggering opcode is indeed JUMPI
%opcode_from_exp_trap_info
// stack: opcode, jump_dest, condition
%sub_const(0x57)
// if it's not JUMPI, then panic
%jumpi(panic)
// otherwise, verify that the condition is nonzero
// stack: jump_dest, condition
SWAP1
// if it's nonzero, then verify that we're actually jumping to an invalid address
%jumpi(invalid_jump_jumpi_destination_common)
// otherwise, panic
PANIC
global invalid_jump_jumpi_destination_common:
// stack: jump_dest
%mload_current(@SEGMENT_JUMPDEST_BITS)
// stack: is_valid_jumpdest
// if valid, then the trap should never have been entered
%jumpi(panic)
%jump(fault_exception)
global exc_stack_overflow:
// stack: trap_info
// check that the triggering opcode _can_ overflow (i.e., it increases the stack size by 1)
%opcode_from_exp_trap_info
PUSH @STACK_LENGTH_INCREASING_OPCODES_USER
// stack: stack_length_increasing_opcodes_user, opcode
SWAP1
// stack: opcode, stack_length_increasing_opcodes_user
SHR
%mod_const(2)
// stack: opcode_increases_stack_length
// if the opcode indeed increases the stack length, then check whether the stack size is at its
// maximum value
%jumpi(exc_stack_overflow_check_stack_length)
// otherwise, panic because this trap should not have been entered
PANIC
global exc_stack_overflow_check_stack_length:
// stack: (empty)
%stack_length
%eq_const(1024)
// if true, stack length is at its maximum allowed value, so the instruction would indeed cause
// an overflow.
%jumpi(fault_exception)
PANIC
// Given the exception trap info, load the opcode that caused the exception
%macro opcode_from_exp_trap_info
%mod_const(0x100000000) // get program counter from low 32 bits of trap_info
%mload_current_code
%endmacro
min_stack_len_for_opcode:
BYTES 0 // 0x00, STOP
BYTES 2 // 0x01, ADD
BYTES 2 // 0x02, MUL
BYTES 2 // 0x03, SUB
BYTES 2 // 0x04, DIV
BYTES 2 // 0x05, SDIV
BYTES 2 // 0x06, MOD
BYTES 2 // 0x07, SMOD
BYTES 2 // 0x08, ADDMOD
BYTES 2 // 0x09, MULMOD
BYTES 2 // 0x0a, EXP
BYTES 2 // 0x0b, SIGNEXTEND
%rep 4 // 0x0c-0x0f, invalid
BYTES 0
%endrep
BYTES 2 // 0x10, LT
BYTES 2 // 0x11, GT
BYTES 2 // 0x12, SLT
BYTES 2 // 0x13, SGT
BYTES 2 // 0x14, EQ
BYTES 1 // 0x15, ISZERO
BYTES 2 // 0x16, AND
BYTES 2 // 0x17, OR
BYTES 2 // 0x18, XOR
BYTES 1 // 0x19, NOT
BYTES 2 // 0x1a, BYTES
BYTES 2 // 0x1b, SHL
BYTES 2 // 0x1c, SHR
BYTES 2 // 0x1d, SAR
BYTES 0 // 0x1e, invalid
BYTES 0 // 0x1f, invalid
BYTES 2 // 0x20, KECCAK256
%rep 15 // 0x21-0x2f, invalid
BYTES 0
%endrep
BYTES 0 // 0x30, ADDRESS
BYTES 1 // 0x31, BALANCE
BYTES 0 // 0x32, ORIGIN
BYTES 0 // 0x33, CALLER
BYTES 0 // 0x34, CALLVALUE
BYTES 1 // 0x35, CALLDATALOAD
BYTES 0 // 0x36, CALLDATASIZE
BYTES 3 // 0x37, CALLDATACOPY
BYTES 0 // 0x38, CODESIZE
BYTES 3 // 0x39, CODECOPY
BYTES 0 // 0x3a, GASPRICE
BYTES 1 // 0x3b, EXTCODESIZE
BYTES 4 // 0x3c, EXTCODECOPY
BYTES 0 // 0x3d, RETURNDATASIZE
BYTES 3 // 0x3e, RETURNDATACOPY
BYTES 1 // 0x3f, EXTCODEHASH
BYTES 1 // 0x40, BLOCKHASH
BYTES 0 // 0x41, COINBASE
BYTES 0 // 0x42, TIMESTAMP
BYTES 0 // 0x43, NUMBER
BYTES 0 // 0x44, DIFFICULTY
BYTES 0 // 0x45, GASLIMIT
BYTES 0 // 0x46, CHAINID
BYTES 0 // 0x47, SELFBALANCE
BYTES 0 // 0x48, BASEFEE
%rep 7 // 0x49-0x4f, invalid
BYTES 0
%endrep
BYTES 1 // 0x50, POP
BYTES 1 // 0x51, MLOAD
BYTES 2 // 0x52, MSTORE
BYTES 2 // 0x53, MSTORE8
BYTES 1 // 0x54, SLOAD
BYTES 2 // 0x55, SSTORE
BYTES 1 // 0x56, JUMP
BYTES 1 // 0x57, JUMPI
BYTES 0 // 0x58, PC
BYTES 0 // 0x59, MSIZE
BYTES 0 // 0x5a, GAS
BYTES 0 // 0x5b, JUMPDEST
%rep 4 // 0x5c-0x5f, invalid
BYTES 0
%endrep
%rep 32 // 0x60-0x7f, PUSH1-PUSH32
BYTES 0
%endrep
BYTES 1 // 0x80, DUP1
BYTES 2 // 0x81, DUP2
BYTES 3 // 0x82, DUP3
BYTES 4 // 0x83, DUP4
BYTES 5 // 0x84, DUP5
BYTES 6 // 0x85, DUP6
BYTES 7 // 0x86, DUP7
BYTES 8 // 0x87, DUP8
BYTES 9 // 0x88, DUP9
BYTES 10 // 0x89, DUP10
BYTES 11 // 0x8a, DUP11
BYTES 12 // 0x8b, DUP12
BYTES 13 // 0x8c, DUP13
BYTES 14 // 0x8d, DUP14
BYTES 15 // 0x8e, DUP15
BYTES 16 // 0x8f, DUP16
BYTES 2 // 0x90, SWAP1
BYTES 3 // 0x91, SWAP2
BYTES 4 // 0x92, SWAP3
BYTES 5 // 0x93, SWAP4
BYTES 6 // 0x94, SWAP5
BYTES 7 // 0x95, SWAP6
BYTES 8 // 0x96, SWAP7
BYTES 9 // 0x97, SWAP8
BYTES 10 // 0x98, SWAP9
BYTES 11 // 0x99, SWAP10
BYTES 12 // 0x9a, SWAP11
BYTES 13 // 0x9b, SWAP12
BYTES 14 // 0x9c, SWAP13
BYTES 15 // 0x9d, SWAP14
BYTES 16 // 0x9e, SWAP15
BYTES 17 // 0x9f, SWAP16
BYTES 2 // 0xa0, LOG0
BYTES 3 // 0xa1, LOG1
BYTES 4 // 0xa2, LOG2
BYTES 5 // 0xa3, LOG3
BYTES 6 // 0xa4, LOG4
%rep 11 // 0xa5-0xaf, invalid
BYTES 0
%endrep
%rep 64 // 0xb0-0xef, invalid
BYTES 0
%endrep
BYTES 3 // 0xf0, CREATE
BYTES 7 // 0xf1, CALL
BYTES 7 // 0xf2, CALLCODE
BYTES 2 // 0xf3, RETURN
BYTES 6 // 0xf4, DELEGATECALL
BYTES 4 // 0xf5, CREATE2
%rep 4 // 0xf6-0xf9, invalid
BYTES 0
%endrep
BYTES 6 // 0xfa, STATICCALL
BYTES 0 // 0xfb, invalid
BYTES 0 // 0xfc, invalid
BYTES 2 // 0xfd, REVERT
BYTES 0 // 0xfe, invalid
BYTES 1 // 0xff, SELFDESTRUCT

View File

@ -1,26 +0,0 @@
global handle_invalid:
// stack: trap_info
// if the kernel is trying to execute an invalid instruction, then we've already screwed up and
// there's no chance of getting a useful proof, so we just panic
DUP1
// stack: trap_info, trap_info
%shr_const(32)
// stack: is_kernel, trap_info
%jumpi(panic)
// check if the opcode that triggered this trap is _actually_ invalid
// stack: program_counter (is_kernel == 0, so trap_info == program_counter)
%mload_current_code
// stack: opcode
PUSH @INVALID_OPCODES_USER
// stack: invalid_opcodes_user, opcode
SWAP1
// stack: opcode, invalid_opcodes_user
SHR
%and_const(1)
// stack: opcode_is_invalid
// if the opcode is indeed invalid, then perform an exceptional exit
%jumpi(fault_exception)
// otherwise, panic because this trap should not have been entered
PANIC

View File

@ -73,3 +73,21 @@
SWAP1 %is_empty
OR
%endmacro
// Gets the size of the stack _before_ the macro is run
// WARNING: this macro is side-effecting. It writes the current stack length to offset
// `CTX_METADATA_STACK_SIZE`, segment `SEGMENT_CONTEXT_METADATA` in the current context. But I can't
// imagine it being an issue unless someone's doing something dumb.
%macro stack_length
// stack: (empty)
GET_CONTEXT
// stack: current_ctx
// It seems odd to switch to the context that we are already in. We do this because SET_CONTEXT
// saves the stack length of the context we are leaving in its metadata segment.
SET_CONTEXT
// stack: (empty)
// We can now read this stack length from memory.
push @CTX_METADATA_STACK_SIZE
%mload_current(@SEGMENT_CONTEXT_METADATA)
// stack: stack_length
%endmacro

View File

@ -0,0 +1,53 @@
use std::ops::RangeInclusive;
use ethereum_types::U256;
/// Create a U256, where the bits at indices inside the specified ranges are set to 1, and all other
/// bits are set to 0.
const fn u256_from_set_index_ranges<const N: usize>(ranges: [RangeInclusive<u8>; N]) -> U256 {
let mut j = 0;
let mut res_limbs = [0u64; 4];
while j < ranges.len() {
let range = &ranges[j];
let mut i = *range.start();
if i > *range.end() {
continue;
}
loop {
let i_lo = i & 0x3f;
let i_hi = i >> 6;
res_limbs[i_hi as usize] |= 1 << i_lo;
if i >= *range.end() {
break;
}
i += 1;
}
j += 1;
}
U256(res_limbs)
}
pub const STACK_LENGTH_INCREASING_OPCODES_USER: U256 = u256_from_set_index_ranges([
0x30..=0x30, // ADDRESS
0x32..=0x34, // ORIGIN, CALLER, CALLVALUE
0x36..=0x36, // CALLDATASIZE
0x38..=0x38, // CODESIZE
0x3a..=0x3a, // GASPRICE
0x3d..=0x3d, // RETURNDATASIZE
0x41..=0x48, // COINBASE, TIMESTAMP, NUMBER, DIFFICULTY, GASLIMIT, CHAINID, SELFBALANCE, BASEFEE
0x58..=0x5a, // PC, MSIZE, GAS
0x60..=0x8f, // PUSH*, DUP*
]);
pub const INVALID_OPCODES_USER: U256 = u256_from_set_index_ranges([
0x0c..=0x0f,
0x1e..=0x1f,
0x21..=0x2f,
0x49..=0x4f,
0x5c..=0x5f,
0xa5..=0xef,
0xf6..=0xf9,
0xfb..=0xfc,
0xfe..=0xfe,
]);

View File

@ -3,7 +3,6 @@ use std::collections::HashMap;
use ethereum_types::U256;
use hex_literal::hex;
use crate::cpu::decode::invalid_opcodes_user;
use crate::cpu::kernel::constants::context_metadata::ContextMetadata;
use crate::cpu::kernel::constants::global_metadata::GlobalMetadata;
use crate::cpu::kernel::constants::journal_entry::JournalEntry;
@ -16,6 +15,7 @@ pub(crate) mod global_metadata;
pub(crate) mod journal_entry;
pub(crate) mod trie_type;
pub(crate) mod txn_fields;
mod exc_bitfields;
/// Constants that are accessible to our kernel assembly code.
pub fn evm_constants() -> HashMap<String, U256> {
@ -76,7 +76,11 @@ pub fn evm_constants() -> HashMap<String, U256> {
}
c.insert(
"INVALID_OPCODES_USER".into(),
U256::from_little_endian(&invalid_opcodes_user()),
exc_bitfields::INVALID_OPCODES_USER,
);
c.insert(
"STACK_LENGTH_INCREASING_OPCODES_USER".into(),
exc_bitfields::STACK_LENGTH_INCREASING_OPCODES_USER,
);
c
}

View File

@ -5,6 +5,7 @@ pub(crate) mod control_flow;
pub mod cpu_stark;
pub(crate) mod decode;
mod dup_swap;
mod exceptions;
mod gas;
mod jumps;
pub mod kernel;

View File

@ -122,6 +122,11 @@ const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsColumnsView {
pushes: true,
disable_other_channels: false,
}),
exception: Some(StackBehavior {
num_pops: 0,
pushes: true,
disable_other_channels: false,
}),
};
fn eval_packed_one<P: PackedField>(

View File

@ -15,7 +15,7 @@ pub(crate) fn gas_to_charge(op: Operation) -> u64 {
Iszero => G_VERYLOW,
Not => G_VERYLOW,
Byte => G_VERYLOW,
Syscall(_) => KERNEL_ONLY_INSTR,
Syscall(_, _, _) => KERNEL_ONLY_INSTR,
Eq => G_VERYLOW,
BinaryLogic(_) => G_VERYLOW,
BinaryArithmetic(Add) => G_VERYLOW,

View File

@ -9,12 +9,13 @@ use crate::cpu::kernel::assembler::BYTES_PER_OFFSET;
use crate::cpu::kernel::constants::context_metadata::ContextMetadata;
use crate::cpu::membus::NUM_GP_CHANNELS;
use crate::cpu::simple_logic::eq_iszero::generate_pinv_diff;
use crate::cpu::stack_bounds::MAX_USER_STACK_SIZE;
use crate::generation::state::GenerationState;
use crate::memory::segments::Segment;
use crate::witness::errors::MemoryError::{ContextTooLarge, SegmentTooLarge, VirtTooLarge};
use crate::witness::errors::ProgramError;
use crate::witness::errors::ProgramError::MemoryError;
use crate::witness::memory::{MemoryAddress, MemoryOp};
use crate::witness::memory::{MemoryAddress, MemoryChannel, MemoryOp, MemoryOpKind};
use crate::witness::util::{
keccak_sponge_log, mem_read_gp_with_log_and_fill, mem_write_gp_log_and_fill,
stack_pop_with_log_and_fill, stack_push_log_and_fill,
@ -28,7 +29,7 @@ pub(crate) enum Operation {
Byte,
Shl,
Shr,
Syscall(u8),
Syscall(u8, usize, bool),
Eq,
BinaryLogic(logic::Op),
BinaryArithmetic(arithmetic::BinaryOperator),
@ -309,7 +310,18 @@ pub(crate) fn generate_set_context<F: Field>(
let new_sp_addr = MemoryAddress::new(new_ctx, Segment::ContextMetadata, sp_field);
let log_write_old_sp = mem_write_gp_log_and_fill(1, old_sp_addr, state, &mut row, sp_to_save);
let (new_sp, log_read_new_sp) = mem_read_gp_with_log_and_fill(2, new_sp_addr, state, &mut row);
let (new_sp, log_read_new_sp) = if old_ctx == new_ctx {
let op = MemoryOp::new(
MemoryChannel::GeneralPurpose(2),
state.traces.clock(),
new_sp_addr,
MemoryOpKind::Read,
sp_to_save,
);
(sp_to_save, op)
} else {
mem_read_gp_with_log_and_fill(2, new_sp_addr, state, &mut row)
};
state.registers.context = new_ctx;
state.registers.stack_len = new_sp.as_usize();
@ -506,6 +518,8 @@ pub(crate) fn generate_shr<F: Field>(
pub(crate) fn generate_syscall<F: Field>(
opcode: u8,
stack_values_read: usize,
stack_len_increased: bool,
state: &mut GenerationState<F>,
mut row: CpuColumnsView<F>,
) -> Result<(), ProgramError> {
@ -513,6 +527,13 @@ pub(crate) fn generate_syscall<F: Field>(
panic!();
}
if state.registers.stack_len < stack_values_read {
return Err(ProgramError::StackUnderflow);
}
if stack_len_increased && !state.registers.is_kernel && state.registers.stack_len >= MAX_USER_STACK_SIZE {
return Err(ProgramError::StackOverflow);
}
let handler_jumptable_addr = KERNEL.global_labels["syscall_jumptable"];
let handler_addr_addr =
handler_jumptable_addr + (opcode as usize) * (BYTES_PER_OFFSET as usize);
@ -591,6 +612,17 @@ pub(crate) fn generate_exit_kernel<F: Field>(
panic!();
}
if is_kernel_mode {
row.general.exit_kernel_mut().stack_len_check_aux = F::ZERO;
} else {
let diff = F::from_canonical_usize(state.registers.stack_len + 1) - F::from_canonical_u32(1026);
if let Some(inv) = diff.try_inverse() {
row.general.exit_kernel_mut().stack_len_check_aux = inv;
} else {
panic!("stack overflow; should have been caught before entering the syscall")
}
}
state.registers.program_counter = program_counter;
state.registers.is_kernel = is_kernel_mode;
state.registers.gas_used = gas_used_val;
@ -659,3 +691,64 @@ pub(crate) fn generate_mstore_general<F: Field>(
state.traces.push_cpu(row);
Ok(())
}
pub(crate) fn generate_exception<F: Field>(
exc_code: u8,
state: &mut GenerationState<F>,
mut row: CpuColumnsView<F>,
) -> Result<(), ProgramError> {
if TryInto::<u32>::try_into(state.registers.gas_used).is_err() {
panic!();
}
row.stack_len_bounds_aux = (row.stack_len + F::ONE).inverse();
row.general.exception_mut().exc_code_bits = [
F::from_bool(exc_code & 1 != 0),
F::from_bool(exc_code & 2 != 0),
F::from_bool(exc_code & 4 != 0),
];
let handler_jumptable_addr = KERNEL.global_labels["exception_jumptable"];
let handler_addr_addr =
handler_jumptable_addr + (exc_code as usize) * (BYTES_PER_OFFSET as usize);
assert_eq!(BYTES_PER_OFFSET, 3, "Code below assumes 3 bytes per offset");
let (handler_addr0, log_in0) = mem_read_gp_with_log_and_fill(
0,
MemoryAddress::new(0, Segment::Code, handler_addr_addr),
state,
&mut row,
);
let (handler_addr1, log_in1) = mem_read_gp_with_log_and_fill(
1,
MemoryAddress::new(0, Segment::Code, handler_addr_addr + 1),
state,
&mut row,
);
let (handler_addr2, log_in2) = mem_read_gp_with_log_and_fill(
2,
MemoryAddress::new(0, Segment::Code, handler_addr_addr + 2),
state,
&mut row,
);
let handler_addr = (handler_addr0 << 16) + (handler_addr1 << 8) + handler_addr2;
let new_program_counter = handler_addr.as_usize();
let exc_info = U256::from(state.registers.program_counter)
+ (U256::from(state.registers.gas_used) << 192);
let log_out = stack_push_log_and_fill(state, &mut row, exc_info)?;
state.registers.program_counter = new_program_counter;
log::debug!("Exception to {}", KERNEL.offset_name(new_program_counter));
state.registers.is_kernel = true;
state.registers.gas_used = 0;
state.traces.push_memory(log_in0);
state.traces.push_memory(log_in1);
state.traces.push_memory(log_in2);
state.traces.push_memory(log_out);
state.traces.push_cpu(row);
Ok(())
}

View File

@ -29,22 +29,22 @@ fn read_code_memory<F: Field>(state: &mut GenerationState<F>, row: &mut CpuColum
fn decode(registers: RegistersState, opcode: u8) -> Result<Operation, ProgramError> {
match (opcode, registers.is_kernel) {
(0x00, _) => Ok(Operation::Syscall(opcode)),
(0x00, _) => Ok(Operation::Syscall(opcode, 0, false)), // STOP
(0x01, _) => Ok(Operation::BinaryArithmetic(arithmetic::BinaryOperator::Add)),
(0x02, _) => Ok(Operation::BinaryArithmetic(arithmetic::BinaryOperator::Mul)),
(0x03, _) => Ok(Operation::BinaryArithmetic(arithmetic::BinaryOperator::Sub)),
(0x04, _) => Ok(Operation::BinaryArithmetic(arithmetic::BinaryOperator::Div)),
(0x05, _) => Ok(Operation::Syscall(opcode)),
(0x05, _) => Ok(Operation::Syscall(opcode, 2, false)), // SDIV
(0x06, _) => Ok(Operation::BinaryArithmetic(arithmetic::BinaryOperator::Mod)),
(0x07, _) => Ok(Operation::Syscall(opcode)),
(0x07, _) => Ok(Operation::Syscall(opcode, 2, false)), // SMOD
(0x08, _) => Ok(Operation::TernaryArithmetic(
arithmetic::TernaryOperator::AddMod,
)),
(0x09, _) => Ok(Operation::TernaryArithmetic(
arithmetic::TernaryOperator::MulMod,
)),
(0x0a, _) => Ok(Operation::Syscall(opcode)),
(0x0b, _) => Ok(Operation::Syscall(opcode)),
(0x0a, _) => Ok(Operation::Syscall(opcode, 2, false)), // EXP
(0x0b, _) => Ok(Operation::Syscall(opcode, 2, false)), // SIGNEXTEND
(0x0c, true) => Ok(Operation::BinaryArithmetic(
arithmetic::BinaryOperator::AddFp254,
)),
@ -59,8 +59,8 @@ fn decode(registers: RegistersState, opcode: u8) -> Result<Operation, ProgramErr
)),
(0x10, _) => Ok(Operation::BinaryArithmetic(arithmetic::BinaryOperator::Lt)),
(0x11, _) => Ok(Operation::BinaryArithmetic(arithmetic::BinaryOperator::Gt)),
(0x12, _) => Ok(Operation::Syscall(opcode)),
(0x13, _) => Ok(Operation::Syscall(opcode)),
(0x12, _) => Ok(Operation::Syscall(opcode, 2, false)), // SLT
(0x13, _) => Ok(Operation::Syscall(opcode, 2, false)), // SGT
(0x14, _) => Ok(Operation::Eq),
(0x15, _) => Ok(Operation::Iszero),
(0x16, _) => Ok(Operation::BinaryLogic(logic::Op::And)),
@ -70,76 +70,76 @@ fn decode(registers: RegistersState, opcode: u8) -> Result<Operation, ProgramErr
(0x1a, _) => Ok(Operation::Byte),
(0x1b, _) => Ok(Operation::Shl),
(0x1c, _) => Ok(Operation::Shr),
(0x1d, _) => Ok(Operation::Syscall(opcode)),
(0x20, _) => Ok(Operation::Syscall(opcode)),
(0x1d, _) => Ok(Operation::Syscall(opcode, 2, false)), // SAR
(0x20, _) => Ok(Operation::Syscall(opcode, 2, false)), // KECCAK256
(0x21, true) => Ok(Operation::KeccakGeneral),
(0x30, _) => Ok(Operation::Syscall(opcode)),
(0x31, _) => Ok(Operation::Syscall(opcode)),
(0x32, _) => Ok(Operation::Syscall(opcode)),
(0x33, _) => Ok(Operation::Syscall(opcode)),
(0x34, _) => Ok(Operation::Syscall(opcode)),
(0x35, _) => Ok(Operation::Syscall(opcode)),
(0x36, _) => Ok(Operation::Syscall(opcode)),
(0x37, _) => Ok(Operation::Syscall(opcode)),
(0x38, _) => Ok(Operation::Syscall(opcode)),
(0x39, _) => Ok(Operation::Syscall(opcode)),
(0x3a, _) => Ok(Operation::Syscall(opcode)),
(0x3b, _) => Ok(Operation::Syscall(opcode)),
(0x3c, _) => Ok(Operation::Syscall(opcode)),
(0x3d, _) => Ok(Operation::Syscall(opcode)),
(0x3e, _) => Ok(Operation::Syscall(opcode)),
(0x3f, _) => Ok(Operation::Syscall(opcode)),
(0x40, _) => Ok(Operation::Syscall(opcode)),
(0x41, _) => Ok(Operation::Syscall(opcode)),
(0x42, _) => Ok(Operation::Syscall(opcode)),
(0x43, _) => Ok(Operation::Syscall(opcode)),
(0x44, _) => Ok(Operation::Syscall(opcode)),
(0x45, _) => Ok(Operation::Syscall(opcode)),
(0x46, _) => Ok(Operation::Syscall(opcode)),
(0x47, _) => Ok(Operation::Syscall(opcode)),
(0x48, _) => Ok(Operation::Syscall(opcode)),
(0x49, _) => Ok(Operation::ProverInput),
(0x30, _) => Ok(Operation::Syscall(opcode, 0, true)), // ADDRESS
(0x31, _) => Ok(Operation::Syscall(opcode, 1, false)), // BALANCE
(0x32, _) => Ok(Operation::Syscall(opcode, 0, true)), // ORIGIN
(0x33, _) => Ok(Operation::Syscall(opcode, 0, true)), // CALLER
(0x34, _) => Ok(Operation::Syscall(opcode, 0, true)), // CALLVALUE
(0x35, _) => Ok(Operation::Syscall(opcode, 1, false)), // CALLDATALOAD
(0x36, _) => Ok(Operation::Syscall(opcode, 0, true)), // CALLDATASIZE
(0x37, _) => Ok(Operation::Syscall(opcode, 3, false)), // CALLDATACOPY
(0x38, _) => Ok(Operation::Syscall(opcode, 0, true)), // CODESIZE
(0x39, _) => Ok(Operation::Syscall(opcode, 3, false)), // CODECOPY
(0x3a, _) => Ok(Operation::Syscall(opcode, 0, true)), // GASPRICE
(0x3b, _) => Ok(Operation::Syscall(opcode, 1, false)), // EXTCODESIZE
(0x3c, _) => Ok(Operation::Syscall(opcode, 4, false)), // EXTCODECOPY
(0x3d, _) => Ok(Operation::Syscall(opcode, 0, true)), // RETURNDATASIZE
(0x3e, _) => Ok(Operation::Syscall(opcode, 3, false)), // RETURNDATACOPY
(0x3f, _) => Ok(Operation::Syscall(opcode, 1, false)), // EXTCODEHASH
(0x40, _) => Ok(Operation::Syscall(opcode, 1, false)), // BLOCKHASH
(0x41, _) => Ok(Operation::Syscall(opcode, 0, true)), // COINBASE
(0x42, _) => Ok(Operation::Syscall(opcode, 0, true)), // TIMESTAMP
(0x43, _) => Ok(Operation::Syscall(opcode, 0, true)), // NUMBER
(0x44, _) => Ok(Operation::Syscall(opcode, 0, true)), // DIFFICULTY
(0x45, _) => Ok(Operation::Syscall(opcode, 0, true)), // GASLIMIT
(0x46, _) => Ok(Operation::Syscall(opcode, 0, true)), // CHAINID
(0x47, _) => Ok(Operation::Syscall(opcode, 0, true)), // SELFBALANCE
(0x48, _) => Ok(Operation::Syscall(opcode, 0, true)), // BASEFEE
(0x49, true) => Ok(Operation::ProverInput),
(0x50, _) => Ok(Operation::Pop),
(0x51, _) => Ok(Operation::Syscall(opcode)),
(0x52, _) => Ok(Operation::Syscall(opcode)),
(0x53, _) => Ok(Operation::Syscall(opcode)),
(0x54, _) => Ok(Operation::Syscall(opcode)),
(0x55, _) => Ok(Operation::Syscall(opcode)),
(0x51, _) => Ok(Operation::Syscall(opcode, 1, false)), // MLOAD
(0x52, _) => Ok(Operation::Syscall(opcode, 2, false)), // MSTORE
(0x53, _) => Ok(Operation::Syscall(opcode, 2, false)), // MSTORE8
(0x54, _) => Ok(Operation::Syscall(opcode, 1, false)), // SLOAD
(0x55, _) => Ok(Operation::Syscall(opcode, 2, false)), // SSTORE
(0x56, _) => Ok(Operation::Jump),
(0x57, _) => Ok(Operation::Jumpi),
(0x58, _) => Ok(Operation::Pc),
(0x59, _) => Ok(Operation::Syscall(opcode)),
(0x5a, _) => Ok(Operation::Syscall(opcode)),
(0x59, _) => Ok(Operation::Syscall(opcode, 0, true)), // MSIZE
(0x5a, _) => Ok(Operation::Syscall(opcode, 0, true)), // GAS
(0x5b, _) => Ok(Operation::Jumpdest),
(0x60..=0x7f, _) => Ok(Operation::Push(opcode & 0x1f)),
(0x80..=0x8f, _) => Ok(Operation::Dup(opcode & 0xf)),
(0x90..=0x9f, _) => Ok(Operation::Swap(opcode & 0xf)),
(0xa0, _) => Ok(Operation::Syscall(opcode)),
(0xa1, _) => Ok(Operation::Syscall(opcode)),
(0xa2, _) => Ok(Operation::Syscall(opcode)),
(0xa3, _) => Ok(Operation::Syscall(opcode)),
(0xa4, _) => Ok(Operation::Syscall(opcode)),
(0xa5, _) => {
(0xa0, _) => Ok(Operation::Syscall(opcode, 2, false)), // LOG0
(0xa1, _) => Ok(Operation::Syscall(opcode, 3, false)), // LOG1
(0xa2, _) => Ok(Operation::Syscall(opcode, 4, false)), // LOG2
(0xa3, _) => Ok(Operation::Syscall(opcode, 5, false)), // LOG3
(0xa4, _) => Ok(Operation::Syscall(opcode, 6, false)), // LOG4
(0xa5, true) => {
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)),
(0xf3, _) => Ok(Operation::Syscall(opcode)),
(0xf4, _) => Ok(Operation::Syscall(opcode)),
(0xf5, _) => Ok(Operation::Syscall(opcode)),
(0xf0, _) => Ok(Operation::Syscall(opcode, 3, false)), // CREATE
(0xf1, _) => Ok(Operation::Syscall(opcode, 7, false)), // CALL
(0xf2, _) => Ok(Operation::Syscall(opcode, 7, false)), // CALLCODE
(0xf3, _) => Ok(Operation::Syscall(opcode, 2, false)), // RETURN
(0xf4, _) => Ok(Operation::Syscall(opcode, 6, false)), // DELEGATECALL
(0xf5, _) => Ok(Operation::Syscall(opcode, 4, false)), // CREATE2
(0xf6, true) => Ok(Operation::GetContext),
(0xf7, true) => Ok(Operation::SetContext),
(0xf9, true) => Ok(Operation::ExitKernel),
(0xfa, _) => Ok(Operation::Syscall(opcode)),
(0xfa, _) => Ok(Operation::Syscall(opcode, 6, false)), // STATICCALL
(0xfb, true) => Ok(Operation::MloadGeneral),
(0xfc, true) => Ok(Operation::MstoreGeneral),
(0xfd, _) => Ok(Operation::Syscall(opcode)),
(0xff, _) => Ok(Operation::Syscall(opcode)),
(0xfd, _) => Ok(Operation::Syscall(opcode, 2, false)), // REVERT
(0xff, _) => Ok(Operation::Syscall(opcode, 1, false)), // SELFDESTRUCT
_ => {
log::warn!("Invalid opcode: {}", opcode);
Err(ProgramError::InvalidOpcode)
@ -156,7 +156,7 @@ fn fill_op_flag<F: Field>(op: Operation, row: &mut CpuColumnsView<F>) {
Operation::Iszero => &mut flags.iszero,
Operation::Not => &mut flags.not,
Operation::Byte => &mut flags.byte,
Operation::Syscall(_) => &mut flags.syscall,
Operation::Syscall(_, _, _) => &mut flags.syscall,
Operation::Eq => &mut flags.eq,
Operation::BinaryLogic(logic::Op::And) => &mut flags.and,
Operation::BinaryLogic(logic::Op::Or) => &mut flags.or,
@ -205,7 +205,7 @@ fn perform_op<F: Field>(
Operation::Byte => generate_byte(state, row)?,
Operation::Shl => generate_shl(state, row)?,
Operation::Shr => generate_shr(state, row)?,
Operation::Syscall(opcode) => generate_syscall(opcode, state, row)?,
Operation::Syscall(opcode, stack_values_read, stack_len_increased) => generate_syscall(opcode, stack_values_read, stack_len_increased, state, row)?,
Operation::Eq => generate_eq(state, row)?,
Operation::BinaryLogic(binary_logic_op) => {
generate_binary_logic_op(binary_logic_op, state, row)?
@ -227,7 +227,7 @@ fn perform_op<F: Field>(
};
state.registers.program_counter += match op {
Operation::Syscall(_) | Operation::ExitKernel => 0,
Operation::Syscall(_, _, _) | Operation::ExitKernel => 0,
Operation::Push(n) => n as usize + 2,
Operation::Jump | Operation::Jumpi => 0,
_ => 1,
@ -238,7 +238,10 @@ fn perform_op<F: Field>(
Ok(())
}
fn try_perform_instruction<F: Field>(state: &mut GenerationState<F>) -> Result<(), ProgramError> {
/// Row that has the correct values for system registers and the code channel, but is otherwise
/// blank. It fulfills the constraints that are common to successful operations and the exception
/// operation. It also returns the opcode.
fn base_row<F: Field>(state: &mut GenerationState<F>) -> (CpuColumnsView<F>, u8) {
let mut row: CpuColumnsView<F> = CpuColumnsView::default();
row.is_cpu_cycle = F::ONE;
row.clock = F::from_canonical_usize(state.traces.clock());
@ -249,6 +252,11 @@ fn try_perform_instruction<F: Field>(state: &mut GenerationState<F>) -> Result<(
row.stack_len = F::from_canonical_usize(state.registers.stack_len);
let opcode = read_code_memory(state, &mut row);
(row, opcode)
}
fn try_perform_instruction<F: Field>(state: &mut GenerationState<F>) -> Result<(), ProgramError> {
let (mut row, opcode) = base_row(state);
let op = decode(state.registers, opcode)?;
if state.registers.is_kernel {
@ -309,8 +317,25 @@ 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>) -> anyhow::Result<()> {
bail!("TODO: generation for exception handling is not implemented");
fn handle_error<F: Field>(state: &mut GenerationState<F>, err: ProgramError) -> anyhow::Result<()> {
let exc_code: u8 = match err {
ProgramError::OutOfGas => 0,
ProgramError::InvalidOpcode => 1,
ProgramError::StackUnderflow => 2,
ProgramError::InvalidJumpDestination => 3,
ProgramError::InvalidJumpiDestination => 4,
ProgramError::StackOverflow => 5,
_ => bail!("TODO: figure out what to do with this...")
};
let checkpoint = state.checkpoint();
let (row, _) = base_row(state);
generate_exception(exc_code, state, row).map_err(|_| anyhow::Error::msg("error handling errored..."))?;
state.memory
.apply_ops(state.traces.mem_ops_since(checkpoint.traces));
Ok(())
}
pub(crate) fn transition<F: Field>(state: &mut GenerationState<F>) -> anyhow::Result<()> {
@ -336,7 +361,7 @@ pub(crate) fn transition<F: Field>(state: &mut GenerationState<F>) -> anyhow::Re
);
}
state.rollback(checkpoint);
handle_error(state)
handle_error(state, e)
}
}
}