mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-05 07:13:08 +00:00
Gas constraints (#880)
* Gas constraints * Bugfix * make test pass post rebase
This commit is contained in:
parent
ac40bd5f5d
commit
f3946f75bf
@ -62,6 +62,9 @@ pub struct CpuColumnsView<T: Copy> {
|
||||
/// If CPU cycle: We're in kernel (privileged) mode.
|
||||
pub is_kernel_mode: T,
|
||||
|
||||
/// If CPU cycle: Gas counter.
|
||||
pub gas: T,
|
||||
|
||||
/// If CPU cycle: flags for EVM instructions (a few cannot be shared; see the comments in
|
||||
/// `OpsColumnsView`).
|
||||
pub op: OpsColumnsView<T>,
|
||||
|
||||
@ -40,7 +40,6 @@ pub struct OpsColumnsView<T: Copy> {
|
||||
pub jump: T, // Note: This column must be 0 when is_cpu_cycle = 0.
|
||||
pub jumpi: T, // Note: This column must be 0 when is_cpu_cycle = 0.
|
||||
pub pc: T,
|
||||
pub gas: T,
|
||||
pub jumpdest: T,
|
||||
pub push: T,
|
||||
pub dup: T,
|
||||
@ -48,13 +47,12 @@ pub struct OpsColumnsView<T: Copy> {
|
||||
// TODO: combine GET_CONTEXT and SET_CONTEXT into one flag
|
||||
pub get_context: T,
|
||||
pub set_context: T,
|
||||
pub consume_gas: T,
|
||||
pub exit_kernel: T,
|
||||
// TODO: combine MLOAD_GENERAL and MSTORE_GENERAL into one flag
|
||||
pub mload_general: T,
|
||||
pub mstore_general: T,
|
||||
|
||||
pub syscall: T,
|
||||
pub syscall: T, // Note: This column must be 0 when is_cpu_cycle = 0.
|
||||
}
|
||||
|
||||
// `u8` is guaranteed to have a `size_of` of 1.
|
||||
|
||||
@ -8,7 +8,7 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer
|
||||
use crate::cpu::columns::{CpuColumnsView, COL_MAP};
|
||||
use crate::cpu::kernel::aggregator::KERNEL;
|
||||
|
||||
const NATIVE_INSTRUCTIONS: [usize; 33] = [
|
||||
const NATIVE_INSTRUCTIONS: [usize; 31] = [
|
||||
COL_MAP.op.add,
|
||||
COL_MAP.op.mul,
|
||||
COL_MAP.op.sub,
|
||||
@ -35,14 +35,12 @@ const NATIVE_INSTRUCTIONS: [usize; 33] = [
|
||||
// not JUMP (need to jump)
|
||||
// not JUMPI (possible need to jump)
|
||||
COL_MAP.op.pc,
|
||||
COL_MAP.op.gas,
|
||||
COL_MAP.op.jumpdest,
|
||||
// not PUSH (need to increment by more than 1)
|
||||
COL_MAP.op.dup,
|
||||
COL_MAP.op.swap,
|
||||
COL_MAP.op.get_context,
|
||||
COL_MAP.op.set_context,
|
||||
COL_MAP.op.consume_gas,
|
||||
// not EXIT_KERNEL (performs a jump)
|
||||
COL_MAP.op.mload_general,
|
||||
COL_MAP.op.mstore_general,
|
||||
|
||||
@ -12,8 +12,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, jumps, membus, memio, modfp254,
|
||||
pc, shift, simple_logic, stack, stack_bounds, syscalls,
|
||||
bootstrap_kernel, contextops, control_flow, decode, dup_swap, gas, jumps, membus, memio,
|
||||
modfp254, pc, shift, simple_logic, stack, stack_bounds, syscalls,
|
||||
};
|
||||
use crate::cross_table_lookup::Column;
|
||||
use crate::memory::segments::Segment;
|
||||
@ -148,6 +148,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, &mut dummy_yield_constr);
|
||||
dup_swap::eval_packed(local_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);
|
||||
memio::eval_packed(local_values, yield_constr);
|
||||
@ -177,6 +178,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, &mut dummy_yield_constr);
|
||||
dup_swap::eval_ext_circuit(builder, local_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);
|
||||
memio::eval_ext_circuit(builder, local_values, yield_constr);
|
||||
|
||||
@ -22,7 +22,7 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP};
|
||||
/// behavior.
|
||||
/// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to decode to
|
||||
/// `is_invalid`. The kernel then verifies that the opcode was _actually_ invalid.
|
||||
const OPCODES: [(u8, usize, bool, usize); 38] = [
|
||||
const OPCODES: [(u8, usize, bool, usize); 36] = [
|
||||
// (start index of block, number of top bits to check (log2), kernel-only, flag column)
|
||||
(0x01, 0, false, COL_MAP.op.add),
|
||||
(0x02, 0, false, COL_MAP.op.mul),
|
||||
@ -51,14 +51,12 @@ const OPCODES: [(u8, usize, bool, usize); 38] = [
|
||||
(0x56, 0, false, COL_MAP.op.jump),
|
||||
(0x57, 0, false, COL_MAP.op.jumpi),
|
||||
(0x58, 0, false, COL_MAP.op.pc),
|
||||
(0x5a, 0, false, COL_MAP.op.gas),
|
||||
(0x5b, 0, false, COL_MAP.op.jumpdest),
|
||||
(0x60, 5, false, COL_MAP.op.push), // 0x60-0x7f
|
||||
(0x80, 4, false, COL_MAP.op.dup), // 0x80-0x8f
|
||||
(0x90, 4, false, COL_MAP.op.swap), // 0x90-0x9f
|
||||
(0xf6, 0, true, COL_MAP.op.get_context),
|
||||
(0xf7, 0, true, COL_MAP.op.set_context),
|
||||
(0xf8, 0, true, COL_MAP.op.consume_gas),
|
||||
(0xf9, 0, true, COL_MAP.op.exit_kernel),
|
||||
(0xfb, 0, true, COL_MAP.op.mload_general),
|
||||
(0xfc, 0, true, COL_MAP.op.mstore_general),
|
||||
|
||||
195
evm/src/cpu/gas.rs
Normal file
195
evm/src/cpu/gas.rs
Normal file
@ -0,0 +1,195 @@
|
||||
use itertools::izip;
|
||||
use plonky2::field::extension::Extendable;
|
||||
use plonky2::field::packed::PackedField;
|
||||
use plonky2::field::types::Field;
|
||||
use plonky2::hash::hash_types::RichField;
|
||||
use plonky2::iop::ext_target::ExtensionTarget;
|
||||
|
||||
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
||||
use crate::cpu::columns::ops::OpsColumnsView;
|
||||
use crate::cpu::columns::CpuColumnsView;
|
||||
|
||||
const KERNEL_ONLY_INSTR: Option<u32> = Some(0);
|
||||
const G_JUMPDEST: Option<u32> = Some(1);
|
||||
const G_BASE: Option<u32> = Some(2);
|
||||
const G_VERYLOW: Option<u32> = Some(3);
|
||||
const G_LOW: Option<u32> = Some(5);
|
||||
const G_MID: Option<u32> = Some(8);
|
||||
const G_HIGH: Option<u32> = Some(10);
|
||||
|
||||
const SIMPLE_OPCODES: OpsColumnsView<Option<u32>> = OpsColumnsView {
|
||||
add: G_VERYLOW,
|
||||
mul: G_LOW,
|
||||
sub: G_VERYLOW,
|
||||
div: G_LOW,
|
||||
mod_: G_LOW,
|
||||
addmod: G_MID,
|
||||
mulmod: G_MID,
|
||||
addfp254: KERNEL_ONLY_INSTR,
|
||||
mulfp254: KERNEL_ONLY_INSTR,
|
||||
subfp254: KERNEL_ONLY_INSTR,
|
||||
submod: KERNEL_ONLY_INSTR,
|
||||
lt: G_VERYLOW,
|
||||
gt: G_VERYLOW,
|
||||
eq: G_VERYLOW,
|
||||
iszero: G_VERYLOW,
|
||||
and: G_VERYLOW,
|
||||
or: G_VERYLOW,
|
||||
xor: G_VERYLOW,
|
||||
not: G_VERYLOW,
|
||||
byte: G_VERYLOW,
|
||||
shl: G_VERYLOW,
|
||||
shr: G_VERYLOW,
|
||||
keccak_general: KERNEL_ONLY_INSTR,
|
||||
prover_input: KERNEL_ONLY_INSTR,
|
||||
pop: G_BASE,
|
||||
jump: G_MID,
|
||||
jumpi: G_HIGH,
|
||||
pc: G_BASE,
|
||||
jumpdest: G_JUMPDEST,
|
||||
push: G_VERYLOW,
|
||||
dup: G_VERYLOW,
|
||||
swap: G_VERYLOW,
|
||||
get_context: KERNEL_ONLY_INSTR,
|
||||
set_context: KERNEL_ONLY_INSTR,
|
||||
exit_kernel: None,
|
||||
mload_general: KERNEL_ONLY_INSTR,
|
||||
mstore_general: KERNEL_ONLY_INSTR,
|
||||
syscall: None,
|
||||
};
|
||||
|
||||
fn eval_packed_accumulate<P: PackedField>(
|
||||
lv: &CpuColumnsView<P>,
|
||||
nv: &CpuColumnsView<P>,
|
||||
yield_constr: &mut ConstraintConsumer<P>,
|
||||
) {
|
||||
// Is it an instruction that we constrain here?
|
||||
// I.e., does it always cost a constant amount of gas?
|
||||
let is_simple_instr: P = SIMPLE_OPCODES
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, maybe_cost)| {
|
||||
// Add flag `lv.op[i]` to the sum if `SIMPLE_OPCODES[i]` is `Some`.
|
||||
maybe_cost.map(|_| lv.op[i])
|
||||
})
|
||||
.sum();
|
||||
let filter = lv.is_cpu_cycle * is_simple_instr;
|
||||
|
||||
// How much gas did we use?
|
||||
let gas_used: P = SIMPLE_OPCODES
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, maybe_cost)| {
|
||||
maybe_cost.map(|cost| P::Scalar::from_canonical_u32(cost) * lv.op[i])
|
||||
})
|
||||
.sum();
|
||||
|
||||
let constr = nv.gas - (lv.gas + gas_used);
|
||||
yield_constr.constraint_transition(filter * constr);
|
||||
|
||||
for (maybe_cost, op_flag) in izip!(SIMPLE_OPCODES.into_iter(), lv.op.into_iter()) {
|
||||
if let Some(cost) = maybe_cost {
|
||||
let cost = P::Scalar::from_canonical_u32(cost);
|
||||
yield_constr
|
||||
.constraint_transition(lv.is_cpu_cycle * op_flag * (nv.gas - lv.gas - cost));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_packed_init<P: PackedField>(
|
||||
lv: &CpuColumnsView<P>,
|
||||
nv: &CpuColumnsView<P>,
|
||||
yield_constr: &mut ConstraintConsumer<P>,
|
||||
) {
|
||||
// `nv` is the first row that executes an instruction.
|
||||
let filter = (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle;
|
||||
// Set initial gas to zero.
|
||||
yield_constr.constraint_transition(filter * nv.gas);
|
||||
}
|
||||
|
||||
pub fn eval_packed<P: PackedField>(
|
||||
lv: &CpuColumnsView<P>,
|
||||
nv: &CpuColumnsView<P>,
|
||||
yield_constr: &mut ConstraintConsumer<P>,
|
||||
) {
|
||||
eval_packed_accumulate(lv, nv, yield_constr);
|
||||
eval_packed_init(lv, nv, yield_constr);
|
||||
}
|
||||
|
||||
fn eval_ext_circuit_accumulate<F: RichField + Extendable<D>, const D: usize>(
|
||||
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
|
||||
lv: &CpuColumnsView<ExtensionTarget<D>>,
|
||||
nv: &CpuColumnsView<ExtensionTarget<D>>,
|
||||
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
||||
) {
|
||||
// Is it an instruction that we constrain here?
|
||||
// I.e., does it always cost a constant amount of gas?
|
||||
let is_simple_instr = SIMPLE_OPCODES.into_iter().enumerate().fold(
|
||||
builder.zero_extension(),
|
||||
|cumul, (i, maybe_cost)| {
|
||||
// Add flag `lv.op[i]` to the sum if `SIMPLE_OPCODES[i]` is `Some`.
|
||||
match maybe_cost {
|
||||
None => cumul,
|
||||
Some(_) => builder.add_extension(lv.op[i], cumul),
|
||||
}
|
||||
},
|
||||
);
|
||||
let filter = builder.mul_extension(lv.is_cpu_cycle, is_simple_instr);
|
||||
|
||||
// How much gas did we use?
|
||||
let gas_used = SIMPLE_OPCODES.into_iter().enumerate().fold(
|
||||
builder.zero_extension(),
|
||||
|cumul, (i, maybe_cost)| match maybe_cost {
|
||||
None => cumul,
|
||||
Some(cost) => {
|
||||
let cost_ext = builder.constant_extension(F::from_canonical_u32(cost).into());
|
||||
builder.mul_add_extension(lv.op[i], cost_ext, cumul)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let constr = {
|
||||
let t = builder.add_extension(lv.gas, gas_used);
|
||||
builder.sub_extension(nv.gas, t)
|
||||
};
|
||||
let filtered_constr = builder.mul_extension(filter, constr);
|
||||
yield_constr.constraint_transition(builder, filtered_constr);
|
||||
|
||||
for (maybe_cost, op_flag) in izip!(SIMPLE_OPCODES.into_iter(), lv.op.into_iter()) {
|
||||
if let Some(cost) = maybe_cost {
|
||||
let filter = builder.mul_extension(lv.is_cpu_cycle, op_flag);
|
||||
let nv_lv_diff = builder.sub_extension(nv.gas, lv.gas);
|
||||
let constr = builder.arithmetic_extension(
|
||||
F::ONE,
|
||||
-F::from_canonical_u32(cost),
|
||||
filter,
|
||||
nv_lv_diff,
|
||||
filter,
|
||||
);
|
||||
yield_constr.constraint_transition(builder, constr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_ext_circuit_init<F: RichField + Extendable<D>, const D: usize>(
|
||||
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
|
||||
lv: &CpuColumnsView<ExtensionTarget<D>>,
|
||||
nv: &CpuColumnsView<ExtensionTarget<D>>,
|
||||
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
||||
) {
|
||||
// `nv` is the first row that executes an instruction.
|
||||
let filter = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle);
|
||||
// Set initial gas to zero.
|
||||
let constr = builder.mul_extension(filter, nv.gas);
|
||||
yield_constr.constraint_transition(builder, constr);
|
||||
}
|
||||
|
||||
pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
|
||||
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
|
||||
lv: &CpuColumnsView<ExtensionTarget<D>>,
|
||||
nv: &CpuColumnsView<ExtensionTarget<D>>,
|
||||
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
||||
) {
|
||||
eval_ext_circuit_accumulate(builder, lv, nv, yield_constr);
|
||||
eval_ext_circuit_init(builder, lv, nv, yield_constr);
|
||||
}
|
||||
@ -15,16 +15,16 @@ 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;
|
||||
|
||||
// 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
|
||||
// kernel to set them to zero).
|
||||
yield_constr.constraint_transition(
|
||||
lv.is_cpu_cycle * lv.op.exit_kernel * (input[0] - nv.program_counter),
|
||||
);
|
||||
yield_constr.constraint_transition(
|
||||
lv.is_cpu_cycle * lv.op.exit_kernel * (input[1] - nv.is_kernel_mode),
|
||||
);
|
||||
// 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,
|
||||
// but we trust the kernel to set them to zero).
|
||||
yield_constr.constraint_transition(filter * (input[0] - nv.program_counter));
|
||||
yield_constr.constraint_transition(filter * (input[1] - nv.is_kernel_mode));
|
||||
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]);
|
||||
}
|
||||
|
||||
pub fn eval_ext_circuit_exit_kernel<F: RichField + Extendable<D>, const D: usize>(
|
||||
@ -47,6 +47,17 @@ pub fn eval_ext_circuit_exit_kernel<F: RichField + Extendable<D>, const D: usize
|
||||
let kernel_constr = builder.sub_extension(input[1], nv.is_kernel_mode);
|
||||
let kernel_constr = builder.mul_extension(filter, kernel_constr);
|
||||
yield_constr.constraint_transition(builder, kernel_constr);
|
||||
|
||||
{
|
||||
let diff = builder.sub_extension(input[6], nv.gas);
|
||||
let constr = builder.mul_extension(filter, diff);
|
||||
yield_constr.constraint_transition(builder, constr);
|
||||
}
|
||||
{
|
||||
// High limb of gas must be 0 for convenient detection of overflow.
|
||||
let constr = builder.mul_extension(filter, input[7]);
|
||||
yield_constr.constraint(builder, constr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_packed_jump_jumpi<P: PackedField>(
|
||||
|
||||
@ -84,7 +84,7 @@ global syscall_jumptable:
|
||||
JUMPTABLE panic // jumpi is implemented natively
|
||||
JUMPTABLE panic // pc is implemented natively
|
||||
JUMPTABLE sys_msize
|
||||
JUMPTABLE panic // gas is implemented natively
|
||||
JUMPTABLE sys_gas
|
||||
JUMPTABLE panic // jumpdest is implemented natively
|
||||
JUMPTABLE panic // 0x5c is an invalid opcode
|
||||
JUMPTABLE panic // 0x5d is an invalid opcode
|
||||
|
||||
@ -35,6 +35,7 @@ global sys_basefee:
|
||||
global sys_sload:
|
||||
global sys_sstore:
|
||||
global sys_msize:
|
||||
global sys_gas:
|
||||
global sys_log0:
|
||||
global sys_log1:
|
||||
global sys_log2:
|
||||
|
||||
@ -345,3 +345,24 @@
|
||||
%endrep
|
||||
// stack: a || b || c || d
|
||||
%endmacro
|
||||
|
||||
// Charge gas.
|
||||
// Arguments:
|
||||
// stack[0]: gas to be charged
|
||||
// stack[1]: syscall info
|
||||
// Returns:
|
||||
// new syscall info
|
||||
%macro charge_gas
|
||||
%shl_const(192)
|
||||
ADD
|
||||
%endmacro
|
||||
|
||||
// Charge gas and exit kernel code.
|
||||
// Arguments:
|
||||
// stack[0]: gas to be charged
|
||||
// stack[1]: syscall info
|
||||
// Returns: nothing
|
||||
%macro charge_gas_and_exit
|
||||
%charge_gas
|
||||
EXIT_KERNEL
|
||||
%endmacro
|
||||
|
||||
@ -5,6 +5,7 @@ pub(crate) mod control_flow;
|
||||
pub mod cpu_stark;
|
||||
pub(crate) mod decode;
|
||||
mod dup_swap;
|
||||
mod gas;
|
||||
mod jumps;
|
||||
pub mod kernel;
|
||||
pub(crate) mod membus;
|
||||
|
||||
@ -80,7 +80,6 @@ const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsColumnsView {
|
||||
pushes: true,
|
||||
disable_other_channels: true,
|
||||
}),
|
||||
gas: None, // TODO
|
||||
jumpdest: Some(StackBehavior {
|
||||
num_pops: 0,
|
||||
pushes: false,
|
||||
@ -95,7 +94,6 @@ const STACK_BEHAVIORS: OpsColumnsView<Option<StackBehavior>> = OpsColumnsView {
|
||||
disable_other_channels: true,
|
||||
}),
|
||||
set_context: None, // SET_CONTEXT is special since it involves the old and the new stack.
|
||||
consume_gas: None, // TODO
|
||||
exit_kernel: Some(StackBehavior {
|
||||
num_pops: 1,
|
||||
pushes: false,
|
||||
|
||||
@ -24,7 +24,7 @@ pub fn eval_packed<P: PackedField>(
|
||||
nv: &CpuColumnsView<P>,
|
||||
yield_constr: &mut ConstraintConsumer<P>,
|
||||
) {
|
||||
let filter = lv.is_cpu_cycle * lv.op.syscall;
|
||||
let filter = lv.op.syscall;
|
||||
|
||||
// Look up the handler in memory
|
||||
let code_segment = P::Scalar::from_canonical_usize(Segment::Code as usize);
|
||||
@ -69,15 +69,20 @@ pub fn eval_packed<P: PackedField>(
|
||||
yield_constr.constraint_transition(filter * (nv.is_kernel_mode - P::ONES));
|
||||
// Maintain current context
|
||||
yield_constr.constraint_transition(filter * (nv.context - lv.context));
|
||||
// Reset gas counter to zero.
|
||||
yield_constr.constraint_transition(filter * nv.gas);
|
||||
|
||||
// This memory channel is constrained in `stack.rs`.
|
||||
let output = lv.mem_channels[NUM_GP_CHANNELS - 1].value;
|
||||
// Push current PC + 1 to stack
|
||||
// Push to stack: current PC + 1 (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7).
|
||||
yield_constr.constraint(filter * (output[0] - (lv.program_counter + P::ONES)));
|
||||
// Push current kernel flag to stack (share register with PC)
|
||||
yield_constr.constraint(filter * (output[1] - lv.is_kernel_mode));
|
||||
yield_constr.constraint(filter * (output[6] - lv.gas));
|
||||
// TODO: Range check `output[6]`.
|
||||
yield_constr.constraint(filter * output[7]); // High limb of gas is zero.
|
||||
|
||||
// Zero the rest of that register
|
||||
for &limb in &output[2..] {
|
||||
for &limb in &output[2..6] {
|
||||
yield_constr.constraint(filter * limb);
|
||||
}
|
||||
}
|
||||
@ -88,7 +93,7 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
|
||||
nv: &CpuColumnsView<ExtensionTarget<D>>,
|
||||
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
||||
) {
|
||||
let filter = builder.mul_extension(lv.is_cpu_cycle, lv.op.syscall);
|
||||
let filter = lv.op.syscall;
|
||||
|
||||
// Look up the handler in memory
|
||||
let code_segment = F::from_canonical_usize(Segment::Code as usize);
|
||||
@ -177,24 +182,40 @@ pub fn eval_ext_circuit<F: RichField + Extendable<D>, const D: usize>(
|
||||
let constr = builder.mul_extension(filter, diff);
|
||||
yield_constr.constraint_transition(builder, constr);
|
||||
}
|
||||
// Reset gas counter to zero.
|
||||
{
|
||||
let constr = builder.mul_extension(filter, nv.gas);
|
||||
yield_constr.constraint_transition(builder, constr);
|
||||
}
|
||||
|
||||
// This memory channel is constrained in `stack.rs`.
|
||||
let output = lv.mem_channels[NUM_GP_CHANNELS - 1].value;
|
||||
// Push current PC + 1 to stack
|
||||
// Push to stack: current PC + 1 (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7).
|
||||
{
|
||||
let pc_plus_1 = builder.add_const_extension(lv.program_counter, F::ONE);
|
||||
let diff = builder.sub_extension(output[0], pc_plus_1);
|
||||
let constr = builder.mul_extension(filter, diff);
|
||||
yield_constr.constraint(builder, constr);
|
||||
}
|
||||
// Push current kernel flag to stack (share register with PC)
|
||||
{
|
||||
let diff = builder.sub_extension(output[1], lv.is_kernel_mode);
|
||||
let constr = builder.mul_extension(filter, diff);
|
||||
yield_constr.constraint(builder, constr);
|
||||
}
|
||||
{
|
||||
let diff = builder.sub_extension(output[6], lv.gas);
|
||||
let constr = builder.mul_extension(filter, diff);
|
||||
yield_constr.constraint(builder, constr);
|
||||
}
|
||||
// TODO: Range check `output[6]`.
|
||||
{
|
||||
// High limb of gas is zero.
|
||||
let constr = builder.mul_extension(filter, output[7]);
|
||||
yield_constr.constraint(builder, constr);
|
||||
}
|
||||
|
||||
// Zero the rest of that register
|
||||
for &limb in &output[2..] {
|
||||
for &limb in &output[2..6] {
|
||||
let constr = builder.mul_extension(filter, limb);
|
||||
yield_constr.constraint(builder, constr);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use ethereum_types::U256;
|
||||
use itertools::Itertools;
|
||||
use plonky2::field::extension::{Extendable, FieldExtension};
|
||||
use plonky2::field::packed::PackedField;
|
||||
@ -175,14 +176,14 @@ impl<F: RichField + Extendable<D>, const D: usize> MemoryStark<F, D> {
|
||||
while next.address.virt - curr.address.virt - 1 > max_rc {
|
||||
let mut dummy_address = curr.address;
|
||||
dummy_address.virt += max_rc + 1;
|
||||
let dummy_read = MemoryOp::new_dummy_read(dummy_address, 0);
|
||||
let dummy_read = MemoryOp::new_dummy_read(dummy_address, 0, U256::zero());
|
||||
memory_ops.push(dummy_read);
|
||||
curr = dummy_read;
|
||||
}
|
||||
} else {
|
||||
while next.timestamp - curr.timestamp > max_rc {
|
||||
let dummy_read =
|
||||
MemoryOp::new_dummy_read(curr.address, curr.timestamp + max_rc);
|
||||
MemoryOp::new_dummy_read(curr.address, curr.timestamp + max_rc, curr.value);
|
||||
memory_ops.push(dummy_read);
|
||||
curr = dummy_read;
|
||||
}
|
||||
@ -311,8 +312,9 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
|
||||
|
||||
// Enumerate purportedly-ordered log.
|
||||
for i in 0..8 {
|
||||
yield_constr
|
||||
.constraint(next_is_read * address_unchanged * (next_values[i] - values[i]));
|
||||
yield_constr.constraint_transition(
|
||||
next_is_read * address_unchanged * (next_values[i] - values[i]),
|
||||
);
|
||||
}
|
||||
|
||||
eval_lookups(vars, yield_constr, RANGE_CHECK_PERMUTED, COUNTER_PERMUTED)
|
||||
@ -433,7 +435,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
|
||||
let value_diff = builder.sub_extension(next_values[i], values[i]);
|
||||
let zero_if_read = builder.mul_extension(address_unchanged, value_diff);
|
||||
let read_constraint = builder.mul_extension(next_is_read, zero_if_read);
|
||||
yield_constr.constraint(builder, read_constraint);
|
||||
yield_constr.constraint_transition(builder, read_constraint);
|
||||
}
|
||||
|
||||
eval_lookups_circuit(
|
||||
|
||||
50
evm/src/witness/gas.rs
Normal file
50
evm/src/witness/gas.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::witness::operation::Operation;
|
||||
|
||||
const KERNEL_ONLY_INSTR: u64 = 0;
|
||||
const G_JUMPDEST: u64 = 1;
|
||||
const G_BASE: u64 = 2;
|
||||
const G_VERYLOW: u64 = 3;
|
||||
const G_LOW: u64 = 5;
|
||||
const G_MID: u64 = 8;
|
||||
const G_HIGH: u64 = 10;
|
||||
|
||||
pub(crate) fn gas_to_charge(op: Operation) -> u64 {
|
||||
use crate::arithmetic::BinaryOperator::*;
|
||||
use crate::witness::operation::Operation::*;
|
||||
match op {
|
||||
Iszero => G_VERYLOW,
|
||||
Not => G_VERYLOW,
|
||||
Byte => G_VERYLOW,
|
||||
Syscall(_) => KERNEL_ONLY_INSTR,
|
||||
Eq => G_VERYLOW,
|
||||
BinaryLogic(_) => G_VERYLOW,
|
||||
BinaryArithmetic(Add) => G_VERYLOW,
|
||||
BinaryArithmetic(Mul) => G_LOW,
|
||||
BinaryArithmetic(Sub) => G_VERYLOW,
|
||||
BinaryArithmetic(Div) => G_LOW,
|
||||
BinaryArithmetic(Mod) => G_LOW,
|
||||
BinaryArithmetic(Lt) => G_VERYLOW,
|
||||
BinaryArithmetic(Gt) => G_VERYLOW,
|
||||
Shl => G_VERYLOW,
|
||||
Shr => G_VERYLOW,
|
||||
BinaryArithmetic(AddFp254) => KERNEL_ONLY_INSTR,
|
||||
BinaryArithmetic(MulFp254) => KERNEL_ONLY_INSTR,
|
||||
BinaryArithmetic(SubFp254) => KERNEL_ONLY_INSTR,
|
||||
TernaryArithmetic(_) => G_MID,
|
||||
KeccakGeneral => KERNEL_ONLY_INSTR,
|
||||
ProverInput => KERNEL_ONLY_INSTR,
|
||||
Pop => G_BASE,
|
||||
Jump => G_MID,
|
||||
Jumpi => G_HIGH,
|
||||
Pc => G_BASE,
|
||||
Jumpdest => G_JUMPDEST,
|
||||
Push(_) => G_VERYLOW,
|
||||
Dup(_) => G_VERYLOW,
|
||||
Swap(_) => G_VERYLOW,
|
||||
GetContext => KERNEL_ONLY_INSTR,
|
||||
SetContext => KERNEL_ONLY_INSTR,
|
||||
ExitKernel => KERNEL_ONLY_INSTR,
|
||||
MloadGeneral => KERNEL_ONLY_INSTR,
|
||||
MstoreGeneral => KERNEL_ONLY_INSTR,
|
||||
}
|
||||
}
|
||||
@ -94,13 +94,13 @@ impl MemoryOp {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_dummy_read(address: MemoryAddress, timestamp: usize) -> Self {
|
||||
pub(crate) fn new_dummy_read(address: MemoryAddress, timestamp: usize, value: U256) -> Self {
|
||||
Self {
|
||||
filter: false,
|
||||
timestamp,
|
||||
address,
|
||||
kind: MemoryOpKind::Read,
|
||||
value: U256::zero(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
mod errors;
|
||||
mod gas;
|
||||
pub(crate) mod memory;
|
||||
mod operation;
|
||||
pub(crate) mod state;
|
||||
|
||||
@ -14,8 +14,8 @@ use crate::memory::segments::Segment;
|
||||
use crate::witness::errors::ProgramError;
|
||||
use crate::witness::memory::{MemoryAddress, MemoryOp};
|
||||
use crate::witness::util::{
|
||||
keccak_sponge_log, mem_read_code_with_log_and_fill, mem_read_gp_with_log_and_fill,
|
||||
mem_write_gp_log_and_fill, stack_pop_with_log_and_fill, stack_push_log_and_fill,
|
||||
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,
|
||||
};
|
||||
use crate::{arithmetic, logic};
|
||||
|
||||
@ -37,14 +37,12 @@ pub(crate) enum Operation {
|
||||
Jump,
|
||||
Jumpi,
|
||||
Pc,
|
||||
Gas,
|
||||
Jumpdest,
|
||||
Push(u8),
|
||||
Dup(u8),
|
||||
Swap(u8),
|
||||
GetContext,
|
||||
SetContext,
|
||||
ConsumeGas,
|
||||
ExitKernel,
|
||||
MloadGeneral,
|
||||
MstoreGeneral,
|
||||
@ -323,8 +321,6 @@ pub(crate) fn generate_push<F: Field>(
|
||||
let code_context = state.registers.code_context();
|
||||
let num_bytes = n as usize + 1;
|
||||
let initial_offset = state.registers.program_counter + 1;
|
||||
let offsets = initial_offset..initial_offset + num_bytes;
|
||||
let mut addrs = offsets.map(|offset| MemoryAddress::new(code_context, Segment::Code, offset));
|
||||
|
||||
// First read val without going through `mem_read_with_log` type methods, so we can pass it
|
||||
// to stack_push_log_and_fill.
|
||||
@ -344,33 +340,9 @@ pub(crate) fn generate_push<F: Field>(
|
||||
let val = U256::from_big_endian(&bytes);
|
||||
let write = stack_push_log_and_fill(state, &mut row, val)?;
|
||||
|
||||
// In the first cycle, we read up to NUM_GP_CHANNELS - 1 bytes, leaving the last GP channel
|
||||
// to push the result.
|
||||
for (i, addr) in (&mut addrs).take(NUM_GP_CHANNELS - 1).enumerate() {
|
||||
let (_, read) = mem_read_gp_with_log_and_fill(i, addr, state, &mut row);
|
||||
state.traces.push_memory(read);
|
||||
}
|
||||
state.traces.push_memory(write);
|
||||
state.traces.push_cpu(row);
|
||||
|
||||
// In any subsequent cycles, we read up to 1 + NUM_GP_CHANNELS bytes.
|
||||
for mut addrs_chunk in &addrs.chunks(1 + NUM_GP_CHANNELS) {
|
||||
let mut row = CpuColumnsView::default();
|
||||
row.is_cpu_cycle = F::ONE;
|
||||
row.op.push = F::ONE;
|
||||
|
||||
let first_addr = addrs_chunk.next().unwrap();
|
||||
let (_, first_read) = mem_read_code_with_log_and_fill(first_addr, state, &mut row);
|
||||
state.traces.push_memory(first_read);
|
||||
|
||||
for (i, addr) in addrs_chunk.enumerate() {
|
||||
let (_, read) = mem_read_gp_with_log_and_fill(i, addr, state, &mut row);
|
||||
state.traces.push_memory(read);
|
||||
}
|
||||
|
||||
state.traces.push_cpu(row);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -530,6 +502,10 @@ pub(crate) fn generate_syscall<F: Field>(
|
||||
state: &mut GenerationState<F>,
|
||||
mut row: CpuColumnsView<F>,
|
||||
) -> Result<(), ProgramError> {
|
||||
if TryInto::<u32>::try_into(state.registers.gas_used).is_err() {
|
||||
panic!();
|
||||
}
|
||||
|
||||
let handler_jumptable_addr = KERNEL.global_labels["syscall_jumptable"];
|
||||
let handler_addr_addr =
|
||||
handler_jumptable_addr + (opcode as usize) * (BYTES_PER_OFFSET as usize);
|
||||
@ -557,12 +533,14 @@ pub(crate) fn generate_syscall<F: Field>(
|
||||
let new_program_counter = handler_addr.as_usize();
|
||||
|
||||
let syscall_info = U256::from(state.registers.program_counter + 1)
|
||||
+ (U256::from(u64::from(state.registers.is_kernel)) << 32);
|
||||
+ (U256::from(u64::from(state.registers.is_kernel)) << 32)
|
||||
+ (U256::from(state.registers.gas_used) << 192);
|
||||
let log_out = stack_push_log_and_fill(state, &mut row, syscall_info)?;
|
||||
|
||||
state.registers.program_counter = new_program_counter;
|
||||
log::debug!("Syscall 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);
|
||||
@ -601,9 +579,14 @@ pub(crate) fn generate_exit_kernel<F: Field>(
|
||||
let is_kernel_mode_val = (kexit_info_u64 >> 32) as u32;
|
||||
assert!(is_kernel_mode_val == 0 || is_kernel_mode_val == 1);
|
||||
let is_kernel_mode = is_kernel_mode_val != 0;
|
||||
let gas_used_val = kexit_info.0[3];
|
||||
if TryInto::<u32>::try_into(gas_used_val).is_err() {
|
||||
panic!();
|
||||
}
|
||||
|
||||
state.registers.program_counter = program_counter;
|
||||
state.registers.is_kernel = is_kernel_mode;
|
||||
state.registers.gas_used = gas_used_val;
|
||||
log::debug!(
|
||||
"Exiting to {}, is_kernel={}",
|
||||
KERNEL.offset_name(program_counter),
|
||||
|
||||
@ -8,6 +8,7 @@ pub struct RegistersState {
|
||||
pub is_kernel: bool,
|
||||
pub stack_len: usize,
|
||||
pub context: usize,
|
||||
pub gas_used: u64,
|
||||
}
|
||||
|
||||
impl RegistersState {
|
||||
@ -27,6 +28,7 @@ impl Default for RegistersState {
|
||||
is_kernel: true,
|
||||
stack_len: 0,
|
||||
context: 0,
|
||||
gas_used: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ use crate::cpu::kernel::aggregator::KERNEL;
|
||||
use crate::generation::state::GenerationState;
|
||||
use crate::memory::segments::Segment;
|
||||
use crate::witness::errors::ProgramError;
|
||||
use crate::witness::gas::gas_to_charge;
|
||||
use crate::witness::memory::MemoryAddress;
|
||||
use crate::witness::operation::*;
|
||||
use crate::witness::state::RegistersState;
|
||||
@ -106,7 +107,6 @@ fn decode(registers: RegistersState, opcode: u8) -> Result<Operation, ProgramErr
|
||||
(0x57, _) => Ok(Operation::Jumpi),
|
||||
(0x58, _) => Ok(Operation::Pc),
|
||||
(0x59, _) => Ok(Operation::Syscall(opcode)),
|
||||
(0x5a, _) => Ok(Operation::Gas),
|
||||
(0x5b, _) => Ok(Operation::Jumpdest),
|
||||
(0x60..=0x7f, _) => Ok(Operation::Push(opcode & 0x1f)),
|
||||
(0x80..=0x8f, _) => Ok(Operation::Dup(opcode & 0xf)),
|
||||
@ -128,7 +128,6 @@ fn decode(registers: RegistersState, opcode: u8) -> Result<Operation, ProgramErr
|
||||
(0xf5, _) => Ok(Operation::Syscall(opcode)),
|
||||
(0xf6, true) => Ok(Operation::GetContext),
|
||||
(0xf7, true) => Ok(Operation::SetContext),
|
||||
(0xf8, true) => Ok(Operation::ConsumeGas),
|
||||
(0xf9, true) => Ok(Operation::ExitKernel),
|
||||
(0xfa, _) => Ok(Operation::Syscall(opcode)),
|
||||
(0xfb, true) => Ok(Operation::MloadGeneral),
|
||||
@ -177,11 +176,9 @@ fn fill_op_flag<F: Field>(op: Operation, row: &mut CpuColumnsView<F>) {
|
||||
Operation::Jump => &mut flags.jump,
|
||||
Operation::Jumpi => &mut flags.jumpi,
|
||||
Operation::Pc => &mut flags.pc,
|
||||
Operation::Gas => &mut flags.gas,
|
||||
Operation::Jumpdest => &mut flags.jumpdest,
|
||||
Operation::GetContext => &mut flags.get_context,
|
||||
Operation::SetContext => &mut flags.set_context,
|
||||
Operation::ConsumeGas => &mut flags.consume_gas,
|
||||
Operation::ExitKernel => &mut flags.exit_kernel,
|
||||
Operation::MloadGeneral => &mut flags.mload_general,
|
||||
Operation::MstoreGeneral => &mut flags.mstore_general,
|
||||
@ -215,11 +212,9 @@ fn perform_op<F: Field>(
|
||||
Operation::Jump => generate_jump(state, row)?,
|
||||
Operation::Jumpi => generate_jumpi(state, row)?,
|
||||
Operation::Pc => generate_pc(state, row)?,
|
||||
Operation::Gas => todo!(),
|
||||
Operation::Jumpdest => generate_jumpdest(state, row)?,
|
||||
Operation::GetContext => generate_get_context(state, row)?,
|
||||
Operation::SetContext => generate_set_context(state, row)?,
|
||||
Operation::ConsumeGas => todo!(),
|
||||
Operation::ExitKernel => generate_exit_kernel(state, row)?,
|
||||
Operation::MloadGeneral => generate_mload_general(state, row)?,
|
||||
Operation::MstoreGeneral => generate_mstore_general(state, row)?,
|
||||
@ -232,6 +227,8 @@ fn perform_op<F: Field>(
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
state.registers.gas_used += gas_to_charge(op);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -242,6 +239,7 @@ fn try_perform_instruction<F: Field>(state: &mut GenerationState<F>) -> Result<(
|
||||
row.context = F::from_canonical_usize(state.registers.context);
|
||||
row.program_counter = F::from_canonical_usize(state.registers.program_counter);
|
||||
row.is_kernel_mode = F::from_bool(state.registers.is_kernel);
|
||||
row.gas = F::from_canonical_u64(state.registers.gas_used);
|
||||
row.stack_len = F::from_canonical_usize(state.registers.stack_len);
|
||||
|
||||
let opcode = read_code_memory(state, &mut row);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user