From 96bf2d49f5fa7d007d293a76a6928f38be4c542a Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 11 Oct 2023 18:36:02 -0600 Subject: [PATCH 01/19] simplifications and fixes --- Makefile | 7 ++-- arbitrator/Cargo.lock | 5 ++- arbitrator/arbutil/Cargo.toml | 1 + arbitrator/arbutil/src/math.rs | 20 +++++++++ arbitrator/jit/src/user/mod.rs | 6 +-- arbitrator/jit/src/wavmio.rs | 24 +++++------ arbitrator/prover/src/binary.rs | 59 +++++++++++++-------------- arbitrator/prover/src/host.rs | 33 +++++++-------- arbitrator/prover/src/lib.rs | 59 ++++++++++++++------------- arbitrator/prover/src/machine.rs | 39 ++++-------------- arbitrator/prover/src/programs/mod.rs | 1 + arbitrator/prover/src/value.rs | 11 ++++- arbitrator/stylus/src/lib.rs | 2 +- arbitrator/stylus/src/native.rs | 10 ++--- arbitrator/wasm-libraries/Cargo.lock | 5 ++- go-ethereum | 2 +- system_tests/program_test.go | 4 -- system_tests/stylus_test.go | 4 ++ validator/server_api/json.go | 6 +-- validator/server_arb/machine.go | 8 ++-- validator/server_jit/jit_machine.go | 2 +- 21 files changed, 155 insertions(+), 153 deletions(-) diff --git a/Makefile b/Makefile index c9bf2316b..1082e7a12 100644 --- a/Makefile +++ b/Makefile @@ -259,17 +259,17 @@ $(replay_wasm): $(DEP_PREDICATE) $(go_source) .make/solgen mkdir -p `dirname $(replay_wasm)` GOOS=js GOARCH=wasm go build -o $@ ./cmd/replay/... -$(prover_bin): $(DEP_PREDICATE) $(rust_prover_files) $(output_latest)/forward_stub.wasm +$(prover_bin): $(DEP_PREDICATE) $(rust_prover_files) mkdir -p `dirname $(prover_bin)` cargo build --manifest-path arbitrator/Cargo.toml --release --bin prover ${CARGOFLAGS} install arbitrator/target/release/prover $@ -$(arbitrator_stylus_lib): $(DEP_PREDICATE) $(stylus_files) $(output_latest)/forward_stub.wasm +$(arbitrator_stylus_lib): $(DEP_PREDICATE) $(stylus_files) mkdir -p `dirname $(arbitrator_stylus_lib)` cargo build --manifest-path arbitrator/Cargo.toml --release --lib -p stylus ${CARGOFLAGS} install arbitrator/target/release/libstylus.a $@ -$(arbitrator_jit): $(DEP_PREDICATE) .make/cbrotli-lib $(jit_files) $(output_latest)/forward_stub.wasm +$(arbitrator_jit): $(DEP_PREDICATE) .make/cbrotli-lib $(jit_files) mkdir -p `dirname $(arbitrator_jit)` cargo build --manifest-path arbitrator/Cargo.toml --release -p jit ${CARGOFLAGS} install arbitrator/target/release/jit $@ @@ -352,7 +352,6 @@ $(output_latest)/forward.wasm: $(DEP_PREDICATE) $(wasm_lib)/user-host/forward.wa wat2wasm $(wasm_lib)/user-host/forward.wat -o $@ $(output_latest)/forward_stub.wasm: $(DEP_PREDICATE) $(wasm_lib)/user-host/forward_stub.wat .make/machines - mkdir -p $(output_latest) wat2wasm $(wasm_lib)/user-host/forward_stub.wat -o $@ $(output_latest)/machine.wavm.br: $(DEP_PREDICATE) $(prover_bin) $(arbitrator_wasm_libs) $(replay_wasm) diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index f0de36f23..ef9116953 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -65,6 +65,7 @@ dependencies = [ "digest 0.9.0", "eyre", "hex", + "num-traits", "serde", "sha3 0.10.8", "siphasher", @@ -1097,9 +1098,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] diff --git a/arbitrator/arbutil/Cargo.toml b/arbitrator/arbutil/Cargo.toml index f48556548..e2a3a750e 100644 --- a/arbitrator/arbutil/Cargo.toml +++ b/arbitrator/arbutil/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" digest = "0.9.0" eyre = "0.6.5" hex = "0.4.3" +num-traits = "0.2.17" sha3 = "0.10.5" siphasher = "0.3.10" wasmparser = "0.83" diff --git a/arbitrator/arbutil/src/math.rs b/arbitrator/arbutil/src/math.rs index 2e8631214..a7556974d 100644 --- a/arbitrator/arbutil/src/math.rs +++ b/arbitrator/arbutil/src/math.rs @@ -1,6 +1,7 @@ // Copyright 2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE +use num_traits::{ops::saturating::SaturatingAdd, Zero}; use std::ops::{BitAnd, Sub}; /// Checks if a number is a power of 2. @@ -13,3 +14,22 @@ where } value & (value - 1.into()) == 0.into() } + +/// Calculates a sum, saturating in cases of overflow. +pub trait SaturatingSum { + type Number; + + fn saturating_sum(self) -> Self::Number; +} + +impl SaturatingSum for I +where + I: Iterator, + T: SaturatingAdd + Zero, +{ + type Number = T; + + fn saturating_sum(self) -> Self::Number { + self.fold(T::zero(), |acc, x| acc.saturating_add(&x)) + } +} diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index 62240609a..9d3b3e9f6 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -48,18 +48,18 @@ pub fn compile_user_wasm(env: WasmEnvMut, sp: u32) { if out_hash_len != 32 { error!(eyre::eyre!( - "Go attempting to read compiled machine hash into bad buffer length: {out_hash_len}" + "Go attempting to read module hash into bad buffer length: {out_hash_len}" )); } // ensure the wasm compiles during proving - let (module, canonical_hash, info) = + let (module, module_hash, info) = match native::compile_user_wasm(&wasm, version, page_limit, debug) { Ok(result) => result, Err(error) => error!(error), }; - sp.write_slice(out_hash_ptr, canonical_hash.as_slice()); + sp.write_slice(out_hash_ptr, module_hash.as_slice()); sp.write_ptr(heapify(module)); sp.write_u16(info.footprint).skip_u16().write_u32(info.size); // wasm info sp.write_nullptr(); diff --git a/arbitrator/jit/src/wavmio.rs b/arbitrator/jit/src/wavmio.rs index 7fb10c5ea..850d61673 100644 --- a/arbitrator/jit/src/wavmio.rs +++ b/arbitrator/jit/src/wavmio.rs @@ -313,24 +313,24 @@ fn ready_hostio(env: &mut WasmEnv) -> MaybeEscape { env.preimages.insert(hash, preimage); } - let stylus_debug = socket::read_u8(stream)? != 0; + let debug = socket::read_u8(stream)? != 0; let programs_count = socket::read_u32(stream)?; for _ in 0..programs_count { let codehash = socket::read_bytes32(stream)?; - let wasm = socket::read_bytes(stream)?; - let compiled_hash = socket::read_bytes32(stream)?; + let wasm = &socket::read_bytes(stream)?; + let module_hash = socket::read_bytes32(stream)?; let version = socket::read_u16(stream)?; - // todo: test wasm against codehash? + // no need to test page_limit, we're just retracing previous compilation - let (module, computed_hash, _) = - match native::compile_user_wasm(wasm.as_slice(), version, u16::MAX, stylus_debug) { - Err(err) => return Escape::hostio(format!("{:?}", err)), - Ok(res) => res, - }; - if compiled_hash != *computed_hash { - return Escape::hostio(format!("error! compiled wasm different from expected codehash {:?}, version {}, expected {:?} computed {}", codehash, version, compiled_hash, computed_hash)); + let (module, hash, _) = match native::compile_user_wasm(wasm, version, u16::MAX, debug) { + Ok(res) => res, + Err(err) => return Escape::hostio(format!("{err:?}")), + }; + if module_hash != *hash { + let msg = format!("module hash divergence {codehash:?}, version {version}, expected {module_hash:?} computed {hash}"); + return Escape::hostio(msg); } - env.compiled_modules.insert(compiled_hash, module); + env.compiled_modules.insert(module_hash, module); } if socket::read_u8(stream)? != socket::READY { diff --git a/arbitrator/prover/src/binary.rs b/arbitrator/prover/src/binary.rs index 8880f8048..c9a3d6040 100644 --- a/arbitrator/prover/src/binary.rs +++ b/arbitrator/prover/src/binary.rs @@ -9,7 +9,7 @@ use crate::{ }, value::{ArbValueType, FunctionType, IntegerValType, Value}, }; -use arbutil::{Color, DebugColor}; +use arbutil::{math::SaturatingSum, Color, DebugColor}; use eyre::{bail, ensure, eyre, Result, WrapErr}; use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet}; use nom::{ @@ -80,22 +80,16 @@ pub enum FloatInstruction { impl FloatInstruction { pub fn signature(&self) -> FunctionType { match *self { - FloatInstruction::UnOp(t, _) => FunctionType::new(vec![t.into()], vec![t.into()]), - FloatInstruction::BinOp(t, _) => FunctionType::new(vec![t.into(); 2], vec![t.into()]), - FloatInstruction::RelOp(t, _) => { - FunctionType::new(vec![t.into(); 2], vec![ArbValueType::I32]) - } - FloatInstruction::TruncIntOp(i, f, ..) => { - FunctionType::new(vec![f.into()], vec![i.into()]) - } - FloatInstruction::ConvertIntOp(f, i, _) => { - FunctionType::new(vec![i.into()], vec![f.into()]) - } + FloatInstruction::UnOp(t, _) => FunctionType::new([t.into()], [t.into()]), + FloatInstruction::BinOp(t, _) => FunctionType::new([t.into(); 2], [t.into()]), + FloatInstruction::RelOp(t, _) => FunctionType::new([t.into(); 2], [ArbValueType::I32]), + FloatInstruction::TruncIntOp(i, f, ..) => FunctionType::new([f.into()], [i.into()]), + FloatInstruction::ConvertIntOp(f, i, _) => FunctionType::new([i.into()], [f.into()]), FloatInstruction::F32DemoteF64 => { - FunctionType::new(vec![ArbValueType::F64], vec![ArbValueType::F32]) + FunctionType::new([ArbValueType::F64], [ArbValueType::F32]) } FloatInstruction::F64PromoteF32 => { - FunctionType::new(vec![ArbValueType::F32], vec![ArbValueType::F64]) + FunctionType::new([ArbValueType::F32], [ArbValueType::F64]) } } } @@ -585,6 +579,10 @@ impl<'a> WasmBinary<'a> { // 4GB maximum implies `footprint` fits in a u16 let footprint = self.memory_info()?.min.0 as u16; + // check the entrypoint + let ty = FunctionType::new([ArbValueType::I32], [ArbValueType::I32]); + let user_main = self.check_func(STYLUS_ENTRY_POINT, ty)?; + let [ink_left, ink_status] = meter.globals(); let depth_left = depth.globals(); Ok(StylusData { @@ -592,6 +590,7 @@ impl<'a> WasmBinary<'a> { ink_status, depth_left, footprint, + user_main, }) } @@ -634,6 +633,9 @@ impl<'a> WasmBinary<'a> { limit!(4096, function.locals.len(), "locals") } + let table_entries = bin.tables.iter().map(|x| x.initial).saturating_sum(); + limit!(10_000, table_entries, "table entries"); + let max_len = 500; macro_rules! too_long { ($name:expr, $len:expr) => { @@ -654,27 +656,22 @@ impl<'a> WasmBinary<'a> { if bin.start.is_some() { bail!("wasm start functions not allowed"); } + Ok((bin, stylus_data, pages as u16)) + } - // check the entrypoint - let Some(&(entrypoint, kind)) = bin.exports.get(STYLUS_ENTRY_POINT) else { - bail!("missing export with name {}", STYLUS_ENTRY_POINT.red()); + /// Ensures a func exists and has the right type. + fn check_func(&self, name: &str, ty: FunctionType) -> Result { + let Some(&(func, kind)) = self.exports.get(name) else { + bail!("missing export with name {}", name.red()); }; if kind != ExportKind::Func { - bail!( - "export {} must be a function but is a {}", - STYLUS_ENTRY_POINT.red(), - kind.debug_red(), - ); + let kind = kind.debug_red(); + bail!("export {} must be a function but is a {kind}", name.red()); } - let entrypoint_ty = bin.get_function(FunctionIndex::new(entrypoint.try_into()?))?; - if entrypoint_ty != FunctionType::new(vec![ArbValueType::I32], vec![ArbValueType::I32]) { - bail!( - "wrong type for {}: {}", - STYLUS_ENTRY_POINT.red(), - entrypoint_ty.red(), - ); + let func_ty = self.get_function(FunctionIndex::new(func.try_into()?))?; + if func_ty != ty { + bail!("wrong type for {}: {}", name.red(), func_ty.red()); } - - Ok((bin, stylus_data, pages as u16)) + Ok(func) } } diff --git a/arbitrator/prover/src/host.rs b/arbitrator/prover/src/host.rs index 184005722..c98ebdf65 100644 --- a/arbitrator/prover/src/host.rs +++ b/arbitrator/prover/src/host.rs @@ -1,7 +1,7 @@ // Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -#![allow(clippy::vec_init_then_push)] +#![allow(clippy::vec_init_then_push, clippy::redundant_closure)] use crate::{ binary, host, @@ -53,7 +53,7 @@ impl InternalFunc { UserSetInk => func!([I64, I32], []), // λ(ink_left, ink_status) UserStackLeft => func!([], [I32]), // λ() → stack_left UserSetStack => func!([I32], []), // λ(stack_left) - CallMain => func!([I32], [I32]), + CallMain => func!([I32], [I32]), // λ(args_len) → status }; ty } @@ -209,7 +209,7 @@ impl Hostio { macro_rules! cross_internal { ($func:ident) => { opcode!(LocalGet, 0); // module - opcode!(CrossModuleInternalCall, InternalFunc::$func); // consumes module and func + opcode!(CrossModuleInternalCall, InternalFunc::$func); // consumes module }; } macro_rules! intern { @@ -360,7 +360,7 @@ pub fn get_impl(module: &str, name: &str) -> Result<(Function, bool)> { /// Adds internal functions to a module. /// Note: the order of the functions must match that of the `InternalFunc` enum -pub fn new_internal_funcs(stylus_data: Option<(StylusData, u32)>) -> Vec { +pub fn new_internal_funcs(stylus_data: Option) -> Vec { use ArbValueType::*; use InternalFunc::*; use Opcode::*; @@ -409,20 +409,17 @@ pub fn new_internal_funcs(stylus_data: Option<(StylusData, u32)>) -> Vec mach, Err(err) => { eprintln!("Error loading binary: {:?}", err); - std::ptr::null_mut() + ptr::null_mut() } } } @@ -111,7 +112,7 @@ pub unsafe extern "C" fn arbitrator_load_wavm_binary(binary_path: *const c_char) Ok(mach) => Box::into_raw(Box::new(mach)), Err(err) => { eprintln!("Error loading binary: {}", err); - std::ptr::null_mut() + ptr::null_mut() } } } @@ -120,6 +121,23 @@ unsafe fn cstr_to_string(c_str: *const c_char) -> String { CStr::from_ptr(c_str).to_string_lossy().into_owned() } +pub fn err_to_c_string(err: Report) -> *mut libc::c_char { + str_to_c_string(&format!("{err:?}")) +} + +/// Copies the str-data into a libc free-able C string +pub fn str_to_c_string(text: &str) -> *mut libc::c_char { + unsafe { + let buf = libc::malloc(text.len() + 1); // includes null-terminating byte + if buf.is_null() { + panic!("Failed to allocate memory for error string"); + } + ptr::copy_nonoverlapping(text.as_ptr(), buf as *mut u8, text.len()); + *(buf.add(text.len()) as *mut u8) = 0; + buf as *mut libc::c_char + } +} + #[no_mangle] pub unsafe extern "C" fn arbitrator_free_machine(mach: *mut Machine) { drop(Box::from_raw(mach)); @@ -137,19 +155,6 @@ pub unsafe extern "C" fn atomic_u8_store(ptr: *mut u8, contents: u8) { (*(ptr as *mut AtomicU8)).store(contents, atomic::Ordering::Relaxed); } -pub fn err_to_c_string(err: eyre::Report) -> *mut libc::c_char { - let err = format!("{:?}", err); - unsafe { - let buf = libc::malloc(err.len() + 1); - if buf.is_null() { - panic!("Failed to allocate memory for error string"); - } - std::ptr::copy_nonoverlapping(err.as_ptr(), buf as *mut u8, err.len()); - *(buf.add(err.len()) as *mut u8) = 0; - buf as *mut libc::c_char - } -} - /// Runs the machine while the condition variable is zero. May return early if num_steps is hit. /// Returns a c string error (freeable with libc's free) on error, or nullptr on success. #[no_mangle] @@ -172,7 +177,7 @@ pub unsafe extern "C" fn arbitrator_step( } remaining_steps -= stepping; } - std::ptr::null_mut() + ptr::null_mut() } #[no_mangle] @@ -205,17 +210,13 @@ pub unsafe extern "C" fn arbitrator_add_user_wasm( root: *const Bytes32, ) -> *mut libc::c_char { let wasm = std::slice::from_raw_parts(wasm, wasm_len as usize); + let debug = debug != 0; if root.is_null() { - return err_to_c_string(eyre::eyre!( - "arbitrator_add_user_wasm got null ptr for module hash" - )); + return str_to_c_string("arbitrator_add_user_wasm got null ptr for module hash"); } - // provide the opportunity to skip calculating the module root - let debug = debug != 0; - match (*mach).add_program(wasm, version, debug, Some(*root)) { - Ok(_) => std::ptr::null_mut(), + Ok(_) => ptr::null_mut(), Err(err) => err_to_c_string(err), } } @@ -232,10 +233,10 @@ pub unsafe extern "C" fn arbitrator_step_until_host_io( while condition.load(atomic::Ordering::Relaxed) == 0 { for _ in 0..1_000_000 { if mach.is_halted() { - return std::ptr::null_mut(); + return ptr::null_mut(); } if mach.next_instruction_is_host_io() { - return std::ptr::null_mut(); + return ptr::null_mut(); } match mach.step_n(1) { Ok(()) => {} @@ -243,7 +244,7 @@ pub unsafe extern "C" fn arbitrator_step_until_host_io( } } } - std::ptr::null_mut() + ptr::null_mut() } #[no_mangle] @@ -254,7 +255,7 @@ pub unsafe extern "C" fn arbitrator_serialize_state( let mach = &*mach; let res = CStr::from_ptr(path) .to_str() - .map_err(eyre::Report::from) + .map_err(Report::from) .and_then(|path| mach.serialize_state(path)); if let Err(err) = res { eprintln!("Failed to serialize machine state: {}", err); @@ -272,7 +273,7 @@ pub unsafe extern "C" fn arbitrator_deserialize_and_replace_state( let mach = &mut *mach; let res = CStr::from_ptr(path) .to_str() - .map_err(eyre::Report::from) + .map_err(Report::from) .and_then(|path| mach.deserialize_and_replace_state(path)); if let Err(err) = res { eprintln!("Failed to deserialize machine state: {}", err); diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index c25a71a2e..aa43e2976 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -8,9 +8,7 @@ use crate::{ host, memory::Memory, merkle::{Merkle, MerkleType}, - programs::{ - config::CompileConfig, meter::MeteredMachine, ModuleMod, StylusData, STYLUS_ENTRY_POINT, - }, + programs::{config::CompileConfig, meter::MeteredMachine, ModuleMod, StylusData}, reinterpret::{ReinterpretAsSigned, ReinterpretAsUnsigned}, utils::{file_bytes, CBytes, RemoteTableType}, value::{ArbValueType, FunctionType, IntegerValType, ProgramCounter, Value}, @@ -390,18 +388,7 @@ impl Module { }) .collect(); - let internals_data = match stylus_data { - None => None, - Some(data) => { - let stylus_main = func_exports - .iter() - .find(|x| x.0 == STYLUS_ENTRY_POINT) - .and_then(|x| Some(x.1)) - .ok_or(eyre::eyre!("stylus program without entry point"))?; - Some((data, *stylus_main)) - } - }; - let internals = host::new_internal_funcs(internals_data); + let internals = host::new_internal_funcs(stylus_data); let internals_offset = (code.len() + bin.codes.len()) as u32; let internals_types = internals.iter().map(|f| f.ty.clone()); @@ -1137,27 +1124,17 @@ impl Machine { wasm: &[u8], version: u16, debug_funcs: bool, - hash: Option, + hash: Option, // computed if not already known ) -> Result { let mut bin = binary::parse(wasm, Path::new("user"))?; let config = CompileConfig::version(version, debug_funcs); let stylus_data = bin.instrument(&config)?; let module = Module::from_user_binary(&bin, debug_funcs, Some(stylus_data))?; - let computed_hash = module.hash(); - - if let Some(expected_hash) = hash { - if computed_hash != expected_hash { - return Err(eyre::eyre!( - "compulted hash {} doesn't match expected {}", - computed_hash, - expected_hash - )); - } - } - eprintln!("adding module {}", computed_hash); - self.stylus_modules.insert(computed_hash, module); - Ok(computed_hash) + let hash = hash.unwrap_or_else(|| module.hash()); + + self.stylus_modules.insert(hash, module); + Ok(hash) } pub fn from_binaries( @@ -1291,7 +1268,7 @@ impl Machine { // Rust support let rust_fn = "__main_void"; if let Some(&f) = main_exports.get(rust_fn).filter(|_| runtime_support) { - let expected_type = FunctionType::new(vec![], vec![I32]); + let expected_type = FunctionType::new([], [I32]); ensure!( main_module.func_types[f as usize] == expected_type, "Main function doesn't match expected signature of [] -> [ret]", diff --git a/arbitrator/prover/src/programs/mod.rs b/arbitrator/prover/src/programs/mod.rs index a519714d9..ccc3c7664 100644 --- a/arbitrator/prover/src/programs/mod.rs +++ b/arbitrator/prover/src/programs/mod.rs @@ -367,6 +367,7 @@ pub struct StylusData { pub ink_status: GlobalIndex, pub depth_left: GlobalIndex, pub footprint: u16, + pub user_main: u32, } impl StylusData { diff --git a/arbitrator/prover/src/value.rs b/arbitrator/prover/src/value.rs index e7bd77fc3..6bd688604 100644 --- a/arbitrator/prover/src/value.rs +++ b/arbitrator/prover/src/value.rs @@ -409,8 +409,15 @@ pub struct FunctionType { } impl FunctionType { - pub fn new(inputs: Vec, outputs: Vec) -> FunctionType { - FunctionType { inputs, outputs } + pub fn new(inputs: T, outputs: U) -> FunctionType + where + T: Into>, + U: Into>, + { + FunctionType { + inputs: inputs.into(), + outputs: outputs.into(), + } } pub fn hash(&self) -> Bytes32 { diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index 6c46a16be..9152151a7 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -132,8 +132,8 @@ pub unsafe extern "C" fn stylus_compile( let (module, canonical_hash, pricing_info) = match native::compile_user_wasm(wasm, version, page_limit, debug_mode) { - Err(err) => return output.write_err(err), Ok(val) => val, + Err(err) => return output.write_err(err), }; out_canonical_hash.write(canonical_hash.to_vec()); diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index c794a7952..558ba116d 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -379,9 +379,9 @@ pub fn compile_user_wasm( ) -> Result<(Vec, Bytes32, WasmPricingInfo)> { let compile = CompileConfig::version(version, debug_mode); let (bin, stylus_data, footprint) = - WasmBinary::parse_user(wasm, page_limit, &compile).wrap_err("failed to parse program")?; + WasmBinary::parse_user(wasm, page_limit, &compile).wrap_err("failed to parse wasm")?; - let canonical_hash = prover::machine::Module::from_user_binary( + let module_hash = prover::machine::Module::from_user_binary( &bin, compile.debug.debug_funcs, Some(stylus_data), @@ -391,9 +391,9 @@ pub fn compile_user_wasm( let info = WasmPricingInfo { size: wasm.len().try_into()?, - footprint: footprint, + footprint, }; - let module = module(wasm, compile).wrap_err("failed generating stylus module")?; + let module = module(wasm, compile).wrap_err("failed to generate stylus module")?; - Ok((module, canonical_hash, info)) + Ok((module, module_hash, info)) } diff --git a/arbitrator/wasm-libraries/Cargo.lock b/arbitrator/wasm-libraries/Cargo.lock index daaa28fee..cdb4bea75 100644 --- a/arbitrator/wasm-libraries/Cargo.lock +++ b/arbitrator/wasm-libraries/Cargo.lock @@ -29,6 +29,7 @@ dependencies = [ "digest 0.9.0", "eyre", "hex", + "num-traits", "serde", "sha3 0.10.6", "siphasher", @@ -593,9 +594,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] diff --git a/go-ethereum b/go-ethereum index 35fead93e..97f1a4d8f 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 35fead93eece0bccf4998360657f02d10155eccc +Subproject commit 97f1a4d8f5150e61a3dea4378cdf39bbb3820787 diff --git a/system_tests/program_test.go b/system_tests/program_test.go index a07f37510..36bcb2f45 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -48,10 +48,6 @@ func TestProgramKeccak(t *testing.T) { keccakTest(t, true) } -func TestProgramArbitratorKeccak(t *testing.T) { - keccakTest(t, false) -} - func keccakTest(t *testing.T, jit bool) { ctx, node, _, l2client, auth, cleanup := setupProgramTest(t, jit) defer cleanup() diff --git a/system_tests/stylus_test.go b/system_tests/stylus_test.go index 128c107b8..f4615da56 100644 --- a/system_tests/stylus_test.go +++ b/system_tests/stylus_test.go @@ -10,6 +10,10 @@ import ( "testing" ) +func TestProgramArbitratorKeccak(t *testing.T) { + keccakTest(t, false) +} + func TestProgramArbitratorErrors(t *testing.T) { errorTest(t, false) } diff --git a/validator/server_api/json.go b/validator/server_api/json.go index 303dd1a05..bee8309dd 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -20,7 +20,7 @@ type BatchInfoJson struct { } type UserWasmJson struct { - CompiledHash common.Hash + ModuleHash common.Hash CompressedWasm string } @@ -57,7 +57,7 @@ func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJso encCall := base64.StdEncoding.EncodeToString(callBytes) encWasm := UserWasmJson{ CompressedWasm: base64.StdEncoding.EncodeToString(wasm.CompressedWasm), - CompiledHash: wasm.CompiledHash, + ModuleHash: wasm.ModuleHash, } res.UserWasms[encCall] = encWasm } @@ -104,7 +104,7 @@ func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationI return nil, err } decWasm := state.UserWasm{ - CompiledHash: wasm.CompiledHash, + ModuleHash: wasm.ModuleHash, CompressedWasm: compressed, } valInput.UserWasms[decCall] = &decWasm diff --git a/validator/server_arb/machine.go b/validator/server_arb/machine.go index b3ceea8c0..0993985ed 100644 --- a/validator/server_arb/machine.go +++ b/validator/server_arb/machine.go @@ -405,21 +405,21 @@ func (m *ArbitratorMachine) AddUserWasm(call state.WasmCall, wasm *state.UserWas return errors.New("machine frozen") } hashBytes := [32]u8{} - for index, byte := range wasm.CompiledHash.Bytes() { + for index, byte := range wasm.ModuleHash.Bytes() { hashBytes[index] = u8(byte) } debugInt := 0 if debug { debugInt = 1 } - decompressed, err := arbcompress.Decompress(wasm.CompressedWasm, programs.MaxWasmSize) + inflated, err := arbcompress.Decompress(wasm.CompressedWasm, programs.MaxWasmSize) if err != nil { return err } cErr := C.arbitrator_add_user_wasm( m.ptr, - (*u8)(arbutil.SliceToPointer(decompressed)), - u32(len(decompressed)), + (*u8)(arbutil.SliceToPointer(inflated)), + u32(len(inflated)), u16(call.Version), u32(debugInt), &C.struct_Bytes32{hashBytes}, diff --git a/validator/server_jit/jit_machine.go b/validator/server_jit/jit_machine.go index cb2530d7b..b02b9af24 100644 --- a/validator/server_jit/jit_machine.go +++ b/validator/server_jit/jit_machine.go @@ -229,7 +229,7 @@ func (machine *JitMachine) prove( if err := writeBytes(inflated); err != nil { return state, err } - if err := writeExact(wasm.CompiledHash[:]); err != nil { + if err := writeExact(wasm.ModuleHash[:]); err != nil { return state, err } if err := writeUint16(call.Version); err != nil { From afe6187f7ec49bd7e81d71ff8cadc9024fe4733c Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 12 Oct 2023 11:02:50 -0600 Subject: [PATCH 02/19] jit asm --- arbitrator/jit/src/machine.rs | 6 ++++-- arbitrator/jit/src/socket.rs | 9 ++++----- arbitrator/jit/src/user/evm_api.rs | 4 ++-- arbitrator/jit/src/user/mod.rs | 16 ++++++---------- arbitrator/jit/src/wavmio.rs | 18 ++---------------- 5 files changed, 18 insertions(+), 35 deletions(-) diff --git a/arbitrator/jit/src/machine.rs b/arbitrator/jit/src/machine.rs index e233f03a4..a634290f2 100644 --- a/arbitrator/jit/src/machine.rs +++ b/arbitrator/jit/src/machine.rs @@ -21,6 +21,7 @@ use std::{ io::{self, Write}, io::{BufReader, BufWriter, ErrorKind, Read}, net::TcpStream, + sync::Arc, time::{Duration, Instant}, }; @@ -193,6 +194,7 @@ impl From for Escape { pub type WasmEnvMut<'a> = FunctionEnvMut<'a, WasmEnv>; pub type Inbox = BTreeMap>; pub type Oracle = BTreeMap>; +pub type ModuleAsm = Arc<[u8]>; #[derive(Default)] pub struct WasmEnv { @@ -208,8 +210,8 @@ pub struct WasmEnv { pub large_globals: [Bytes32; 2], /// An oracle allowing the prover to reverse keccak256 pub preimages: Oracle, - /// A collection of user wasms called during the course of execution - pub compiled_modules: HashMap>, + /// A collection of programs called during the course of execution + pub module_asms: HashMap, /// The sequencer inbox's messages pub sequencer_messages: Inbox, /// The delayed inbox's messages diff --git a/arbitrator/jit/src/socket.rs b/arbitrator/jit/src/socket.rs index 6b4370196..634af3635 100644 --- a/arbitrator/jit/src/socket.rs +++ b/arbitrator/jit/src/socket.rs @@ -20,11 +20,6 @@ pub fn read_u8(reader: &mut BufReader) -> Result { reader.read_exact(&mut buf).map(|_| u8::from_be_bytes(buf)) } -pub fn read_u16(reader: &mut BufReader) -> Result { - let mut buf = [0; 2]; - reader.read_exact(&mut buf).map(|_| u16::from_be_bytes(buf)) -} - pub fn read_u32(reader: &mut BufReader) -> Result { let mut buf = [0; 4]; reader.read_exact(&mut buf).map(|_| u32::from_be_bytes(buf)) @@ -47,6 +42,10 @@ pub fn read_bytes(reader: &mut BufReader) -> Result, io::Err Ok(buf) } +pub fn read_box(reader: &mut BufReader) -> Result, io::Error> { + Ok(Vec::into_boxed_slice(read_bytes(reader)?)) +} + pub fn write_u8(writer: &mut BufWriter, data: u8) -> Result<(), io::Error> { let buf = [data; 1]; writer.write_all(&buf) diff --git a/arbitrator/jit/src/user/evm_api.rs b/arbitrator/jit/src/user/evm_api.rs index 183ebfb0e..d17290226 100644 --- a/arbitrator/jit/src/user/evm_api.rs +++ b/arbitrator/jit/src/user/evm_api.rs @@ -5,7 +5,7 @@ use crate::{ gostack::GoStack, - machine::WasmEnvMut, + machine::{ModuleAsm, WasmEnvMut}, syscall::{DynamicObject, GoValue, JsValue, STYLUS_ID}, }; use arbutil::{ @@ -53,7 +53,7 @@ impl JsCallIntoGo for ApiCaller { pub(super) fn exec_wasm( sp: &mut GoStack, mut env: WasmEnvMut, - module: Vec, + module: ModuleAsm, calldata: Vec, compile: CompileConfig, config: StylusConfig, diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index 9d3b3e9f6..b188a9bf6 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -83,7 +83,7 @@ pub fn call_user_wasm(env: WasmEnvMut, sp: u32) -> MaybeEscape { use UserOutcome::*; // move inputs - let compiled_hash = sp.read_bytes32(); + let module_hash = sp.read_bytes32(); let calldata = sp.read_go_slice_owned(); let (compile, config): (CompileConfig, StylusConfig) = sp.unbox(); let evm_api = sp.read_go_slice_owned(); @@ -94,15 +94,11 @@ pub fn call_user_wasm(env: WasmEnvMut, sp: u32) -> MaybeEscape { let pricing = config.pricing; let ink = pricing.gas_to_ink(sp.read_u64_raw(gas)); - let module = match &env.data().compiled_modules.get(&compiled_hash) { - None => { - return Err(Escape::Failure(format!( - "compiled hash requested {:?} not found in {:?}", - compiled_hash, - env.data().compiled_modules.keys() - ))) - } - Some(module) => (*module).clone(), + let Some(module) = env.data().module_asms.get(&module_hash).cloned() else { + return Escape::failure(format!( + "module hash {module_hash:?} not found in {:?}", + env.data().module_asms.keys() + )); }; let result = exec_wasm( diff --git a/arbitrator/jit/src/wavmio.rs b/arbitrator/jit/src/wavmio.rs index 850d61673..1aa39fcc7 100644 --- a/arbitrator/jit/src/wavmio.rs +++ b/arbitrator/jit/src/wavmio.rs @@ -14,7 +14,6 @@ use std::{ net::TcpStream, time::Instant, }; -use stylus::native; pub type Bytes20 = [u8; 20]; pub type Bytes32 = [u8; 32]; @@ -313,24 +312,11 @@ fn ready_hostio(env: &mut WasmEnv) -> MaybeEscape { env.preimages.insert(hash, preimage); } - let debug = socket::read_u8(stream)? != 0; let programs_count = socket::read_u32(stream)?; for _ in 0..programs_count { - let codehash = socket::read_bytes32(stream)?; - let wasm = &socket::read_bytes(stream)?; + let module_asm = socket::read_box(stream)?; let module_hash = socket::read_bytes32(stream)?; - let version = socket::read_u16(stream)?; - - // no need to test page_limit, we're just retracing previous compilation - let (module, hash, _) = match native::compile_user_wasm(wasm, version, u16::MAX, debug) { - Ok(res) => res, - Err(err) => return Escape::hostio(format!("{err:?}")), - }; - if module_hash != *hash { - let msg = format!("module hash divergence {codehash:?}, version {version}, expected {module_hash:?} computed {hash}"); - return Escape::hostio(msg); - } - env.compiled_modules.insert(module_hash, module); + env.module_asms.insert(module_hash, module_asm.into()); } if socket::read_u8(stream)? != socket::READY { From 5c2fbf871e3f3701ba3ef8dd28dbc34b277b40fd Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 12 Oct 2023 11:20:15 -0600 Subject: [PATCH 03/19] skip JIT recompilation --- arbitrator/jit/src/wavmio.rs | 2 +- go-ethereum | 2 +- validator/server_api/json.go | 7 +++++++ validator/server_jit/jit_machine.go | 26 ++------------------------ 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/arbitrator/jit/src/wavmio.rs b/arbitrator/jit/src/wavmio.rs index 1aa39fcc7..17ba6fa81 100644 --- a/arbitrator/jit/src/wavmio.rs +++ b/arbitrator/jit/src/wavmio.rs @@ -314,8 +314,8 @@ fn ready_hostio(env: &mut WasmEnv) -> MaybeEscape { let programs_count = socket::read_u32(stream)?; for _ in 0..programs_count { - let module_asm = socket::read_box(stream)?; let module_hash = socket::read_bytes32(stream)?; + let module_asm = socket::read_box(stream)?; env.module_asms.insert(module_hash, module_asm.into()); } diff --git a/go-ethereum b/go-ethereum index 97f1a4d8f..017546438 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 97f1a4d8f5150e61a3dea4378cdf39bbb3820787 +Subproject commit 017546438f3f887c3061cbe1b1f7ffd86461f794 diff --git a/validator/server_api/json.go b/validator/server_api/json.go index bee8309dd..bb97683b0 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -21,6 +21,7 @@ type BatchInfoJson struct { type UserWasmJson struct { ModuleHash common.Hash + ModuleAsm string CompressedWasm string } @@ -58,6 +59,7 @@ func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJso encWasm := UserWasmJson{ CompressedWasm: base64.StdEncoding.EncodeToString(wasm.CompressedWasm), ModuleHash: wasm.ModuleHash, + ModuleAsm: base64.StdEncoding.EncodeToString(wasm.ModuleAsm), } res.UserWasms[encCall] = encWasm } @@ -103,8 +105,13 @@ func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationI if err != nil { return nil, err } + moduleAsm, err := base64.StdEncoding.DecodeString(wasm.ModuleAsm) + if err != nil { + return nil, err + } decWasm := state.UserWasm{ ModuleHash: wasm.ModuleHash, + ModuleAsm: moduleAsm, CompressedWasm: compressed, } valInput.UserWasms[decCall] = &decWasm diff --git a/validator/server_jit/jit_machine.go b/validator/server_jit/jit_machine.go index b02b9af24..e356ae719 100644 --- a/validator/server_jit/jit_machine.go +++ b/validator/server_jit/jit_machine.go @@ -16,8 +16,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbcompress" - "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/validator" ) @@ -120,9 +118,6 @@ func (machine *JitMachine) prove( writeUint8 := func(data uint8) error { return writeExact([]byte{data}) } - writeUint16 := func(data uint16) error { - return writeExact(arbmath.Uint16ToBytes(data)) - } writeUint32 := func(data uint32) error { return writeExact(arbmath.Uint32ToBytes(data)) } @@ -207,32 +202,15 @@ func (machine *JitMachine) prove( } // send user wasms - debugFlag := uint8(0) - if entry.DebugChain { - debugFlag = 1 - } - if err := writeUint8(debugFlag); err != nil { - return state, err - } userWasms := entry.UserWasms if err := writeUint32(uint32(len(userWasms))); err != nil { return state, err } - for call, wasm := range userWasms { - if err := writeExact(call.CodeHash[:]); err != nil { - return state, err - } - inflated, err := arbcompress.Decompress(wasm.CompressedWasm, programs.MaxWasmSize) - if err != nil { - return state, fmt.Errorf("error decompressing program: %w", err) - } - if err := writeBytes(inflated); err != nil { - return state, err - } + for _, wasm := range userWasms { if err := writeExact(wasm.ModuleHash[:]); err != nil { return state, err } - if err := writeUint16(call.Version); err != nil { + if err := writeBytes(wasm.ModuleAsm); err != nil { return state, err } } From 6b4124ee529b584b13bedc05fba97d9a092c6a9c Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 12 Oct 2023 11:30:03 -0600 Subject: [PATCH 04/19] rename to Get/Set ActivateAsm --- arbos/programs/native.go | 4 ++-- go-ethereum | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 4f613c7d2..1da44b3e0 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -81,7 +81,7 @@ func compileUserWasm( return nil, common.Hash{}, err } - db.SetCompiledWasmCode(program, data, version) + db.SetActivatedAsm(program, data, version) return &info, common.BytesToHash(canonicalHashRust.intoBytes()), err } @@ -100,7 +100,7 @@ func callUserWasm( if db, ok := db.(*state.StateDB); ok { db.RecordProgram(address, scope.Contract.CodeHash, stylusParams.version, program.compiledHash) } - module := db.GetCompiledWasmCode(address, stylusParams.version) + module := db.GetActivatedAsm(address, stylusParams.version) evmApi, id := newApi(interpreter, tracingInfo, scope, memoryModel) defer dropApi(id) diff --git a/go-ethereum b/go-ethereum index 017546438..83e9aec6c 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 017546438f3f887c3061cbe1b1f7ffd86461f794 +Subproject commit 83e9aec6c5019a6518c616ded00c8d3e20203221 From 0d9521ef4f527a7da29eb4e10483a29f335bb013 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 12 Oct 2023 16:13:13 -0600 Subject: [PATCH 05/19] switch to asm-module pairs --- arbos/programs/native.go | 5 ++++- go-ethereum | 2 +- validator/server_api/json.go | 22 +++++++++++----------- validator/server_arb/machine.go | 16 +++------------- validator/server_jit/jit_machine.go | 2 +- 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 1da44b3e0..30a75a264 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -81,7 +81,10 @@ func compileUserWasm( return nil, common.Hash{}, err } - db.SetActivatedAsm(program, data, version) + asm := data + module := []byte{} + + db.NewActivation(program, version, asm, module) return &info, common.BytesToHash(canonicalHashRust.intoBytes()), err } diff --git a/go-ethereum b/go-ethereum index 83e9aec6c..d65925551 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 83e9aec6c5019a6518c616ded00c8d3e20203221 +Subproject commit d65925551350281002cd2a7ca8093ba38e19214b diff --git a/validator/server_api/json.go b/validator/server_api/json.go index bb97683b0..b093e91cb 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -20,9 +20,9 @@ type BatchInfoJson struct { } type UserWasmJson struct { - ModuleHash common.Hash - ModuleAsm string - CompressedWasm string + ModuleHash common.Hash + Module string + Asm string } type ValidationInputJson struct { @@ -57,9 +57,9 @@ func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJso callBytes = append(callBytes, call.CodeHash.Bytes()...) encCall := base64.StdEncoding.EncodeToString(callBytes) encWasm := UserWasmJson{ - CompressedWasm: base64.StdEncoding.EncodeToString(wasm.CompressedWasm), - ModuleHash: wasm.ModuleHash, - ModuleAsm: base64.StdEncoding.EncodeToString(wasm.ModuleAsm), + ModuleHash: wasm.ModuleHash, + Module: base64.StdEncoding.EncodeToString(wasm.Module), + Asm: base64.StdEncoding.EncodeToString(wasm.Asm), } res.UserWasms[encCall] = encWasm } @@ -101,18 +101,18 @@ func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationI Version: arbmath.BytesToUint16(callBytes[:2]), CodeHash: common.BytesToHash(callBytes[2:]), } - compressed, err := base64.StdEncoding.DecodeString(wasm.CompressedWasm) + asm, err := base64.StdEncoding.DecodeString(wasm.Asm) if err != nil { return nil, err } - moduleAsm, err := base64.StdEncoding.DecodeString(wasm.ModuleAsm) + module, err := base64.StdEncoding.DecodeString(wasm.Module) if err != nil { return nil, err } decWasm := state.UserWasm{ - ModuleHash: wasm.ModuleHash, - ModuleAsm: moduleAsm, - CompressedWasm: compressed, + ModuleHash: wasm.ModuleHash, + Module: module, + Asm: asm, } valInput.UserWasms[decCall] = &decWasm } diff --git a/validator/server_arb/machine.go b/validator/server_arb/machine.go index 0993985ed..eba79b95f 100644 --- a/validator/server_arb/machine.go +++ b/validator/server_arb/machine.go @@ -22,8 +22,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbcompress" - "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/validator" @@ -408,20 +406,12 @@ func (m *ArbitratorMachine) AddUserWasm(call state.WasmCall, wasm *state.UserWas for index, byte := range wasm.ModuleHash.Bytes() { hashBytes[index] = u8(byte) } - debugInt := 0 - if debug { - debugInt = 1 - } - inflated, err := arbcompress.Decompress(wasm.CompressedWasm, programs.MaxWasmSize) - if err != nil { - return err - } cErr := C.arbitrator_add_user_wasm( m.ptr, - (*u8)(arbutil.SliceToPointer(inflated)), - u32(len(inflated)), + (*u8)(arbutil.SliceToPointer(wasm.Module)), + u32(len(wasm.Module)), u16(call.Version), - u32(debugInt), + u32(arbmath.BoolToUint32(debug)), &C.struct_Bytes32{hashBytes}, ) defer C.free(unsafe.Pointer(cErr)) diff --git a/validator/server_jit/jit_machine.go b/validator/server_jit/jit_machine.go index e356ae719..6de60e912 100644 --- a/validator/server_jit/jit_machine.go +++ b/validator/server_jit/jit_machine.go @@ -210,7 +210,7 @@ func (machine *JitMachine) prove( if err := writeExact(wasm.ModuleHash[:]); err != nil { return state, err } - if err := writeBytes(wasm.ModuleAsm); err != nil { + if err := writeBytes(wasm.Asm); err != nil { return state, err } } From e6a5b6cd82267272ce88e190012b852b4dbaf9e0 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 12 Oct 2023 21:37:40 -0600 Subject: [PATCH 06/19] produce module-asm pairs --- arbitrator/Cargo.lock | 1 + arbitrator/jit/src/user/mod.rs | 16 ++++----- arbitrator/prover/src/machine.rs | 8 +++++ arbitrator/stylus/Cargo.toml | 1 + arbitrator/stylus/src/lib.rs | 28 +++++++--------- arbitrator/stylus/src/native.rs | 37 ++++++++++----------- arbos/programs/native.go | 26 +++++++++------ arbos/programs/programs.go | 26 +++++++-------- arbos/programs/wasm.go | 2 +- go-ethereum | 2 +- validator/server_api/json.go | 40 +++++++++-------------- validator/server_arb/machine.go | 11 +++---- validator/server_arb/validator_spawner.go | 6 ++-- validator/server_jit/jit_machine.go | 6 ++-- 14 files changed, 103 insertions(+), 107 deletions(-) diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index ef9116953..20a7e8335 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -1803,6 +1803,7 @@ name = "stylus" version = "0.1.0" dependencies = [ "arbutil", + "bincode", "derivative", "eyre", "fnv", diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index b188a9bf6..92f2618fd 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -52,15 +52,13 @@ pub fn compile_user_wasm(env: WasmEnvMut, sp: u32) { )); } - // ensure the wasm compiles during proving - let (module, module_hash, info) = - match native::compile_user_wasm(&wasm, version, page_limit, debug) { - Ok(result) => result, - Err(error) => error!(error), - }; - - sp.write_slice(out_hash_ptr, module_hash.as_slice()); - sp.write_ptr(heapify(module)); + let (asm, module, info) = match native::compile_user_wasm(&wasm, version, page_limit, debug) { + Ok(result) => result, + Err(error) => error!(error), + }; + + sp.write_slice(out_hash_ptr, module.hash().as_slice()); + sp.write_ptr(heapify(asm)); sp.write_u16(info.footprint).skip_u16().write_u32(info.size); // wasm info sp.write_nullptr(); } diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index aa43e2976..9b8baab47 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -631,6 +631,14 @@ impl Module { data } + + pub fn into_bytes(&self) -> Box<[u8]> { + bincode::serialize(self).unwrap().into_boxed_slice() + } + + pub unsafe fn from_bytes(bytes: &[u8]) -> Self { + bincode::deserialize(bytes).unwrap() + } } // Globalstate holds: diff --git a/arbitrator/stylus/Cargo.toml b/arbitrator/stylus/Cargo.toml index c91fc4106..b469288dc 100644 --- a/arbitrator/stylus/Cargo.toml +++ b/arbitrator/stylus/Cargo.toml @@ -15,6 +15,7 @@ wasmer-compiler-llvm = { path = "../tools/wasmer/lib/compiler-llvm", optional = derivative = "2.2.0" parking_lot = "0.12.1" thiserror = "1.0.33" +bincode = "1.3.3" libc = "0.2.108" eyre = "0.6.5" rand = "0.8.5" diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index 9152151a7..83f02656d 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -8,6 +8,7 @@ use arbutil::{ EvmData, }, format::DebugBytes, + Bytes32, }; use eyre::ErrReport; use native::NativeInstance; @@ -104,7 +105,7 @@ impl RustVec { /// /// # Safety /// -/// Output, pricing_info, output_canonical_hash must not be null. +/// output, pricing_info, module_hash must not be null. #[no_mangle] pub unsafe extern "C" fn stylus_compile( wasm: GoSliceData, @@ -113,33 +114,26 @@ pub unsafe extern "C" fn stylus_compile( debug_mode: bool, out_pricing_info: *mut WasmPricingInfo, output: *mut RustVec, - out_canonical_hash: *mut RustVec, + asm_len: *mut usize, + module_hash: *mut Bytes32, ) -> UserOutcomeKind { let wasm = wasm.slice(); - - if output.is_null() { - return UserOutcomeKind::Failure; - } let output = &mut *output; + let module_hash = &mut *module_hash; - if out_pricing_info.is_null() { - return output.write_err(eyre::eyre!("pricing_info is null")); - } - if out_canonical_hash.is_null() { - return output.write_err(eyre::eyre!("canonical_hash is null")); - } - let out_canonical_hash = &mut *out_canonical_hash; - - let (module, canonical_hash, pricing_info) = + let (asm, module, pricing_info) = match native::compile_user_wasm(wasm, version, page_limit, debug_mode) { Ok(val) => val, Err(err) => return output.write_err(err), }; - out_canonical_hash.write(canonical_hash.to_vec()); + *asm_len = asm.len(); + *module_hash = module.hash(); *out_pricing_info = pricing_info; - output.write(module); + let mut data = asm; + data.extend(&*module.into_bytes()); + output.write(data); UserOutcomeKind::Success } diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index 558ba116d..99631499b 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -8,17 +8,20 @@ use crate::{ use arbutil::{ evm::{api::EvmApi, EvmData}, operator::OperatorCode, - Bytes32, Color, + Color, }; use eyre::{bail, eyre, Context, ErrReport, Result}; -use prover::binary::WasmBinary; -use prover::programs::{ - config::{PricingParams, WasmPricingInfo}, - counter::{Counter, CountingMachine, OP_OFFSETS}, - depth::STYLUS_STACK_LEFT, - meter::{STYLUS_INK_LEFT, STYLUS_INK_STATUS}, - prelude::*, - start::STYLUS_START, +use prover::{ + binary::WasmBinary, + machine::Module as ProverModule, + programs::{ + config::{PricingParams, WasmPricingInfo}, + counter::{Counter, CountingMachine, OP_OFFSETS}, + depth::STYLUS_STACK_LEFT, + meter::{STYLUS_INK_LEFT, STYLUS_INK_STATUS}, + prelude::*, + start::STYLUS_START, + }, }; use std::{ collections::BTreeMap, @@ -376,24 +379,20 @@ pub fn compile_user_wasm( version: u16, page_limit: u16, debug_mode: bool, -) -> Result<(Vec, Bytes32, WasmPricingInfo)> { +) -> Result<(Vec, ProverModule, WasmPricingInfo)> { let compile = CompileConfig::version(version, debug_mode); let (bin, stylus_data, footprint) = WasmBinary::parse_user(wasm, page_limit, &compile).wrap_err("failed to parse wasm")?; - let module_hash = prover::machine::Module::from_user_binary( - &bin, - compile.debug.debug_funcs, - Some(stylus_data), - ) - .wrap_err("failed to build module from program")? - .hash(); + let prover_module = + ProverModule::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data)) + .wrap_err("failed to build module from program")?; let info = WasmPricingInfo { size: wasm.len().try_into()?, footprint, }; - let module = module(wasm, compile).wrap_err("failed to generate stylus module")?; + let asm = module(wasm, compile).wrap_err("failed to generate stylus module")?; - Ok((module, module_hash, info)) + Ok((asm, prover_module, info)) } diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 30a75a264..e1b116674 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -52,9 +52,12 @@ func compileUserWasm( debug bool, burner burn.Burner, ) (*wasmPricingInfo, common.Hash, error) { - rustInfo := &C.WasmPricingInfo{} output := &rustVec{} - canonicalHashRust := &rustVec{} + asmLen := usize(0) + + moduleHash := &bytes32{} + rustInfo := &C.WasmPricingInfo{} + status := userStatus(C.stylus_compile( goSlice(wasm), u16(page_limit), @@ -62,7 +65,8 @@ func compileUserWasm( cbool(debug), rustInfo, output, - canonicalHashRust, + &asmLen, + moduleHash, )) data, msg, err := status.toResult(output.intoBytes(), debug) @@ -81,11 +85,13 @@ func compileUserWasm( return nil, common.Hash{}, err } - asm := data - module := []byte{} + hash := moduleHash.toHash() + split := int(asmLen) + asm := data[:split] + module := data[split:] - db.NewActivation(program, version, asm, module) - return &info, common.BytesToHash(canonicalHashRust.intoBytes()), err + db.ActivateWasm(hash, asm, module) + return &info, hash, err } func callUserWasm( @@ -101,16 +107,16 @@ func callUserWasm( memoryModel *MemoryModel, ) ([]byte, error) { if db, ok := db.(*state.StateDB); ok { - db.RecordProgram(address, scope.Contract.CodeHash, stylusParams.version, program.compiledHash) + db.RecordProgram(program.moduleHash) } - module := db.GetActivatedAsm(address, stylusParams.version) + asm := db.GetActivatedAsm(program.moduleHash) evmApi, id := newApi(interpreter, tracingInfo, scope, memoryModel) defer dropApi(id) output := &rustVec{} status := userStatus(C.stylus_call( - goSlice(module), + goSlice(asm), goSlice(calldata), stylusParams.encode(), evmApi, diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index cdba2f23e..218c92f4f 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -35,10 +35,10 @@ type Programs struct { } type Program struct { - wasmSize uint16 // Unit is half of a kb - footprint uint16 - version uint16 - compiledHash common.Hash + wasmSize uint16 // Unit is half of a kb + footprint uint16 + version uint16 + moduleHash common.Hash } type uint24 = arbmath.Uint24 @@ -212,10 +212,10 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode wasmSize := arbmath.SaturatingUCast[uint16]((len(wasm) + 511) / 512) programData := Program{ - wasmSize: wasmSize, - footprint: info.footprint, - version: version, - compiledHash: compiledHash, + wasmSize: wasmSize, + footprint: info.footprint, + version: version, + moduleHash: compiledHash, } return version, false, p.setProgram(codeHash, programData) } @@ -326,10 +326,10 @@ func (p Programs) deserializeProgram(codeHash common.Hash) (Program, error) { return Program{}, err } return Program{ - wasmSize: arbmath.BytesToUint16(data[26:28]), - footprint: arbmath.BytesToUint16(data[28:30]), - version: arbmath.BytesToUint16(data[30:]), - compiledHash: compiledHash, + wasmSize: arbmath.BytesToUint16(data[26:28]), + footprint: arbmath.BytesToUint16(data[28:30]), + version: arbmath.BytesToUint16(data[30:]), + moduleHash: compiledHash, }, nil } @@ -342,7 +342,7 @@ func (p Programs) setProgram(codehash common.Hash, program Program) error { if err != nil { return err } - return p.compiledHashes.Set(codehash, program.compiledHash) + return p.compiledHashes.Set(codehash, program.moduleHash) } func (p Programs) CodehashVersion(codeHash common.Hash) (uint16, error) { diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index d09cc00d2..cb4853e48 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -96,7 +96,7 @@ func callUserWasm( debug := arbmath.UintToBool(params.debugMode) status, output := callUserWasmRustImpl( - &program.compiledHash, + &program.moduleHash, calldata, params.encode(), evmApi.funcs, diff --git a/go-ethereum b/go-ethereum index d65925551..d16318ab8 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit d65925551350281002cd2a7ca8093ba38e19214b +Subproject commit d16318ab8b159730142fa5a2431cac70d172d9d5 diff --git a/validator/server_api/json.go b/validator/server_api/json.go index b093e91cb..07f39b345 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" - "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/jsonapi" "github.com/offchainlabs/nitro/validator" ) @@ -20,9 +19,8 @@ type BatchInfoJson struct { } type UserWasmJson struct { - ModuleHash common.Hash - Module string - Asm string + Module string + Asm string } type ValidationInputJson struct { @@ -52,16 +50,13 @@ func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJso encData := base64.StdEncoding.EncodeToString(binfo.Data) res.BatchInfo = append(res.BatchInfo, BatchInfoJson{binfo.Number, encData}) } - for call, wasm := range entry.UserWasms { - callBytes := arbmath.Uint16ToBytes(call.Version) - callBytes = append(callBytes, call.CodeHash.Bytes()...) - encCall := base64.StdEncoding.EncodeToString(callBytes) + for moduleHash, info := range entry.UserWasms { + encModuleHash := base64.StdEncoding.EncodeToString(moduleHash[:]) encWasm := UserWasmJson{ - ModuleHash: wasm.ModuleHash, - Module: base64.StdEncoding.EncodeToString(wasm.Module), - Asm: base64.StdEncoding.EncodeToString(wasm.Asm), + Asm: base64.StdEncoding.EncodeToString(info.Asm), + Module: base64.StdEncoding.EncodeToString(info.Module), } - res.UserWasms[encCall] = encWasm + res.UserWasms[encModuleHash] = encWasm } return res } @@ -92,29 +87,24 @@ func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationI } valInput.BatchInfo = append(valInput.BatchInfo, decInfo) } - for call, wasm := range entry.UserWasms { - callBytes, err := base64.StdEncoding.DecodeString(call) + for moduleHash, info := range entry.UserWasms { + decModuleHash, err := base64.StdEncoding.DecodeString(moduleHash) if err != nil { return nil, err } - decCall := state.WasmCall{ - Version: arbmath.BytesToUint16(callBytes[:2]), - CodeHash: common.BytesToHash(callBytes[2:]), - } - asm, err := base64.StdEncoding.DecodeString(wasm.Asm) + asm, err := base64.StdEncoding.DecodeString(info.Asm) if err != nil { return nil, err } - module, err := base64.StdEncoding.DecodeString(wasm.Module) + module, err := base64.StdEncoding.DecodeString(info.Module) if err != nil { return nil, err } - decWasm := state.UserWasm{ - ModuleHash: wasm.ModuleHash, - Module: module, - Asm: asm, + decInfo := state.ActivatedWasm{ + Asm: asm, + Module: module, } - valInput.UserWasms[decCall] = &decWasm + valInput.UserWasms[common.Hash(decModuleHash)] = decInfo } return valInput, nil } diff --git a/validator/server_arb/machine.go b/validator/server_arb/machine.go index eba79b95f..020530141 100644 --- a/validator/server_arb/machine.go +++ b/validator/server_arb/machine.go @@ -20,7 +20,6 @@ import ( "unsafe" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/arbmath" @@ -397,20 +396,20 @@ func (m *ArbitratorMachine) SetPreimageResolver(resolver GoPreimageResolver) err return nil } -func (m *ArbitratorMachine) AddUserWasm(call state.WasmCall, wasm *state.UserWasm, debug bool) error { +func (m *ArbitratorMachine) AddUserWasm(moduleHash common.Hash, module []byte, debug bool) error { defer runtime.KeepAlive(m) if m.frozen { return errors.New("machine frozen") } hashBytes := [32]u8{} - for index, byte := range wasm.ModuleHash.Bytes() { + for index, byte := range moduleHash.Bytes() { hashBytes[index] = u8(byte) } cErr := C.arbitrator_add_user_wasm( m.ptr, - (*u8)(arbutil.SliceToPointer(wasm.Module)), - u32(len(wasm.Module)), - u16(call.Version), + (*u8)(arbutil.SliceToPointer(module)), + u32(len(module)), + u16(0), // TODO: remove u32(arbmath.BoolToUint32(debug)), &C.struct_Bytes32{hashBytes}, ) diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index e89cd20f6..174029f1b 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -106,12 +106,12 @@ func (v *ArbitratorSpawner) loadEntryToMachine(ctx context.Context, entry *valid return fmt.Errorf("error while trying to add sequencer msg for proving: %w", err) } } - for call, wasm := range entry.UserWasms { - err = mach.AddUserWasm(call, wasm, entry.DebugChain) + for moduleHash, info := range entry.UserWasms { + err = mach.AddUserWasm(moduleHash, info.Module, entry.DebugChain) if err != nil { log.Error( "error adding user wasm for proving", - "err", err, "codehash", call.CodeHash, "blockNr", entry.Id, + "err", err, "moduleHash", moduleHash, "blockNr", entry.Id, ) return fmt.Errorf("error adding user wasm for proving: %w", err) } diff --git a/validator/server_jit/jit_machine.go b/validator/server_jit/jit_machine.go index 6de60e912..c1eb3fe45 100644 --- a/validator/server_jit/jit_machine.go +++ b/validator/server_jit/jit_machine.go @@ -206,11 +206,11 @@ func (machine *JitMachine) prove( if err := writeUint32(uint32(len(userWasms))); err != nil { return state, err } - for _, wasm := range userWasms { - if err := writeExact(wasm.ModuleHash[:]); err != nil { + for moduleHash, info := range userWasms { + if err := writeExact(moduleHash[:]); err != nil { return state, err } - if err := writeBytes(wasm.Asm); err != nil { + if err := writeBytes(info.Asm); err != nil { return state, err } } From f52b7e20a69cd8cf47d3191eaca0e882a91810c3 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Thu, 12 Oct 2023 22:01:29 -0600 Subject: [PATCH 07/19] re-use prover modules --- arbitrator/prover/src/lib.rs | 25 ++++++++--------------- arbitrator/prover/src/machine.rs | 16 +++++++-------- arbitrator/prover/src/main.rs | 16 +++++---------- validator/server_arb/machine.go | 12 +++-------- validator/server_arb/validator_spawner.go | 2 +- 5 files changed, 24 insertions(+), 47 deletions(-) diff --git a/arbitrator/prover/src/lib.rs b/arbitrator/prover/src/lib.rs index 23d7cef58..dbb3ba5d0 100644 --- a/arbitrator/prover/src/lib.rs +++ b/arbitrator/prover/src/lib.rs @@ -23,7 +23,7 @@ pub use machine::Machine; use arbutil::Bytes32; use eyre::{Report, Result}; use machine::{ - argument_data_to_inbox, get_empty_preimage_resolver, GlobalState, MachineStatus, + argument_data_to_inbox, get_empty_preimage_resolver, GlobalState, MachineStatus, Module, PreimageResolver, }; use sha3::{Digest, Keccak256}; @@ -203,22 +203,13 @@ pub unsafe extern "C" fn arbitrator_add_inbox_message( #[no_mangle] pub unsafe extern "C" fn arbitrator_add_user_wasm( mach: *mut Machine, - wasm: *const u8, - wasm_len: u32, - version: u16, - debug: u32, - root: *const Bytes32, -) -> *mut libc::c_char { - let wasm = std::slice::from_raw_parts(wasm, wasm_len as usize); - let debug = debug != 0; - - if root.is_null() { - return str_to_c_string("arbitrator_add_user_wasm got null ptr for module hash"); - } - match (*mach).add_program(wasm, version, debug, Some(*root)) { - Ok(_) => ptr::null_mut(), - Err(err) => err_to_c_string(err), - } + module: *const u8, + module_len: usize, + module_hash: *const Bytes32, +) { + let module = std::slice::from_raw_parts(module, module_len); + let module = Module::from_bytes(module); + (*mach).add_stylus_module(module, *module_hash); } /// Like arbitrator_step, but stops early if it hits a host io operation. diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 9b8baab47..82a067d90 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -1127,22 +1127,20 @@ impl Machine { /// Adds a user program to the machine's known set of wasms, compiling it into a link-able module. /// Note that the module produced will need to be configured before execution via hostio calls. - pub fn add_program( - &mut self, - wasm: &[u8], - version: u16, - debug_funcs: bool, - hash: Option, // computed if not already known - ) -> Result { + pub fn add_program(&mut self, wasm: &[u8], version: u16, debug_funcs: bool) -> Result { let mut bin = binary::parse(wasm, Path::new("user"))?; let config = CompileConfig::version(version, debug_funcs); let stylus_data = bin.instrument(&config)?; let module = Module::from_user_binary(&bin, debug_funcs, Some(stylus_data))?; - let hash = hash.unwrap_or_else(|| module.hash()); + let hash = module.hash(); + self.add_stylus_module(module, hash); + Ok(hash) + } + /// Adds a pre-built program to the machine's known set of wasms. + pub fn add_stylus_module(&mut self, module: Module, hash: Bytes32) { self.stylus_modules.insert(hash, module); - Ok(hash) } pub fn from_binaries( diff --git a/arbitrator/prover/src/main.rs b/arbitrator/prover/src/main.rs index 50831cd02..22476f16d 100644 --- a/arbitrator/prover/src/main.rs +++ b/arbitrator/prover/src/main.rs @@ -4,7 +4,7 @@ #![cfg(feature = "native")] use arbutil::{format, Bytes32, Color, DebugColor}; -use eyre::{Context, Result}; +use eyre::{eyre, Context, Result}; use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet}; use prover::{ machine::{GlobalState, InboxIdentifier, Machine, MachineStatus, PreimageResolver, ProofInfo}, @@ -201,16 +201,10 @@ fn main() -> Result<()> { preimage_resolver, )?; - for module in &opts.stylus_modules { - let error = || { - format!( - "failed to read module at {}", - module.to_string_lossy().red() - ) - }; - let wasm = file_bytes(module).wrap_err_with(error)?; - mach.add_program(&wasm, 1, true, None) - .wrap_err_with(error)?; + for path in &opts.stylus_modules { + let err = || eyre!("failed to read module at {}", path.to_string_lossy().red()); + let wasm = file_bytes(path).wrap_err_with(err)?; + mach.add_program(&wasm, 1, true).wrap_err_with(err)?; } if let Some(output_path) = opts.generate_binaries { diff --git a/validator/server_arb/machine.go b/validator/server_arb/machine.go index 020530141..23642b90f 100644 --- a/validator/server_arb/machine.go +++ b/validator/server_arb/machine.go @@ -396,7 +396,7 @@ func (m *ArbitratorMachine) SetPreimageResolver(resolver GoPreimageResolver) err return nil } -func (m *ArbitratorMachine) AddUserWasm(moduleHash common.Hash, module []byte, debug bool) error { +func (m *ArbitratorMachine) AddUserWasm(moduleHash common.Hash, module []byte) error { defer runtime.KeepAlive(m) if m.frozen { return errors.New("machine frozen") @@ -405,17 +405,11 @@ func (m *ArbitratorMachine) AddUserWasm(moduleHash common.Hash, module []byte, d for index, byte := range moduleHash.Bytes() { hashBytes[index] = u8(byte) } - cErr := C.arbitrator_add_user_wasm( + C.arbitrator_add_user_wasm( m.ptr, (*u8)(arbutil.SliceToPointer(module)), - u32(len(module)), - u16(0), // TODO: remove - u32(arbmath.BoolToUint32(debug)), + usize(len(module)), &C.struct_Bytes32{hashBytes}, ) - defer C.free(unsafe.Pointer(cErr)) - if cErr != nil { - return errors.New(C.GoString(cErr)) - } return nil } diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index 174029f1b..85f55ef6c 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -107,7 +107,7 @@ func (v *ArbitratorSpawner) loadEntryToMachine(ctx context.Context, entry *valid } } for moduleHash, info := range entry.UserWasms { - err = mach.AddUserWasm(moduleHash, info.Module, entry.DebugChain) + err = mach.AddUserWasm(moduleHash, info.Module) if err != nil { log.Error( "error adding user wasm for proving", From f88557d061d3184c68f99f9614bace3afed34d9c Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 13 Oct 2023 16:35:57 -0600 Subject: [PATCH 08/19] charge for gas during activation: native + JIT --- arbitrator/jit/src/gostack.rs | 15 ++++-- arbitrator/jit/src/machine.rs | 3 +- arbitrator/jit/src/user/mod.rs | 59 ++++++++--------------- arbitrator/prover/src/programs/config.rs | 7 --- arbitrator/prover/src/programs/prelude.rs | 2 +- arbitrator/stylus/src/lib.rs | 28 +++++------ arbitrator/stylus/src/native.rs | 32 ++++++------ arbos/burn/burn.go | 5 ++ arbos/programs/native.go | 40 +++++++-------- arbos/programs/programs.go | 25 ++-------- arbos/programs/raw.s | 6 +-- arbos/programs/wasm.go | 48 ++++++++---------- precompiles/ArbWasm.go | 5 ++ precompiles/context.go | 4 ++ system_tests/program_test.go | 3 +- 15 files changed, 121 insertions(+), 161 deletions(-) diff --git a/arbitrator/jit/src/gostack.rs b/arbitrator/jit/src/gostack.rs index 14b1d4084..3f355eb18 100644 --- a/arbitrator/jit/src/gostack.rs +++ b/arbitrator/jit/src/gostack.rs @@ -11,7 +11,10 @@ use crate::{ use arbutil::Color; use ouroboros::self_referencing; use rand_pcg::Pcg32; -use std::collections::{BTreeSet, BinaryHeap}; +use std::{ + collections::{BTreeSet, BinaryHeap}, + fmt::Debug, +}; use wasmer::{AsStoreRef, Memory, MemoryView, StoreMut, StoreRef, WasmPtr}; #[self_referencing] @@ -138,6 +141,10 @@ impl GoStack { self.read_u64() as *mut T } + pub unsafe fn read_ref<'a, 'b, T>(&'a mut self) -> &'b T { + &*self.read_ptr() + } + /// TODO: replace `unbox` with a safe id-based API pub fn unbox(&mut self) -> T { let ptr: *mut T = self.read_ptr_mut(); @@ -236,9 +243,9 @@ impl GoStack { data } - pub fn write_slice(&self, ptr: u64, src: &[u8]) { - u32::try_from(ptr).expect("Go pointer not a u32"); - self.view().write(ptr, src).unwrap(); + pub fn write_slice>(&self, ptr: T, src: &[u8]) { + let ptr: u32 = ptr.try_into().map_err(|_| "Go pointer not a u32").unwrap(); + self.view().write(ptr.into(), src).unwrap(); } pub fn read_value_slice(&self, mut ptr: u64, len: u64) -> Vec { diff --git a/arbitrator/jit/src/machine.rs b/arbitrator/jit/src/machine.rs index a634290f2..c3069ac78 100644 --- a/arbitrator/jit/src/machine.rs +++ b/arbitrator/jit/src/machine.rs @@ -115,11 +115,10 @@ pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Sto github!("wavmio.readDelayedInboxMessage") => func!(wavmio::read_delayed_inbox_message), github!("wavmio.resolvePreImage") => func!(wavmio::resolve_preimage), - github!("arbos/programs.compileUserWasmRustImpl") => func!(user::compile_user_wasm), + github!("arbos/programs.activateWasmRustImpl") => func!(user::activate_wasm), github!("arbos/programs.callUserWasmRustImpl") => func!(user::call_user_wasm), github!("arbos/programs.readRustVecLenImpl") => func!(user::read_rust_vec_len), github!("arbos/programs.rustVecIntoSliceImpl") => func!(user::rust_vec_into_slice), - github!("arbos/programs.rustModuleDropImpl") => func!(user::drop_module), github!("arbos/programs.rustConfigImpl") => func!(user::rust_config_impl), github!("arbos/programs.rustEvmDataImpl") => func!(user::evm_data_impl), diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index 92f2618fd..01d29dab2 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -5,6 +5,7 @@ use crate::{ gostack::GoStack, machine::{Escape, MaybeEscape, WasmEnvMut}, user::evm_api::exec_wasm, + wavmio::Bytes32, }; use arbutil::{ evm::{user::UserOutcome, EvmData}, @@ -17,49 +18,44 @@ use stylus::native; mod evm_api; -/// Compiles and instruments a user wasm. +/// Instruments a user wasm. /// /// # Go side /// /// The Go compiler expects the call to take the form -/// λ(wasm []byte, pageLimit, version u16, debug u32) (module *Vec, info WasmInfo, err *Vec) +/// λ(wasm []byte, pageLimit, version u16, debug u32, modHash *hash, gas *u64) (footprint u16, err *Vec) /// /// These values are placed on the stack as follows -/// stack: || wasm... || pageLimit | version | debug || mod ptr || info... || err ptr || -/// info: || footprint | 2 pad | size || +/// || wasm... || pageLimit | version | debug || modhash ptr || gas ptr || footprint | 6 pad || err ptr || /// -pub fn compile_user_wasm(env: WasmEnvMut, sp: u32) { +pub fn activate_wasm(env: WasmEnvMut, sp: u32) { let mut sp = GoStack::simple(sp, &env); let wasm = sp.read_go_slice_owned(); let page_limit = sp.read_u16(); let version = sp.read_u16(); let debug = sp.read_bool32(); - let (out_hash_ptr, out_hash_len) = sp.read_go_slice(); + let module_hash = sp.read_go_ptr(); + let gas = sp.read_go_ptr(); macro_rules! error { ($error:expr) => {{ let error = $error.wrap_err("failed to compile").debug_bytes(); - sp.write_nullptr(); - sp.skip_space(); // skip info + sp.write_u64_raw(gas, 0); + sp.write_slice(module_hash, &Bytes32::default()); + sp.skip_space(); sp.write_ptr(heapify(error)); return; }}; } - if out_hash_len != 32 { - error!(eyre::eyre!( - "Go attempting to read module hash into bad buffer length: {out_hash_len}" - )); - } - - let (asm, module, info) = match native::compile_user_wasm(&wasm, version, page_limit, debug) { + let gas_left = &mut sp.read_u64_raw(gas); + let (_, module, pages) = match native::activate(&wasm, version, page_limit, debug, gas_left) { Ok(result) => result, Err(error) => error!(error), }; - - sp.write_slice(out_hash_ptr, module.hash().as_slice()); - sp.write_ptr(heapify(asm)); - sp.write_u16(info.footprint).skip_u16().write_u32(info.size); // wasm info + sp.write_u64_raw(gas, *gas_left); + sp.write_slice(module_hash, &module.hash().0); + sp.write_u16(pages).skip_space(); sp.write_nullptr(); } @@ -68,13 +64,12 @@ pub fn compile_user_wasm(env: WasmEnvMut, sp: u32) { /// # Go side /// /// The Go compiler expects the call to take the form -/// λ( -/// hash *common.Hash, calldata []byte, params *Configs, evmApi []byte, evmData: *EvmData, -/// gas *u64, root *[32]byte -/// ) -> (status byte, out *Vec) +/// λ(moduleHash *[32]byte, calldata []byte, params *Configs, evmApi []byte, evmData: *EvmData, gas *u64) ( +/// status byte, out *Vec, +/// ) /// /// These values are placed on the stack as follows -/// || hash || calldata... || params || evmApi... || evmData || gas || root || status | 3 pad | out ptr || +/// || modHash || calldata... || params || evmApi... || evmData || gas || status | 7 pad | out ptr || /// pub fn call_user_wasm(env: WasmEnvMut, sp: u32) -> MaybeEscape { let sp = &mut GoStack::simple(sp, &env); @@ -127,7 +122,7 @@ pub fn call_user_wasm(env: WasmEnvMut, sp: u32) -> MaybeEscape { /// pub fn read_rust_vec_len(env: WasmEnvMut, sp: u32) { let mut sp = GoStack::simple(sp, &env); - let vec: &Vec = unsafe { &*sp.read_ptr() }; + let vec: &Vec = unsafe { sp.read_ref() }; sp.write_u32(vec.len() as u32); } @@ -149,20 +144,6 @@ pub fn rust_vec_into_slice(env: WasmEnvMut, sp: u32) { mem::drop(vec) } -/// Drops module bytes. Note that in user-host this would be a `Machine`. -/// -/// # Go side -/// -/// The Go compiler expects the call to take the form -/// λ(module *Vec) -/// -pub fn drop_module(env: WasmEnvMut, sp: u32) { - let mut sp = GoStack::simple(sp, &env); - if let Some(module) = sp.unbox_option::>() { - mem::drop(module); - } -} - /// Creates a `StylusConfig` from its component parts. /// /// # Go side diff --git a/arbitrator/prover/src/programs/config.rs b/arbitrator/prover/src/programs/config.rs index bdb41291f..4d4f331ef 100644 --- a/arbitrator/prover/src/programs/config.rs +++ b/arbitrator/prover/src/programs/config.rs @@ -208,10 +208,3 @@ impl CompileConfig { Store::new(compiler) } } - -/// Information about a wasm for pricing purposes. -#[repr(C)] -pub struct WasmPricingInfo { - pub footprint: u16, - pub size: u32, -} diff --git a/arbitrator/prover/src/programs/prelude.rs b/arbitrator/prover/src/programs/prelude.rs index edf782beb..4db8e0341 100644 --- a/arbitrator/prover/src/programs/prelude.rs +++ b/arbitrator/prover/src/programs/prelude.rs @@ -2,7 +2,7 @@ // For license information, see https://github.com/nitro/blob/master/LICENSE pub use super::{ - config::{CompileConfig, StylusConfig, WasmPricingInfo}, + config::{CompileConfig, StylusConfig}, counter::CountingMachine, depth::DepthCheckedMachine, meter::{GasMeteredMachine, MachineMeter, MeteredMachine}, diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index 83f02656d..b2f29ba06 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -100,36 +100,36 @@ impl RustVec { } } -/// Compiles a user program to its native representation. -/// The `output` is either the serialized module or an error string. +/// Activates a user program. +/// The `output` is either the serialized asm & module or an error string. /// /// # Safety /// -/// output, pricing_info, module_hash must not be null. +/// `output`, `asm_len`, `module_hash`, `footprint`, and `gas` must not be null. #[no_mangle] -pub unsafe extern "C" fn stylus_compile( +pub unsafe extern "C" fn stylus_activate( wasm: GoSliceData, page_limit: u16, version: u16, - debug_mode: bool, - out_pricing_info: *mut WasmPricingInfo, + debug: bool, output: *mut RustVec, asm_len: *mut usize, module_hash: *mut Bytes32, + footprint: *mut u16, + gas: *mut u64, ) -> UserOutcomeKind { let wasm = wasm.slice(); let output = &mut *output; let module_hash = &mut *module_hash; + let gas = &mut *gas; - let (asm, module, pricing_info) = - match native::compile_user_wasm(wasm, version, page_limit, debug_mode) { - Ok(val) => val, - Err(err) => return output.write_err(err), - }; - + let (asm, module, pages) = match native::activate(wasm, version, page_limit, debug, gas) { + Ok(val) => val, + Err(err) => return output.write_err(err), + }; *asm_len = asm.len(); *module_hash = module.hash(); - *out_pricing_info = pricing_info; + *footprint = pages; let mut data = asm; data.extend(&*module.into_bytes()); @@ -141,7 +141,7 @@ pub unsafe extern "C" fn stylus_compile( /// /// # Safety /// -/// `module` must represent a valid module produced from `stylus_compile`. +/// `module` must represent a valid module produced from `stylus_activate`. /// `output` and `gas` must not be null. #[no_mangle] pub unsafe extern "C" fn stylus_call( diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index 99631499b..736611194 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -15,7 +15,7 @@ use prover::{ binary::WasmBinary, machine::Module as ProverModule, programs::{ - config::{PricingParams, WasmPricingInfo}, + config::PricingParams, counter::{Counter, CountingMachine, OP_OFFSETS}, depth::STYLUS_STACK_LEFT, meter::{STYLUS_INK_LEFT, STYLUS_INK_STATUS}, @@ -374,25 +374,29 @@ pub fn module(wasm: &[u8], compile: CompileConfig) -> Result> { Ok(module.to_vec()) } -pub fn compile_user_wasm( +pub fn activate( wasm: &[u8], version: u16, page_limit: u16, - debug_mode: bool, -) -> Result<(Vec, ProverModule, WasmPricingInfo)> { - let compile = CompileConfig::version(version, debug_mode); + debug: bool, + gas: &mut u64, +) -> Result<(Vec, ProverModule, u16)> { + // paid for by the 3 million gas charge in program.go + let compile = CompileConfig::version(version, debug); let (bin, stylus_data, footprint) = WasmBinary::parse_user(wasm, page_limit, &compile).wrap_err("failed to parse wasm")?; - let prover_module = - ProverModule::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data)) - .wrap_err("failed to build module from program")?; + // naively charge 11 million gas to do the rest. + // in the future we'll implement a proper compilation pricing mechanism. + if *gas < 11_000_000 { + *gas = 0; + bail!("out of gas"); + } + *gas -= 11_000_000; - let info = WasmPricingInfo { - size: wasm.len().try_into()?, - footprint, - }; - let asm = module(wasm, compile).wrap_err("failed to generate stylus module")?; + let module = ProverModule::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data)) + .wrap_err("failed to build user module")?; - Ok((asm, prover_module, info)) + let asm = self::module(wasm, compile).wrap_err("failed to generate stylus module")?; + Ok((asm, module, footprint)) } diff --git a/arbos/burn/burn.go b/arbos/burn/burn.go index 973452cab..c3d778636 100644 --- a/arbos/burn/burn.go +++ b/arbos/burn/burn.go @@ -13,6 +13,7 @@ import ( type Burner interface { Burn(amount uint64) error Burned() uint64 + GasLeft() uint64 BurnOut() error Restrict(err error) HandleError(err error) error @@ -46,6 +47,10 @@ func (burner *SystemBurner) BurnOut() error { panic("called BurnOut on a system burner") } +func (burner *SystemBurner) GasLeft() uint64 { + panic("called GasLeft on a system burner") +} + func (burner *SystemBurner) Restrict(err error) { if err != nil { glog.Error("Restrict() received an error", "err", err) diff --git a/arbos/programs/native.go b/arbos/programs/native.go index e1b116674..797d029f5 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -21,6 +21,7 @@ import "C" import ( "errors" "fmt" + "math" "math/big" "github.com/ethereum/go-ethereum/common" @@ -30,6 +31,7 @@ import ( "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/util/arbmath" ) type u8 = C.uint8_t @@ -43,7 +45,7 @@ type bytes32 = C.Bytes32 type rustVec = C.RustVec type rustSlice = C.RustSlice -func compileUserWasm( +func activateProgram( db vm.StateDB, program common.Address, wasm []byte, @@ -51,38 +53,37 @@ func compileUserWasm( version uint16, debug bool, burner burn.Burner, -) (*wasmPricingInfo, common.Hash, error) { +) (common.Hash, uint16, error) { output := &rustVec{} asmLen := usize(0) - moduleHash := &bytes32{} - rustInfo := &C.WasmPricingInfo{} + footprint := uint16(math.MaxUint16) + gas := burner.GasLeft() - status := userStatus(C.stylus_compile( + status := userStatus(C.stylus_activate( goSlice(wasm), u16(page_limit), u16(version), cbool(debug), - rustInfo, output, &asmLen, moduleHash, + (*u16)(&footprint), + (*u64)(&gas), )) - data, msg, err := status.toResult(output.intoBytes(), debug) + if err := burner.Burn(arbmath.SaturatingUSub(burner.GasLeft(), gas)); err != nil { + return common.Hash{}, footprint, err + } + data, msg, err := status.toResult(output.intoBytes(), debug) if err != nil { if debug { - log.Warn("stylus parse failed", "err", err, "msg", msg, "program", program) + log.Warn("activation failed", "err", err, "msg", msg, "program", program) } if errors.Is(err, vm.ErrExecutionReverted) { - return nil, common.Hash{}, fmt.Errorf("%w: %s", ErrProgramActivation, msg) + return common.Hash{}, footprint, fmt.Errorf("%w: %s", ErrProgramActivation, msg) } - return nil, common.Hash{}, err - } - - info := rustInfo.decode() - if err := payForCompilation(burner, &info); err != nil { - return nil, common.Hash{}, err + return common.Hash{}, footprint, err } hash := moduleHash.toHash() @@ -91,7 +92,7 @@ func compileUserWasm( module := data[split:] db.ActivateWasm(hash, asm, module) - return &info, hash, err + return hash, footprint, err } func callUserWasm( @@ -369,10 +370,3 @@ func (data *evmData) encode() C.EvmData { tracing: cbool(data.tracing), } } - -func (info *C.WasmPricingInfo) decode() wasmPricingInfo { - return wasmPricingInfo{ - footprint: uint16(info.footprint), - size: uint32(info.size), - } -} diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 218c92f4f..36d1f4663 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbcompress" - "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/storage" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbutil" @@ -173,6 +172,7 @@ func (p Programs) SetCallScalar(scalar uint16) error { func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode bool) (uint16, bool, error) { statedb := evm.StateDB codeHash := statedb.GetCodeHash(address) + burner := p.programs.Burner() version, err := p.StylusVersion() if err != nil { @@ -198,12 +198,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode } pageLimit = arbmath.SaturatingUSub(pageLimit, statedb.GetStylusPagesOpen()) - // charge 3 million up front to begin compilation - burner := p.programs.Burner() - if err := burner.Burn(3000000); err != nil { - return 0, false, err - } - info, compiledHash, err := compileUserWasm(statedb, address, wasm, pageLimit, version, debugMode, burner) + moduleHash, footprint, err := activateProgram(statedb, address, wasm, pageLimit, version, debugMode, burner) if err != nil { return 0, true, err } @@ -213,9 +208,9 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode programData := Program{ wasmSize: wasmSize, - footprint: info.footprint, + footprint: footprint, version: version, - moduleHash: compiledHash, + moduleHash: moduleHash, } return version, false, p.setProgram(codeHash, programData) } @@ -436,15 +431,3 @@ func (status userStatus) toResult(data []byte, debug bool) ([]byte, string, erro return nil, msg, vm.ErrExecutionReverted } } - -type wasmPricingInfo struct { - footprint uint16 - size uint32 -} - -// Pay for compilation. Right now this is a fixed amount of gas. -// In the future, costs will be variable and based on the wasm. -// Note: memory expansion costs are baked into compilation charging. -func payForCompilation(burner burn.Burner, _info *wasmPricingInfo) error { - return burner.Burn(11000000) -} diff --git a/arbos/programs/raw.s b/arbos/programs/raw.s index f855d53c3..c222bfbe2 100644 --- a/arbos/programs/raw.s +++ b/arbos/programs/raw.s @@ -6,7 +6,7 @@ #include "textflag.h" -TEXT ·compileUserWasmRustImpl(SB), NOSPLIT, $0 +TEXT ·activateWasmRustImpl(SB), NOSPLIT, $0 CallImport RET @@ -22,10 +22,6 @@ TEXT ·rustVecIntoSliceImpl(SB), NOSPLIT, $0 CallImport RET -TEXT ·rustModuleDropImpl(SB), NOSPLIT, $0 - CallImport - RET - TEXT ·rustConfigImpl(SB), NOSPLIT, $0 CallImport RET diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index cb4853e48..aaf1006a9 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -28,21 +28,20 @@ type usize = uintptr // opaque types type rustVec byte type rustConfig byte -type rustMachine byte +type rustModule byte type rustEvmData byte -func compileUserWasmRustImpl( - wasm []byte, pageLimit, version u16, debugMode u32, outMachineHash []byte, -) (machine *rustMachine, info wasmPricingInfo, err *rustVec) +func activateWasmRustImpl( + wasm []byte, pageLimit, version u16, debugMode u32, moduleHash *hash, gas *u64, +) (footprint u16, err *rustVec) func callUserWasmRustImpl( - compiledHash *hash, calldata []byte, params *rustConfig, evmApi []byte, - evmData *rustEvmData, gas *u64, + moduleHash *hash, calldata []byte, params *rustConfig, evmApi []byte, evmData *rustEvmData, gas *u64, ) (status userStatus, out *rustVec) func readRustVecLenImpl(vec *rustVec) (len u32) func rustVecIntoSliceImpl(vec *rustVec, ptr *byte) -func rustModuleDropImpl(mach *rustMachine) +func rustModuleDropImpl(mach *rustModule) func rustConfigImpl(version u16, maxDepth, inkPrice, debugMode u32) *rustConfig func rustEvmDataImpl( blockBasefee *hash, @@ -59,7 +58,7 @@ func rustEvmDataImpl( reentrant u32, ) *rustEvmData -func compileUserWasm( +func activateProgram( db vm.StateDB, program addr, wasm []byte, @@ -67,16 +66,20 @@ func compileUserWasm( version u16, debug bool, burner burn.Burner, -) (*wasmPricingInfo, common.Hash, error) { - module, info, hash, err := compileUserWasmRustWrapper(db, program, wasm, pageLimit, version, debug) - defer rustModuleDropImpl(module) - if err != nil { - return nil, common.Hash{}, err +) (common.Hash, u16, error) { + debugMode := arbmath.BoolToUint32(debug) + moduleHash := common.Hash{} + gas := burner.GasLeft() + + footprint, err := activateWasmRustImpl(wasm, pageLimit, version, debugMode, &moduleHash, &gas) + if err := burner.Burn(arbmath.SaturatingUSub(burner.GasLeft(), gas)); err != nil { + return moduleHash, footprint, err } - if err := payForCompilation(burner, &info); err != nil { - return nil, common.Hash{}, err + if err != nil { + _, _, err := userFailure.toResult(err.intoSlice(), debug) + return moduleHash, footprint, err } - return &info, hash, err + return moduleHash, footprint, nil } func callUserWasm( @@ -107,19 +110,6 @@ func callUserWasm( return data, err } -func compileUserWasmRustWrapper( - db vm.StateDB, program addr, wasm []byte, pageLimit, version u16, debug bool, -) (*rustMachine, wasmPricingInfo, common.Hash, error) { - debugMode := arbmath.BoolToUint32(debug) - outHash := common.Hash{} - machine, info, err := compileUserWasmRustImpl(wasm, pageLimit, version, debugMode, outHash[:]) - if err != nil { - _, _, err := userFailure.toResult(err.intoSlice(), debug) - return nil, info, outHash, err - } - return machine, info, outHash, nil -} - func (vec *rustVec) intoSlice() []byte { len := readRustVecLenImpl(vec) slice := make([]byte, len) diff --git a/precompiles/ArbWasm.go b/precompiles/ArbWasm.go index aeb61d082..be4ad44fc 100644 --- a/precompiles/ArbWasm.go +++ b/precompiles/ArbWasm.go @@ -13,6 +13,11 @@ type ArbWasm struct { // Compile a wasm program with the latest instrumentation func (con ArbWasm) ActivateProgram(c ctx, evm mech, program addr) (uint16, error) { + + // charge 3 million up front to begin activation + if err := c.Burn(3000000); err != nil { + return 0, err + } version, takeAllGas, err := c.State.Programs().ActivateProgram(evm, program, evm.ChainConfig().DebugMode()) if takeAllGas { _ = c.BurnOut() diff --git a/precompiles/context.go b/precompiles/context.go index bea90fbc6..b52c3ce9a 100644 --- a/precompiles/context.go +++ b/precompiles/context.go @@ -53,6 +53,10 @@ func (c *Context) BurnOut() error { return vm.ErrOutOfGas } +func (c *Context) GasLeft() uint64 { + return c.gasLeft +} + func (c *Context) Restrict(err error) { log.Crit("A metered burner was used for access-controlled work", "error", err) } diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 36bcb2f45..d1d216538 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -899,7 +899,7 @@ func TestProgramActivateFails(t *testing.T) { } func testActivateFails(t *testing.T, jit bool) { - ctx, node, _, l2client, auth, cleanup := setupProgramTest(t, false) + ctx, node, _, l2client, auth, cleanup := setupProgramTest(t, jit) defer cleanup() arbWasm, err := precompilesgen.NewArbWasm(types.ArbWasmAddress, l2client) @@ -1189,7 +1189,6 @@ func validateBlockRange( t *testing.T, blocks []uint64, jit bool, ctx context.Context, node *arbnode.Node, l2client *ethclient.Client, ) { - t.Helper() waitForSequencer(t, node, arbmath.MaxInt(blocks...)) blockHeight, err := l2client.BlockNumber(ctx) Require(t, err) From 411d64492c741b713447d87a8f48551dcbe3bf50 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Fri, 13 Oct 2023 16:57:41 -0600 Subject: [PATCH 09/19] charge for gas in arbitrator --- arbitrator/jit/src/gostack.rs | 2 +- arbitrator/jit/src/user/mod.rs | 2 +- arbitrator/prover/src/programs/mod.rs | 33 +++++++- arbitrator/stylus/src/native.rs | 16 +--- .../wasm-libraries/user-host/src/link.rs | 79 ++++++------------- 5 files changed, 59 insertions(+), 73 deletions(-) diff --git a/arbitrator/jit/src/gostack.rs b/arbitrator/jit/src/gostack.rs index 3f355eb18..63cf2547b 100644 --- a/arbitrator/jit/src/gostack.rs +++ b/arbitrator/jit/src/gostack.rs @@ -141,7 +141,7 @@ impl GoStack { self.read_u64() as *mut T } - pub unsafe fn read_ref<'a, 'b, T>(&'a mut self) -> &'b T { + pub unsafe fn read_ref<'a, T>(&mut self) -> &'a T { &*self.read_ptr() } diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index 01d29dab2..e1697d47f 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -39,7 +39,7 @@ pub fn activate_wasm(env: WasmEnvMut, sp: u32) { macro_rules! error { ($error:expr) => {{ - let error = $error.wrap_err("failed to compile").debug_bytes(); + let error = $error.wrap_err("failed to activate").debug_bytes(); sp.write_u64_raw(gas, 0); sp.write_slice(module_hash, &Bytes32::default()); sp.skip_space(); diff --git a/arbitrator/prover/src/programs/mod.rs b/arbitrator/prover/src/programs/mod.rs index ccc3c7664..5b2562822 100644 --- a/arbitrator/prover/src/programs/mod.rs +++ b/arbitrator/prover/src/programs/mod.rs @@ -3,11 +3,12 @@ use crate::{ binary::{ExportKind, WasmBinary}, + machine::Module, memory::MemoryType, value::{FunctionType as ArbFunctionType, Value}, }; use arbutil::Color; -use eyre::{bail, eyre, Report, Result}; +use eyre::{bail, eyre, Report, Result, WrapErr}; use fnv::FnvHashMap as HashMap; use std::fmt::Debug; use wasmer_types::{ @@ -16,6 +17,8 @@ use wasmer_types::{ }; use wasmparser::{Operator, Type as WpType}; +use self::config::CompileConfig; + #[cfg(feature = "native")] use { super::value, @@ -379,3 +382,31 @@ impl StylusData { ) } } + +impl Module { + pub fn activate( + wasm: &[u8], + version: u16, + page_limit: u16, + debug: bool, + gas: &mut u64, + ) -> Result<(Self, u16)> { + // paid for by the 3 million gas charge in program.go + let compile = CompileConfig::version(version, debug); + let (bin, stylus_data, footprint) = + WasmBinary::parse_user(wasm, page_limit, &compile).wrap_err("failed to parse wasm")?; + + // naively charge 11 million gas to do the rest. + // in the future we'll implement a proper compilation pricing mechanism. + if *gas < 11_000_000 { + *gas = 0; + bail!("out of gas"); + } + *gas -= 11_000_000; + + let module = Self::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data)) + .wrap_err("failed to build user module")?; + + Ok((module, footprint)) + } +} diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index 736611194..b34d3f195 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -12,7 +12,6 @@ use arbutil::{ }; use eyre::{bail, eyre, Context, ErrReport, Result}; use prover::{ - binary::WasmBinary, machine::Module as ProverModule, programs::{ config::PricingParams, @@ -381,21 +380,8 @@ pub fn activate( debug: bool, gas: &mut u64, ) -> Result<(Vec, ProverModule, u16)> { - // paid for by the 3 million gas charge in program.go let compile = CompileConfig::version(version, debug); - let (bin, stylus_data, footprint) = - WasmBinary::parse_user(wasm, page_limit, &compile).wrap_err("failed to parse wasm")?; - - // naively charge 11 million gas to do the rest. - // in the future we'll implement a proper compilation pricing mechanism. - if *gas < 11_000_000 { - *gas = 0; - bail!("out of gas"); - } - *gas -= 11_000_000; - - let module = ProverModule::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data)) - .wrap_err("failed to build user module")?; + let (module, footprint) = ProverModule::activate(wasm, version, page_limit, debug, gas)?; let asm = self::module(wasm, compile).wrap_err("failed to generate stylus module")?; Ok((asm, module, footprint)) diff --git a/arbitrator/wasm-libraries/user-host/src/link.rs b/arbitrator/wasm-libraries/user-host/src/link.rs index 70c2b5cdd..51a530362 100644 --- a/arbitrator/wasm-libraries/user-host/src/link.rs +++ b/arbitrator/wasm-libraries/user-host/src/link.rs @@ -4,13 +4,13 @@ use crate::{evm_api::ApiCaller, Program, PROGRAMS}; use arbutil::{ evm::{js::JsEvmApi, user::UserOutcomeKind, EvmData}, - heapify, wavm, + format::DebugBytes, + heapify, wavm, Bytes32, }; use go_abi::GoStack; use prover::{ - binary::WasmBinary, machine::Module, - programs::config::{CompileConfig, PricingParams, StylusConfig}, + programs::config::{PricingParams, StylusConfig}, }; use std::mem; @@ -35,19 +35,18 @@ extern "C" { #[repr(C, align(256))] struct MemoryLeaf([u8; 32]); -/// Compiles and instruments a user wasm. +/// Instruments a user wasm. /// /// # Safety /// /// The Go compiler expects the call to take the form -/// λ(wasm []byte, pageLimit, version u16, debug u32, machineHash []byte) (module *Module, info WasmInfo, err *Vec) +/// λ(wasm []byte, pageLimit, version u16, debug u32, modHash *hash, gas *u64) (footprint u16, err *Vec) /// /// These values are placed on the stack as follows -/// stack: || wasm... || pageLimit | version | debug || mach ptr || info... || err ptr || -/// info: || footprint | 2 pad | size || +/// || wasm... || pageLimit | version | debug || modhash ptr || gas ptr || footprint | 6 pad || err ptr || /// #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_compileUserWasmRustImpl( +pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_activateWasmRustImpl( sp: usize, ) { let mut sp = GoStack::new(sp); @@ -55,40 +54,28 @@ pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_compil let page_limit = sp.read_u16(); let version = sp.read_u16(); let debug = sp.read_bool32(); - let (out_hash_ptr, out_hash_len) = sp.read_go_slice(); + let module_hash = sp.read_go_ptr(); + let gas = sp.read_go_ptr(); macro_rules! error { ($msg:expr, $error:expr) => {{ - let error = $error.wrap_err($msg); - let error = format!("{error:?}").as_bytes().to_vec(); - sp.write_nullptr(); - sp.skip_space(); // skip footprint + let error = $error.wrap_err($msg).debug_bytes(); + wavm::caller_store64(gas, 0); + wavm::write_bytes32(module_hash, Bytes32::default()); + sp.skip_space(); sp.write_ptr(heapify(error)); return; }}; } - let compile = CompileConfig::version(version, debug); - let (bin, stylus_data, footprint) = match WasmBinary::parse_user(&wasm, page_limit, &compile) { - Ok(parse) => parse, - Err(error) => error!("failed to parse program", error), - }; - - let module = match Module::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data)) { - Ok(module) => module, - Err(error) => error!("failed to instrument program", error), - }; - - if out_hash_len != 32 { - error!("Go attempting to read compiled machine hash into bad buffer",eyre::eyre!("buffer length: {out_hash_ptr}")); - } - wavm::write_slice(module.hash().as_slice(), out_hash_ptr); - - let Ok(wasm_len) = TryInto::::try_into(wasm.len()) else { - error!("wasm len not u32",eyre::eyre!("wasm length: {}", wasm.len())); + let gas_left = &mut wavm::caller_load64(gas); + let (module, pages) = match Module::activate(&wasm, version, page_limit, debug, gas_left) { + Ok(data) => data, + Err(error) => error!("failed to activate", error), }; - sp.write_ptr(heapify(module)); - sp.write_u16(footprint).skip_u16().write_u32(wasm_len); + wavm::caller_store64(gas, *gas_left); + wavm::write_bytes32(module_hash, module.hash()); + sp.write_u16(pages).skip_space(); sp.write_nullptr(); } @@ -97,13 +84,12 @@ pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_compil /// # Safety /// /// The Go compiler expects the call to take the form -/// λ( -/// hash *common.Hash, calldata []byte, params *Configs, evmApi []byte, evmData: *EvmData, -/// gas *u64, root *[32]byte -/// ) -> (status byte, out *Vec) +/// λ(moduleHash *[32]byte, calldata []byte, params *Configs, evmApi []byte, evmData: *EvmData, gas *u64) ( +/// status byte, out *Vec, +/// ) /// /// These values are placed on the stack as follows -/// || hash || calldata... || params || evmApi... || evmData || gas || root || status | 3 pad | out ptr || +/// || modHash || calldata... || params || evmApi... || evmData || gas || status | 7 pad | out ptr || /// #[no_mangle] pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_callUserWasmRustImpl( @@ -204,23 +190,6 @@ pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_rustVe mem::drop(vec) } -/// Drops a `Module`. -/// -/// # Safety -/// -/// The Go compiler expects the call to take the form -/// λ(mach *Module) -/// -#[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_rustModuleDropImpl( - sp: usize, -) { - let mut sp = GoStack::new(sp); - if let Some(module) = sp.unbox_option::() { - mem::drop(module); - } -} - /// Creates a `StylusConfig` from its component parts. /// /// # Safety From b8598e0ac12bc563da33d32795d5bd72ab68668f Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sat, 14 Oct 2023 11:28:19 -0600 Subject: [PATCH 10/19] address review comments --- arbitrator/jit/src/user/mod.rs | 7 ++++++- arbitrator/stylus/src/lib.rs | 9 +++++++-- arbitrator/wasm-libraries/user-host/src/link.rs | 7 ++++++- arbos/burn/burn.go | 4 ++-- arbos/programs/native.go | 7 +------ arbos/programs/programs.go | 10 +++++----- arbos/programs/wasm.go | 7 ++----- precompiles/context.go | 4 ++-- 8 files changed, 31 insertions(+), 24 deletions(-) diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index e1697d47f..425afb43b 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -18,10 +18,15 @@ use stylus::native; mod evm_api; -/// Instruments a user wasm. +/// Instruments and "activates" a user wasm, producing a unique module hash. +/// +/// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer. +/// The amount left is written back at the end of the call. /// /// # Go side /// +/// The `modHash` and `gas` pointers must not be null. +/// /// The Go compiler expects the call to take the form /// λ(wasm []byte, pageLimit, version u16, debug u32, modHash *hash, gas *u64) (footprint u16, err *Vec) /// diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index b2f29ba06..fd406ee1b 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -100,8 +100,13 @@ impl RustVec { } } -/// Activates a user program. -/// The `output` is either the serialized asm & module or an error string. +/// Instruments and "activates" a user wasm. +/// +/// The `output` is either the serialized asm & module pair or an error string. +/// Returns consensus info such as the module hash and footprint on success. +/// +/// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer. +/// The amount left is written back at the end of the call. /// /// # Safety /// diff --git a/arbitrator/wasm-libraries/user-host/src/link.rs b/arbitrator/wasm-libraries/user-host/src/link.rs index 51a530362..a1d829a45 100644 --- a/arbitrator/wasm-libraries/user-host/src/link.rs +++ b/arbitrator/wasm-libraries/user-host/src/link.rs @@ -35,10 +35,15 @@ extern "C" { #[repr(C, align(256))] struct MemoryLeaf([u8; 32]); -/// Instruments a user wasm. +/// Instruments and "activates" a user wasm, producing a unique module hash. +/// +/// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer. +/// The amount left is written back at the end of the call. /// /// # Safety /// +/// The `modHash` and `gas` pointers must not be null. +/// /// The Go compiler expects the call to take the form /// λ(wasm []byte, pageLimit, version u16, debug u32, modHash *hash, gas *u64) (footprint u16, err *Vec) /// diff --git a/arbos/burn/burn.go b/arbos/burn/burn.go index c3d778636..665ff4e58 100644 --- a/arbos/burn/burn.go +++ b/arbos/burn/burn.go @@ -13,7 +13,7 @@ import ( type Burner interface { Burn(amount uint64) error Burned() uint64 - GasLeft() uint64 + GasLeft() *uint64 BurnOut() error Restrict(err error) HandleError(err error) error @@ -47,7 +47,7 @@ func (burner *SystemBurner) BurnOut() error { panic("called BurnOut on a system burner") } -func (burner *SystemBurner) GasLeft() uint64 { +func (burner *SystemBurner) GasLeft() *uint64 { panic("called GasLeft on a system burner") } diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 797d029f5..9aa541c9c 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -31,7 +31,6 @@ import ( "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/util/arbmath" ) type u8 = C.uint8_t @@ -58,7 +57,6 @@ func activateProgram( asmLen := usize(0) moduleHash := &bytes32{} footprint := uint16(math.MaxUint16) - gas := burner.GasLeft() status := userStatus(C.stylus_activate( goSlice(wasm), @@ -69,11 +67,8 @@ func activateProgram( &asmLen, moduleHash, (*u16)(&footprint), - (*u64)(&gas), + (*u64)(burner.GasLeft()), )) - if err := burner.Burn(arbmath.SaturatingUSub(burner.GasLeft(), gas)); err != nil { - return common.Hash{}, footprint, err - } data, msg, err := status.toResult(output.intoBytes(), debug) if err != nil { diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 36d1f4663..7dd96f3a1 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -22,7 +22,7 @@ import ( type Programs struct { backingStorage *storage.Storage programs *storage.Storage - compiledHashes *storage.Storage + moduleHashes *storage.Storage inkPrice storage.StorageBackedUint24 maxStackDepth storage.StorageBackedUint32 freePages storage.StorageBackedUint16 @@ -43,7 +43,7 @@ type Program struct { type uint24 = arbmath.Uint24 var programDataKey = []byte{0} -var machineHashesKey = []byte{1} +var moduleHashesKey = []byte{1} const ( versionOffset uint64 = iota @@ -93,7 +93,7 @@ func Open(sto *storage.Storage) *Programs { return &Programs{ backingStorage: sto, programs: sto.OpenSubStorage(programDataKey), - compiledHashes: sto.OpenSubStorage(machineHashesKey), + moduleHashes: sto.OpenSubStorage(moduleHashesKey), inkPrice: sto.OpenStorageBackedUint24(inkPriceOffset), maxStackDepth: sto.OpenStorageBackedUint32(maxStackDepthOffset), freePages: sto.OpenStorageBackedUint16(freePagesOffset), @@ -316,7 +316,7 @@ func (p Programs) deserializeProgram(codeHash common.Hash) (Program, error) { if err != nil { return Program{}, err } - compiledHash, err := p.compiledHashes.Get(codeHash) + compiledHash, err := p.moduleHashes.Get(codeHash) if err != nil { return Program{}, err } @@ -337,7 +337,7 @@ func (p Programs) setProgram(codehash common.Hash, program Program) error { if err != nil { return err } - return p.compiledHashes.Set(codehash, program.moduleHash) + return p.moduleHashes.Set(codehash, program.moduleHash) } func (p Programs) CodehashVersion(codeHash common.Hash) (uint16, error) { diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index aaf1006a9..5f72c1335 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -69,12 +69,9 @@ func activateProgram( ) (common.Hash, u16, error) { debugMode := arbmath.BoolToUint32(debug) moduleHash := common.Hash{} - gas := burner.GasLeft() + gasPtr := burner.GasLeft() - footprint, err := activateWasmRustImpl(wasm, pageLimit, version, debugMode, &moduleHash, &gas) - if err := burner.Burn(arbmath.SaturatingUSub(burner.GasLeft(), gas)); err != nil { - return moduleHash, footprint, err - } + footprint, err := activateWasmRustImpl(wasm, pageLimit, version, debugMode, &moduleHash, gasPtr) if err != nil { _, _, err := userFailure.toResult(err.intoSlice(), debug) return moduleHash, footprint, err diff --git a/precompiles/context.go b/precompiles/context.go index b52c3ce9a..670ffa744 100644 --- a/precompiles/context.go +++ b/precompiles/context.go @@ -53,8 +53,8 @@ func (c *Context) BurnOut() error { return vm.ErrOutOfGas } -func (c *Context) GasLeft() uint64 { - return c.gasLeft +func (c *Context) GasLeft() *uint64 { + return &c.gasLeft } func (c *Context) Restrict(err error) { From 10a8b79237eed30730e7c1d6da58e4598f366994 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 11:13:47 -0600 Subject: [PATCH 11/19] panic on divergence --- arbitrator/stylus/src/native.rs | 4 ++-- go-ethereum | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index b34d3f195..e0ac105b2 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -10,7 +10,7 @@ use arbutil::{ operator::OperatorCode, Color, }; -use eyre::{bail, eyre, Context, ErrReport, Result}; +use eyre::{bail, eyre, ErrReport, Result}; use prover::{ machine::Module as ProverModule, programs::{ @@ -383,6 +383,6 @@ pub fn activate( let compile = CompileConfig::version(version, debug); let (module, footprint) = ProverModule::activate(wasm, version, page_limit, debug, gas)?; - let asm = self::module(wasm, compile).wrap_err("failed to generate stylus module")?; + let asm = self::module(wasm, compile).expect("failed to generate stylus module"); Ok((asm, module, footprint)) } diff --git a/go-ethereum b/go-ethereum index d16318ab8..548c77470 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit d16318ab8b159730142fa5a2431cac70d172d9d5 +Subproject commit 548c7747099b4feb21369b02941482e7cd79e1c5 From 67f52b9954f8fdb69cec7b529af7848eb3cf77c9 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 12:18:45 -0600 Subject: [PATCH 12/19] make names consistent --- arbitrator/jit/src/machine.rs | 2 +- arbitrator/jit/src/user/mod.rs | 2 +- arbitrator/wasm-libraries/user-host/src/link.rs | 2 +- arbos/programs/raw.s | 2 +- arbos/programs/wasm.go | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arbitrator/jit/src/machine.rs b/arbitrator/jit/src/machine.rs index c3069ac78..44597706b 100644 --- a/arbitrator/jit/src/machine.rs +++ b/arbitrator/jit/src/machine.rs @@ -115,7 +115,7 @@ pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Sto github!("wavmio.readDelayedInboxMessage") => func!(wavmio::read_delayed_inbox_message), github!("wavmio.resolvePreImage") => func!(wavmio::resolve_preimage), - github!("arbos/programs.activateWasmRustImpl") => func!(user::activate_wasm), + github!("arbos/programs.activateProgramRustImpl") => func!(user::stylus_activate), github!("arbos/programs.callUserWasmRustImpl") => func!(user::call_user_wasm), github!("arbos/programs.readRustVecLenImpl") => func!(user::read_rust_vec_len), github!("arbos/programs.rustVecIntoSliceImpl") => func!(user::rust_vec_into_slice), diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index 425afb43b..370d9fada 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -33,7 +33,7 @@ mod evm_api; /// These values are placed on the stack as follows /// || wasm... || pageLimit | version | debug || modhash ptr || gas ptr || footprint | 6 pad || err ptr || /// -pub fn activate_wasm(env: WasmEnvMut, sp: u32) { +pub fn stylus_activate(env: WasmEnvMut, sp: u32) { let mut sp = GoStack::simple(sp, &env); let wasm = sp.read_go_slice_owned(); let page_limit = sp.read_u16(); diff --git a/arbitrator/wasm-libraries/user-host/src/link.rs b/arbitrator/wasm-libraries/user-host/src/link.rs index a1d829a45..299a02b64 100644 --- a/arbitrator/wasm-libraries/user-host/src/link.rs +++ b/arbitrator/wasm-libraries/user-host/src/link.rs @@ -51,7 +51,7 @@ struct MemoryLeaf([u8; 32]); /// || wasm... || pageLimit | version | debug || modhash ptr || gas ptr || footprint | 6 pad || err ptr || /// #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_activateWasmRustImpl( +pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_activateProgramRustImpl( sp: usize, ) { let mut sp = GoStack::new(sp); diff --git a/arbos/programs/raw.s b/arbos/programs/raw.s index c222bfbe2..8e2dbf625 100644 --- a/arbos/programs/raw.s +++ b/arbos/programs/raw.s @@ -6,7 +6,7 @@ #include "textflag.h" -TEXT ·activateWasmRustImpl(SB), NOSPLIT, $0 +TEXT ·activateProgramRustImpl(SB), NOSPLIT, $0 CallImport RET diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index 5f72c1335..b099a3f02 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -31,7 +31,7 @@ type rustConfig byte type rustModule byte type rustEvmData byte -func activateWasmRustImpl( +func activateProgramRustImpl( wasm []byte, pageLimit, version u16, debugMode u32, moduleHash *hash, gas *u64, ) (footprint u16, err *rustVec) @@ -71,7 +71,7 @@ func activateProgram( moduleHash := common.Hash{} gasPtr := burner.GasLeft() - footprint, err := activateWasmRustImpl(wasm, pageLimit, version, debugMode, &moduleHash, gasPtr) + footprint, err := activateProgramRustImpl(wasm, pageLimit, version, debugMode, &moduleHash, gasPtr) if err != nil { _, _, err := userFailure.toResult(err.intoSlice(), debug) return moduleHash, footprint, err From 238b5f32e6ee00ec9cc0e2d79152d2fc2ce4c160 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 14:10:27 -0600 Subject: [PATCH 13/19] more renaming --- arbitrator/jit/src/machine.rs | 2 +- arbitrator/jit/src/user/mod.rs | 2 +- arbitrator/wasm-libraries/user-host/src/link.rs | 2 +- arbos/programs/native.go | 2 +- arbos/programs/programs.go | 2 +- arbos/programs/raw.s | 2 +- arbos/programs/wasm.go | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/arbitrator/jit/src/machine.rs b/arbitrator/jit/src/machine.rs index 44597706b..40feb9d7e 100644 --- a/arbitrator/jit/src/machine.rs +++ b/arbitrator/jit/src/machine.rs @@ -116,7 +116,7 @@ pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Sto github!("wavmio.resolvePreImage") => func!(wavmio::resolve_preimage), github!("arbos/programs.activateProgramRustImpl") => func!(user::stylus_activate), - github!("arbos/programs.callUserWasmRustImpl") => func!(user::call_user_wasm), + github!("arbos/programs.callProgramRustImpl") => func!(user::stylus_call), github!("arbos/programs.readRustVecLenImpl") => func!(user::read_rust_vec_len), github!("arbos/programs.rustVecIntoSliceImpl") => func!(user::rust_vec_into_slice), github!("arbos/programs.rustConfigImpl") => func!(user::rust_config_impl), diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index 370d9fada..313dd9620 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -76,7 +76,7 @@ pub fn stylus_activate(env: WasmEnvMut, sp: u32) { /// These values are placed on the stack as follows /// || modHash || calldata... || params || evmApi... || evmData || gas || status | 7 pad | out ptr || /// -pub fn call_user_wasm(env: WasmEnvMut, sp: u32) -> MaybeEscape { +pub fn stylus_call(env: WasmEnvMut, sp: u32) -> MaybeEscape { let sp = &mut GoStack::simple(sp, &env); use UserOutcome::*; diff --git a/arbitrator/wasm-libraries/user-host/src/link.rs b/arbitrator/wasm-libraries/user-host/src/link.rs index 299a02b64..fb3b65444 100644 --- a/arbitrator/wasm-libraries/user-host/src/link.rs +++ b/arbitrator/wasm-libraries/user-host/src/link.rs @@ -97,7 +97,7 @@ pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_activa /// || modHash || calldata... || params || evmApi... || evmData || gas || status | 7 pad | out ptr || /// #[no_mangle] -pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_callUserWasmRustImpl( +pub unsafe extern "C" fn go__github_com_offchainlabs_nitro_arbos_programs_callProgramRustImpl( sp: usize, ) { let mut sp = GoStack::new(sp); diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 9aa541c9c..71153d2cb 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -90,7 +90,7 @@ func activateProgram( return hash, footprint, err } -func callUserWasm( +func callProgram( address common.Address, program Program, scope *vm.ScopeContext, diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 7dd96f3a1..fcbeff3d7 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -292,7 +292,7 @@ func (p Programs) CallProgram( if contract.CodeAddr != nil { address = *contract.CodeAddr } - return callUserWasm(address, program, scope, statedb, interpreter, tracingInfo, calldata, evmData, params, model) + return callProgram(address, program, scope, statedb, interpreter, tracingInfo, calldata, evmData, params, model) } func getWasm(statedb vm.StateDB, program common.Address) ([]byte, error) { diff --git a/arbos/programs/raw.s b/arbos/programs/raw.s index 8e2dbf625..c0b3dc45e 100644 --- a/arbos/programs/raw.s +++ b/arbos/programs/raw.s @@ -10,7 +10,7 @@ TEXT ·activateProgramRustImpl(SB), NOSPLIT, $0 CallImport RET -TEXT ·callUserWasmRustImpl(SB), NOSPLIT, $0 +TEXT ·callProgramRustImpl(SB), NOSPLIT, $0 CallImport RET diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index b099a3f02..c46e450e3 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -35,7 +35,7 @@ func activateProgramRustImpl( wasm []byte, pageLimit, version u16, debugMode u32, moduleHash *hash, gas *u64, ) (footprint u16, err *rustVec) -func callUserWasmRustImpl( +func callProgramRustImpl( moduleHash *hash, calldata []byte, params *rustConfig, evmApi []byte, evmData *rustEvmData, gas *u64, ) (status userStatus, out *rustVec) @@ -79,7 +79,7 @@ func activateProgram( return moduleHash, footprint, nil } -func callUserWasm( +func callProgram( address common.Address, program Program, scope *vm.ScopeContext, @@ -95,7 +95,7 @@ func callUserWasm( defer evmApi.drop() debug := arbmath.UintToBool(params.debugMode) - status, output := callUserWasmRustImpl( + status, output := callProgramRustImpl( &program.moduleHash, calldata, params.encode(), From 6d95c61af59e61f732499fbd20b3168d5de254e1 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 14:59:44 -0600 Subject: [PATCH 14/19] reorder test-only Machine::run checks --- arbitrator/stylus/src/run.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arbitrator/stylus/src/run.rs b/arbitrator/stylus/src/run.rs index f50ba161a..a9ff1836f 100644 --- a/arbitrator/stylus/src/run.rs +++ b/arbitrator/stylus/src/run.rs @@ -44,12 +44,12 @@ impl RunProgram for Machine { self.set_stack(config.max_depth); let status: u32 = call!("user", STYLUS_ENTRY_POINT, vec![args_len], |error| { - if self.ink_left() == MachineMeter::Exhausted { - return UserOutcome::OutOfInk; - } if self.stack_left() == 0 { return UserOutcome::OutOfStack; } + if self.ink_left() == MachineMeter::Exhausted { + return UserOutcome::OutOfInk; + } UserOutcome::Failure(error) }); From aa636dcb58087b8cab5251fdb4166eefa63b0a16 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 22:55:49 -0600 Subject: [PATCH 15/19] address review comments --- arbitrator/jit/src/gostack.rs | 7 +++- arbitrator/jit/src/socket.rs | 2 +- arbitrator/jit/src/user/mod.rs | 8 ++-- arbitrator/jit/src/wavmio.rs | 2 +- arbitrator/prover/src/lib.rs | 2 +- arbitrator/prover/src/machine.rs | 23 ++++++------ arbitrator/prover/src/programs/mod.rs | 3 +- arbitrator/prover/test-cases/dynamic.wat | 3 ++ arbitrator/prover/test-cases/link.wat | 45 +++++++++++----------- arbitrator/stylus/src/evm_api.rs | 27 ++++++------- arbitrator/stylus/src/lib.rs | 12 +++--- arbitrator/tools/module_roots/Cargo.lock | 5 ++- arbitrator/tools/module_roots/src/main.rs | 2 +- arbos/burn/burn.go | 2 +- arbos/programs/native.go | 31 +++++++-------- arbos/programs/native_api.go | 20 +++++----- arbos/programs/programs.go | 46 +++++++++++------------ arbos/programs/wasm.go | 3 +- contracts | 2 +- go-ethereum | 2 +- validator/server_api/json.go | 13 ++----- 21 files changed, 134 insertions(+), 126 deletions(-) diff --git a/arbitrator/jit/src/gostack.rs b/arbitrator/jit/src/gostack.rs index 63cf2547b..ad166f4a5 100644 --- a/arbitrator/jit/src/gostack.rs +++ b/arbitrator/jit/src/gostack.rs @@ -243,8 +243,11 @@ impl GoStack { data } - pub fn write_slice>(&self, ptr: T, src: &[u8]) { - let ptr: u32 = ptr.try_into().map_err(|_| "Go pointer not a u32").unwrap(); + pub fn write_slice>(&self, ptr: T, src: &[u8]) + where + T::Error: Debug, + { + let ptr: u32 = ptr.try_into().expect("Go pointer not a u32"); self.view().write(ptr.into(), src).unwrap(); } diff --git a/arbitrator/jit/src/socket.rs b/arbitrator/jit/src/socket.rs index 634af3635..f63653ad4 100644 --- a/arbitrator/jit/src/socket.rs +++ b/arbitrator/jit/src/socket.rs @@ -42,7 +42,7 @@ pub fn read_bytes(reader: &mut BufReader) -> Result, io::Err Ok(buf) } -pub fn read_box(reader: &mut BufReader) -> Result, io::Error> { +pub fn read_boxed_slice(reader: &mut BufReader) -> Result, io::Error> { Ok(Vec::into_boxed_slice(read_bytes(reader)?)) } diff --git a/arbitrator/jit/src/user/mod.rs b/arbitrator/jit/src/user/mod.rs index 313dd9620..30e6d062f 100644 --- a/arbitrator/jit/src/user/mod.rs +++ b/arbitrator/jit/src/user/mod.rs @@ -12,9 +12,11 @@ use arbutil::{ format::DebugBytes, heapify, }; -use prover::programs::{config::PricingParams, prelude::*}; +use prover::{ + machine::Module, + programs::{config::PricingParams, prelude::*}, +}; use std::mem; -use stylus::native; mod evm_api; @@ -54,7 +56,7 @@ pub fn stylus_activate(env: WasmEnvMut, sp: u32) { } let gas_left = &mut sp.read_u64_raw(gas); - let (_, module, pages) = match native::activate(&wasm, version, page_limit, debug, gas_left) { + let (module, pages) = match Module::activate(&wasm, version, page_limit, debug, gas_left) { Ok(result) => result, Err(error) => error!(error), }; diff --git a/arbitrator/jit/src/wavmio.rs b/arbitrator/jit/src/wavmio.rs index 17ba6fa81..3831d16c7 100644 --- a/arbitrator/jit/src/wavmio.rs +++ b/arbitrator/jit/src/wavmio.rs @@ -315,7 +315,7 @@ fn ready_hostio(env: &mut WasmEnv) -> MaybeEscape { let programs_count = socket::read_u32(stream)?; for _ in 0..programs_count { let module_hash = socket::read_bytes32(stream)?; - let module_asm = socket::read_box(stream)?; + let module_asm = socket::read_boxed_slice(stream)?; env.module_asms.insert(module_hash, module_asm.into()); } diff --git a/arbitrator/prover/src/lib.rs b/arbitrator/prover/src/lib.rs index dbb3ba5d0..3e5267b8b 100644 --- a/arbitrator/prover/src/lib.rs +++ b/arbitrator/prover/src/lib.rs @@ -133,7 +133,7 @@ pub fn str_to_c_string(text: &str) -> *mut libc::c_char { panic!("Failed to allocate memory for error string"); } ptr::copy_nonoverlapping(text.as_ptr(), buf as *mut u8, text.len()); - *(buf.add(text.len()) as *mut u8) = 0; + *(buf as *mut u8).add(text.len()) = 0; buf as *mut libc::c_char } } diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 82a067d90..3e6ba1c2a 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -286,30 +286,29 @@ pub struct Module { } lazy_static! { - static ref USER_IMPORTS: Result> = Module::calc_user_imports(); + static ref USER_IMPORTS: HashMap = Module::calc_user_imports(); } impl Module { const FORWARDING_PREFIX: &str = "arbitrator_forward__"; - fn calc_user_imports() -> Result> { - let forward_bytes = include_bytes!("../../../target/machines/latest/forward_stub.wasm"); - let forward_bin = binary::parse(forward_bytes, Path::new("forward")).unwrap(); + fn calc_user_imports() -> HashMap { + let mut imports = HashMap::default(); - let mut available_imports = HashMap::default(); + let forward = include_bytes!("../../../target/machines/latest/forward_stub.wasm"); + let forward = binary::parse(forward, Path::new("forward")).unwrap(); - for (name, &(export, kind)) in &forward_bin.exports { + for (name, &(export, kind)) in &forward.exports { if kind == ExportKind::Func { - let ty = match forward_bin.get_function(FunctionIndex::from_u32(export)) { + let ty = match forward.get_function(FunctionIndex::from_u32(export)) { Ok(ty) => ty, - Err(error) => bail!("failed to read export {}: {}", name, error), + Err(error) => panic!("failed to read export {name}: {error:?}"), }; let import = AvailableImport::new(ty, 1, export); - available_imports.insert(name.to_owned(), import); + imports.insert(name.to_owned(), import); } } - - Ok(available_imports) + imports } fn from_binary( @@ -573,7 +572,7 @@ impl Module { ) -> Result { Self::from_binary( bin, - USER_IMPORTS.as_ref().unwrap(), + &USER_IMPORTS, &HashMap::default(), false, debug_funcs, diff --git a/arbitrator/prover/src/programs/mod.rs b/arbitrator/prover/src/programs/mod.rs index 5b2562822..423cb0974 100644 --- a/arbitrator/prover/src/programs/mod.rs +++ b/arbitrator/prover/src/programs/mod.rs @@ -5,6 +5,7 @@ use crate::{ binary::{ExportKind, WasmBinary}, machine::Module, memory::MemoryType, + programs::config::CompileConfig, value::{FunctionType as ArbFunctionType, Value}, }; use arbutil::Color; @@ -17,8 +18,6 @@ use wasmer_types::{ }; use wasmparser::{Operator, Type as WpType}; -use self::config::CompileConfig; - #[cfg(feature = "native")] use { super::value, diff --git a/arbitrator/prover/test-cases/dynamic.wat b/arbitrator/prover/test-cases/dynamic.wat index d284e0bd3..1f9c8d029 100644 --- a/arbitrator/prover/test-cases/dynamic.wat +++ b/arbitrator/prover/test-cases/dynamic.wat @@ -6,8 +6,11 @@ (import "hostio" "program_ink_left" (func $ink_left (param i32) (result i64))) (import "hostio" "program_ink_status" (func $ink_status (param i32) (result i32))) (import "hostio" "program_call_main" (func $user_func (param i32 i32) (result i32))) + + ;; WAVM Module hash (data (i32.const 0x0) "\97\0c\df\6a\a9\bf\d4\3c\03\80\7f\8a\7e\67\9a\5c\12\05\94\4f\c6\5e\39\9e\00\df\5c\b3\7d\de\55\ad") ;; user + (func $start (local $user i32) (local $internals i32) ;; link in user.wat i32.const 0 diff --git a/arbitrator/prover/test-cases/link.wat b/arbitrator/prover/test-cases/link.wat index 72ce0783a..f3f24fb28 100644 --- a/arbitrator/prover/test-cases/link.wat +++ b/arbitrator/prover/test-cases/link.wat @@ -4,32 +4,35 @@ (module (import "hostio" "wavm_link_module" (func $link (param i32) (result i32))) (import "hostio" "wavm_unlink_module" (func $unlink (param) (result))) - (data (i32.const 0x0) - "\6d\c0\9f\17\5f\5b\e8\73\64\bc\79\62\e8\13\fd\cb\09\2a\12\24\87\4a\af\15\f2\e1\2e\93\b0\95\30\9a") - (data (i32.const 0x20) - "\f5\6b\4c\c7\19\da\61\01\e4\e4\9a\f1\04\ca\29\97\fd\07\05\d6\c2\3b\e6\55\70\c5\54\65\a0\3f\3d\ee") - (data (i32.const 0x40) - "\57\27\40\77\40\da\77\f8\1f\fd\81\cb\00\e0\02\17\40\f0\be\e4\11\89\0a\56\ba\80\e4\b9\31\74\13\a2") - (data (i32.const 0x60) - "\53\36\71\e6\bf\90\0f\50\fd\18\5f\44\d6\18\77\2f\70\17\19\2a\1a\8d\b6\92\5a\3c\14\1a\af\86\81\d4") - (data (i32.const 0x80) - "\97\0c\df\6a\a9\bf\d4\3c\03\80\7f\8a\7e\67\9a\5c\12\05\94\4f\c6\5e\39\9e\00\df\5c\b3\7d\de\55\ad") - (data (i32.const 0xa0) - "\c7\db\9f\8e\ed\13\ac\66\72\62\76\65\93\1b\9a\64\03\c3\c8\21\44\92\5c\8d\bc\1a\d6\bd\65\f8\2b\20") - (data (i32.const 0xc0) - "\83\46\03\41\b4\5f\a6\e6\a3\0d\e9\fc\79\fc\3c\d6\c9\c3\c7\ac\97\42\bc\48\54\92\e6\84\08\37\07\a6") - (data (i32.const 0xe0) - "\42\1d\62\e9\9a\51\d4\71\ce\50\6e\b4\83\72\18\ea\f8\ab\ab\b9\29\b8\bd\6d\66\ea\52\b3\3d\50\26\34") + + ;; WAVM module hashes + (data (i32.const 0x000) + "\74\22\43\ad\22\2e\e5\6d\f4\bb\3f\0b\09\76\0a\bf\51\b7\17\a4\c5\50\c9\5b\45\be\ea\ed\4c\57\4d\17") ;; block + (data (i32.const 0x020) + "\53\36\71\e6\bf\90\0f\50\fd\18\5f\44\d6\18\77\2f\70\17\19\2a\1a\8d\b6\92\5a\3c\14\1a\af\86\81\d4") ;; call + (data (i32.const 0x040) + "\57\27\40\77\40\da\77\f8\1f\fd\81\cb\00\e0\02\17\40\f0\be\e4\11\89\0a\56\ba\80\e4\b9\31\74\13\a2") ;; indirect + (data (i32.const 0x060) + "\3f\c3\a1\eb\a6\62\70\2b\3b\fa\dc\5b\29\22\11\6f\58\4a\6e\e5\70\60\6f\cf\6c\66\d8\c9\77\c5\c9\23") ;; const + (data (i32.const 0x080) + "\83\46\03\41\b4\5f\a6\e6\a3\0d\e9\fc\79\fc\3c\d6\c9\c3\c7\ac\97\42\bc\48\54\92\e6\84\08\37\07\a6") ;; div + (data (i32.const 0x0a0) + "\16\90\98\f2\7f\8d\bf\73\90\b9\eb\94\9f\b9\41\cd\c3\93\2e\30\b8\12\1b\d5\87\98\18\26\f2\62\7d\2c") ;; globals + (data (i32.const 0x0c0) + "\f5\6b\4c\c7\19\da\61\01\e4\e4\9a\f1\04\ca\29\97\fd\07\05\d6\c2\3b\e6\55\70\c5\54\65\a0\3f\3d\ee") ;; if-else + (data (i32.const 0x0e0) + "\42\1d\62\e9\9a\51\d4\71\ce\50\6e\b4\83\72\18\ea\f8\ab\ab\b9\29\b8\bd\6d\66\ea\52\b3\3d\50\26\34") ;; locals (data (i32.const 0x100) - "\74\22\43\ad\22\2e\e5\6d\f4\bb\3f\0b\09\76\0a\bf\51\b7\17\a4\c5\50\c9\5b\45\be\ea\ed\4c\57\4d\17") + "\6d\c0\9f\17\5f\5b\e8\73\64\bc\79\62\e8\13\fd\cb\09\2a\12\24\87\4a\af\15\f2\e1\2e\93\b0\95\30\9a") ;; loop (data (i32.const 0x120) - "\16\90\98\f2\7f\8d\bf\73\90\b9\eb\94\9f\b9\41\cd\c3\93\2e\30\b8\12\1b\d5\87\98\18\26\f2\62\7d\2c") + "\a7\66\cb\0e\c4\31\ea\16\fd\c6\2f\d3\11\ca\4a\78\f8\48\6a\69\0a\4c\b9\1c\fc\47\f8\b6\63\6d\80\fa") ;; math (data (i32.const 0x140) - "\3f\c3\a1\eb\a6\62\70\2b\3b\fa\dc\5b\29\22\11\6f\58\4a\6e\e5\70\60\6f\cf\6c\66\d8\c9\77\c5\c9\23") + "\ea\02\78\f7\a3\b3\e0\0e\55\f6\8f\13\87\d6\6f\04\38\b3\6b\4c\d5\33\e2\3d\0b\36\71\9f\57\f5\f0\59") ;; iops (data (i32.const 0x160) - "\a7\66\cb\0e\c4\31\ea\16\fd\c6\2f\d3\11\ca\4a\78\f8\48\6a\69\0a\4c\b9\1c\fc\47\f8\b6\63\6d\80\fa") + "\97\0c\df\6a\a9\bf\d4\3c\03\80\7f\8a\7e\67\9a\5c\12\05\94\4f\c6\5e\39\9e\00\df\5c\b3\7d\de\55\ad") ;; user (data (i32.const 0x180) - "\ea\02\78\f7\a3\b3\e0\0e\55\f6\8f\13\87\d6\6f\04\38\b3\6b\4c\d5\33\e2\3d\0b\36\71\9f\57\f5\f0\59") + "\c7\db\9f\8e\ed\13\ac\66\72\62\76\65\93\1b\9a\64\03\c3\c8\21\44\92\5c\8d\bc\1a\d6\bd\65\f8\2b\20") ;; return + (func $start (local $counter i32) ;; add modules diff --git a/arbitrator/stylus/src/evm_api.rs b/arbitrator/stylus/src/evm_api.rs index 8cda407fc..3b1b261dc 100644 --- a/arbitrator/stylus/src/evm_api.rs +++ b/arbitrator/stylus/src/evm_api.rs @@ -1,7 +1,7 @@ // Copyright 2022-2023, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::{RustSlice, RustVec}; +use crate::{RustBytes, RustSlice}; use arbutil::{ evm::{ api::{EvmApi, EvmApiStatus}, @@ -19,7 +19,7 @@ pub struct GoEvmApi { key: Bytes32, value: Bytes32, gas_cost: *mut u64, - error: *mut RustVec, + error: *mut RustBytes, ) -> EvmApiStatus, pub contract_call: unsafe extern "C" fn( id: usize, @@ -45,22 +45,23 @@ pub struct GoEvmApi { ) -> EvmApiStatus, pub create1: unsafe extern "C" fn( id: usize, - code: *mut RustVec, + code: *mut RustBytes, endowment: Bytes32, gas: *mut u64, return_data_len: *mut u32, ) -> EvmApiStatus, pub create2: unsafe extern "C" fn( id: usize, - code: *mut RustVec, + code: *mut RustBytes, endowment: Bytes32, salt: Bytes32, gas: *mut u64, return_data_len: *mut u32, ) -> EvmApiStatus, pub get_return_data: - unsafe extern "C" fn(id: usize, output: *mut RustVec, offset: u32, size: u32), - pub emit_log: unsafe extern "C" fn(id: usize, data: *mut RustVec, topics: u32) -> EvmApiStatus, + unsafe extern "C" fn(id: usize, output: *mut RustBytes, offset: u32, size: u32), + pub emit_log: + unsafe extern "C" fn(id: usize, data: *mut RustBytes, topics: u32) -> EvmApiStatus, pub account_balance: unsafe extern "C" fn(id: usize, address: Bytes20, gas_cost: *mut u64) -> Bytes32, // balance pub account_codehash: @@ -68,7 +69,7 @@ pub struct GoEvmApi { pub add_pages: unsafe extern "C" fn(id: usize, pages: u16) -> u64, // gas cost pub capture_hostio: unsafe extern "C" fn( id: usize, - name: *mut RustVec, + name: *mut RustBytes, args: *mut RustSlice, outs: *mut RustSlice, start_ink: u64, @@ -106,7 +107,7 @@ impl EvmApi for GoEvmApi { } fn set_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Result { - let mut error = RustVec::new(vec![]); + let mut error = RustBytes::new(vec![]); let mut cost = 0; let api_status = call!(self, set_bytes32, key, value, ptr!(cost), ptr!(error)); let error = into_vec!(error); // done here to always drop @@ -183,7 +184,7 @@ impl EvmApi for GoEvmApi { ) -> (Result, u32, u64) { let mut call_gas = gas; // becomes the call's cost let mut return_data_len = 0; - let mut code = RustVec::new(code); + let mut code = RustBytes::new(code); let api_status = call!( self, create1, @@ -209,7 +210,7 @@ impl EvmApi for GoEvmApi { ) -> (Result, u32, u64) { let mut call_gas = gas; // becomes the call's cost let mut return_data_len = 0; - let mut code = RustVec::new(code); + let mut code = RustBytes::new(code); let api_status = call!( self, create2, @@ -228,13 +229,13 @@ impl EvmApi for GoEvmApi { } fn get_return_data(&mut self, offset: u32, size: u32) -> Vec { - let mut data = RustVec::new(vec![]); + let mut data = RustBytes::new(vec![]); call!(self, get_return_data, ptr!(data), offset, size); into_vec!(data) } fn emit_log(&mut self, data: Vec, topics: u32) -> Result<()> { - let mut data = RustVec::new(data); + let mut data = RustBytes::new(data); let api_status = call!(self, emit_log, ptr!(data), topics); let error = into_vec!(data); // done here to always drop match api_status { @@ -263,7 +264,7 @@ impl EvmApi for GoEvmApi { call!( self, capture_hostio, - ptr!(RustVec::new(name.as_bytes().to_vec())), + ptr!(RustBytes::new(name.as_bytes().to_vec())), ptr!(RustSlice::new(args)), ptr!(RustSlice::new(outs)), start_ink, diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index fd406ee1b..40aecdebd 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -60,13 +60,13 @@ impl<'a> RustSlice<'a> { } #[repr(C)] -pub struct RustVec { +pub struct RustBytes { ptr: *mut u8, len: usize, cap: usize, } -impl RustVec { +impl RustBytes { fn new(vec: Vec) -> Self { let mut rust_vec = Self { ptr: std::ptr::null_mut(), @@ -117,7 +117,7 @@ pub unsafe extern "C" fn stylus_activate( page_limit: u16, version: u16, debug: bool, - output: *mut RustVec, + output: *mut RustBytes, asm_len: *mut usize, module_hash: *mut Bytes32, footprint: *mut u16, @@ -156,7 +156,7 @@ pub unsafe extern "C" fn stylus_call( go_api: GoEvmApi, evm_data: EvmData, debug_chain: u32, - output: *mut RustVec, + output: *mut RustBytes, gas: *mut u64, ) -> UserOutcomeKind { let module = module.slice(); @@ -191,7 +191,7 @@ pub unsafe extern "C" fn stylus_call( /// /// Must only be called once per vec. #[no_mangle] -pub unsafe extern "C" fn stylus_drop_vec(vec: RustVec) { +pub unsafe extern "C" fn stylus_drop_vec(vec: RustBytes) { if !vec.ptr.is_null() { mem::drop(vec.into_vec()) } @@ -203,7 +203,7 @@ pub unsafe extern "C" fn stylus_drop_vec(vec: RustVec) { /// /// `rust` must not be null. #[no_mangle] -pub unsafe extern "C" fn stylus_vec_set_bytes(rust: *mut RustVec, data: GoSliceData) { +pub unsafe extern "C" fn stylus_vec_set_bytes(rust: *mut RustBytes, data: GoSliceData) { let rust = &mut *rust; let mut vec = Vec::from_raw_parts(rust.ptr, rust.len, rust.cap); vec.clear(); diff --git a/arbitrator/tools/module_roots/Cargo.lock b/arbitrator/tools/module_roots/Cargo.lock index 4bfaa7db1..b4ccea9ca 100644 --- a/arbitrator/tools/module_roots/Cargo.lock +++ b/arbitrator/tools/module_roots/Cargo.lock @@ -44,6 +44,7 @@ dependencies = [ "digest 0.9.0", "eyre", "hex", + "num-traits", "serde", "sha3 0.10.6", "siphasher", @@ -894,9 +895,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] diff --git a/arbitrator/tools/module_roots/src/main.rs b/arbitrator/tools/module_roots/src/main.rs index 2cc52d614..2a42a0131 100644 --- a/arbitrator/tools/module_roots/src/main.rs +++ b/arbitrator/tools/module_roots/src/main.rs @@ -35,7 +35,7 @@ fn main() -> Result<()> { for module in &opts.stylus_modules { let error = || format!("failed to read module at {}", module.to_string_lossy()); let wasm = file_bytes(module).wrap_err_with(error)?; - let hash = mach.add_program(&wasm, 1, true, None).wrap_err_with(error)?; + let hash = mach.add_program(&wasm, 1, true).wrap_err_with(error)?; let name = module.file_stem().unwrap().to_string_lossy(); stylus.push((name.to_owned(), hash)); println!("{} {}", name, hash); diff --git a/arbos/burn/burn.go b/arbos/burn/burn.go index 665ff4e58..0463588af 100644 --- a/arbos/burn/burn.go +++ b/arbos/burn/burn.go @@ -13,7 +13,7 @@ import ( type Burner interface { Burn(amount uint64) error Burned() uint64 - GasLeft() *uint64 + GasLeft() *uint64 // SystemBurner's panic BurnOut() error Restrict(err error) HandleError(err error) error diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 71153d2cb..cd84caf48 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -41,7 +41,7 @@ type usize = C.size_t type cbool = C._Bool type bytes20 = C.Bytes20 type bytes32 = C.Bytes32 -type rustVec = C.RustVec +type rustBytes = C.RustBytes type rustSlice = C.RustSlice func activateProgram( @@ -53,7 +53,7 @@ func activateProgram( debug bool, burner burn.Burner, ) (common.Hash, uint16, error) { - output := &rustVec{} + output := &rustBytes{} asmLen := usize(0) moduleHash := &bytes32{} footprint := uint16(math.MaxUint16) @@ -93,6 +93,7 @@ func activateProgram( func callProgram( address common.Address, program Program, + moduleHash common.Hash, scope *vm.ScopeContext, db vm.StateDB, interpreter *vm.EVMInterpreter, @@ -103,14 +104,14 @@ func callProgram( memoryModel *MemoryModel, ) ([]byte, error) { if db, ok := db.(*state.StateDB); ok { - db.RecordProgram(program.moduleHash) + db.RecordProgram(moduleHash) } - asm := db.GetActivatedAsm(program.moduleHash) + asm := db.GetActivatedAsm(moduleHash) evmApi, id := newApi(interpreter, tracingInfo, scope, memoryModel) defer dropApi(id) - output := &rustVec{} + output := &rustBytes{} status := userStatus(C.stylus_call( goSlice(asm), goSlice(calldata), @@ -144,7 +145,7 @@ func getBytes32Impl(api usize, key bytes32, cost *u64) bytes32 { } //export setBytes32Impl -func setBytes32Impl(api usize, key, value bytes32, cost *u64, errVec *rustVec) apiStatus { +func setBytes32Impl(api usize, key, value bytes32, cost *u64, errVec *rustBytes) apiStatus { closures := getApi(api) gas, err := closures.setBytes32(key.toHash(), value.toHash()) @@ -193,7 +194,7 @@ func staticCallImpl(api usize, contract bytes20, data *rustSlice, evmGas *u64, l } //export create1Impl -func create1Impl(api usize, code *rustVec, endowment bytes32, evmGas *u64, len *u32) apiStatus { +func create1Impl(api usize, code *rustBytes, endowment bytes32, evmGas *u64, len *u32) apiStatus { closures := getApi(api) addr, ret_len, cost, err := closures.create1(code.read(), endowment.toBig(), uint64(*evmGas)) *evmGas = u64(cost) // evmGas becomes the call's cost @@ -207,7 +208,7 @@ func create1Impl(api usize, code *rustVec, endowment bytes32, evmGas *u64, len * } //export create2Impl -func create2Impl(api usize, code *rustVec, endowment, salt bytes32, evmGas *u64, len *u32) apiStatus { +func create2Impl(api usize, code *rustBytes, endowment, salt bytes32, evmGas *u64, len *u32) apiStatus { closures := getApi(api) addr, ret_len, cost, err := closures.create2(code.read(), endowment.toBig(), salt.toBig(), uint64(*evmGas)) *evmGas = u64(cost) // evmGas becomes the call's cost @@ -221,14 +222,14 @@ func create2Impl(api usize, code *rustVec, endowment, salt bytes32, evmGas *u64, } //export getReturnDataImpl -func getReturnDataImpl(api usize, output *rustVec, offset u32, size u32) { +func getReturnDataImpl(api usize, output *rustBytes, offset u32, size u32) { closures := getApi(api) returnData := closures.getReturnData(uint32(offset), uint32(size)) output.setBytes(returnData) } //export emitLogImpl -func emitLogImpl(api usize, data *rustVec, topics u32) apiStatus { +func emitLogImpl(api usize, data *rustBytes, topics u32) apiStatus { closures := getApi(api) err := closures.emitLog(data.read(), uint32(topics)) if err != nil { @@ -307,25 +308,25 @@ func (slice *rustSlice) read() []byte { return arbutil.PointerToSlice((*byte)(slice.ptr), int(slice.len)) } -func (vec *rustVec) read() []byte { +func (vec *rustBytes) read() []byte { return arbutil.PointerToSlice((*byte)(vec.ptr), int(vec.len)) } -func (vec *rustVec) intoBytes() []byte { +func (vec *rustBytes) intoBytes() []byte { slice := vec.read() vec.drop() return slice } -func (vec *rustVec) drop() { +func (vec *rustBytes) drop() { C.stylus_drop_vec(*vec) } -func (vec *rustVec) setString(data string) { +func (vec *rustBytes) setString(data string) { vec.setBytes([]byte(data)) } -func (vec *rustVec) setBytes(data []byte) { +func (vec *rustBytes) setBytes(data []byte) { C.stylus_vec_set_bytes(vec, goSlice(data)) } diff --git a/arbos/programs/native_api.go b/arbos/programs/native_api.go index 6956d493b..121ff79ea 100644 --- a/arbos/programs/native_api.go +++ b/arbos/programs/native_api.go @@ -21,8 +21,8 @@ Bytes32 getBytes32Wrap(usize api, Bytes32 key, u64 * cost) { return getBytes32Impl(api, key, cost); } -EvmApiStatus setBytes32Impl(usize api, Bytes32 key, Bytes32 value, u64 * cost, RustVec * error); -EvmApiStatus setBytes32Wrap(usize api, Bytes32 key, Bytes32 value, u64 * cost, RustVec * error) { +EvmApiStatus setBytes32Impl(usize api, Bytes32 key, Bytes32 value, u64 * cost, RustBytes * error); +EvmApiStatus setBytes32Wrap(usize api, Bytes32 key, Bytes32 value, u64 * cost, RustBytes * error) { return setBytes32Impl(api, key, value, cost, error); } @@ -41,23 +41,23 @@ EvmApiStatus staticCallWrap(usize api, Bytes20 contract, RustSlice * calldata, u return staticCallImpl(api, contract, calldata, gas, len); } -EvmApiStatus create1Impl(usize api, RustVec * code, Bytes32 endowment, u64 * gas, u32 * len); -EvmApiStatus create1Wrap(usize api, RustVec * code, Bytes32 endowment, u64 * gas, u32 * len) { +EvmApiStatus create1Impl(usize api, RustBytes * code, Bytes32 endowment, u64 * gas, u32 * len); +EvmApiStatus create1Wrap(usize api, RustBytes * code, Bytes32 endowment, u64 * gas, u32 * len) { return create1Impl(api, code, endowment, gas, len); } -EvmApiStatus create2Impl(usize api, RustVec * code, Bytes32 endowment, Bytes32 salt, u64 * gas, u32 * len); -EvmApiStatus create2Wrap(usize api, RustVec * code, Bytes32 endowment, Bytes32 salt, u64 * gas, u32 * len) { +EvmApiStatus create2Impl(usize api, RustBytes * code, Bytes32 endowment, Bytes32 salt, u64 * gas, u32 * len); +EvmApiStatus create2Wrap(usize api, RustBytes * code, Bytes32 endowment, Bytes32 salt, u64 * gas, u32 * len) { return create2Impl(api, code, endowment, salt, gas, len); } -void getReturnDataImpl(usize api, RustVec * data, u32 offset, u32 size); -void getReturnDataWrap(usize api, RustVec * data, u32 offset, u32 size) { +void getReturnDataImpl(usize api, RustBytes * data, u32 offset, u32 size); +void getReturnDataWrap(usize api, RustBytes * data, u32 offset, u32 size) { return getReturnDataImpl(api, data, offset, size); } -EvmApiStatus emitLogImpl(usize api, RustVec * data, usize topics); -EvmApiStatus emitLogWrap(usize api, RustVec * data, usize topics) { +EvmApiStatus emitLogImpl(usize api, RustBytes * data, usize topics); +EvmApiStatus emitLogWrap(usize api, RustBytes * data, usize topics) { return emitLogImpl(api, data, topics); } diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index fcbeff3d7..42cea9dff 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -30,14 +30,13 @@ type Programs struct { pageRamp storage.StorageBackedUint64 pageLimit storage.StorageBackedUint16 callScalar storage.StorageBackedUint16 - version storage.StorageBackedUint16 + version storage.StorageBackedUint16 // Must only be changed during ArbOS upgrades } type Program struct { - wasmSize uint16 // Unit is half of a kb - footprint uint16 - version uint16 - moduleHash common.Hash + wasmSize uint16 // Unit is half of a kb + footprint uint16 + version uint16 } type uint24 = arbmath.Uint24 @@ -202,15 +201,17 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode if err != nil { return 0, true, err } + if err := p.moduleHashes.Set(codeHash, moduleHash); err != nil { + return version, true, err + } // wasmSize is stored as half kb units, rounding up wasmSize := arbmath.SaturatingUCast[uint16]((len(wasm) + 511) / 512) programData := Program{ - wasmSize: wasmSize, - footprint: footprint, - version: version, - moduleHash: moduleHash, + wasmSize: wasmSize, + footprint: footprint, + version: version, } return version, false, p.setProgram(codeHash, programData) } @@ -241,6 +242,11 @@ func (p Programs) CallProgram( return nil, ProgramOutOfDateError(program.version) } + moduleHash, err := p.moduleHashes.Get(contract.CodeHash) + if err != nil { + return nil, err + } + debugMode := interpreter.Evm().ChainConfig().DebugMode() params, err := p.goParams(program.version, debugMode) if err != nil { @@ -292,7 +298,10 @@ func (p Programs) CallProgram( if contract.CodeAddr != nil { address = *contract.CodeAddr } - return callProgram(address, program, scope, statedb, interpreter, tracingInfo, calldata, evmData, params, model) + return callProgram( + address, program, moduleHash, scope, statedb, interpreter, + tracingInfo, calldata, evmData, params, model, + ) } func getWasm(statedb vm.StateDB, program common.Address) ([]byte, error) { @@ -316,15 +325,10 @@ func (p Programs) deserializeProgram(codeHash common.Hash) (Program, error) { if err != nil { return Program{}, err } - compiledHash, err := p.moduleHashes.Get(codeHash) - if err != nil { - return Program{}, err - } return Program{ - wasmSize: arbmath.BytesToUint16(data[26:28]), - footprint: arbmath.BytesToUint16(data[28:30]), - version: arbmath.BytesToUint16(data[30:]), - moduleHash: compiledHash, + wasmSize: arbmath.BytesToUint16(data[26:28]), + footprint: arbmath.BytesToUint16(data[28:30]), + version: arbmath.BytesToUint16(data[30:]), }, nil } @@ -333,11 +337,7 @@ func (p Programs) setProgram(codehash common.Hash, program Program) error { copy(data[26:], arbmath.Uint16ToBytes(program.wasmSize)) copy(data[28:], arbmath.Uint16ToBytes(program.footprint)) copy(data[30:], arbmath.Uint16ToBytes(program.version)) - err := p.programs.Set(codehash, data) - if err != nil { - return err - } - return p.moduleHashes.Set(codehash, program.moduleHash) + return p.programs.Set(codehash, data) } func (p Programs) CodehashVersion(codeHash common.Hash) (uint16, error) { diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index c46e450e3..44e9bb1c0 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -82,6 +82,7 @@ func activateProgram( func callProgram( address common.Address, program Program, + moduleHash common.Hash, scope *vm.ScopeContext, db vm.StateDB, interpreter *vm.EVMInterpreter, @@ -96,7 +97,7 @@ func callProgram( debug := arbmath.UintToBool(params.debugMode) status, output := callProgramRustImpl( - &program.moduleHash, + &moduleHash, calldata, params.encode(), evmApi.funcs, diff --git a/contracts b/contracts index 73a9a78b1..78ecfd5a8 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 73a9a78b194bd99eadc0f41d7b899f45ff53b786 +Subproject commit 78ecfd5a8cad6858cd18f75500284756bf854d16 diff --git a/go-ethereum b/go-ethereum index 548c77470..4ae0e6497 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 548c7747099b4feb21369b02941482e7cd79e1c5 +Subproject commit 4ae0e649726e0079d142d1050331c068edd9acfd diff --git a/validator/server_api/json.go b/validator/server_api/json.go index 07f39b345..d81fee176 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -29,7 +29,7 @@ type ValidationInputJson struct { DelayedMsgNr uint64 PreimagesB64 jsonapi.PreimagesMapJson BatchInfo []BatchInfoJson - UserWasms map[string]UserWasmJson + UserWasms map[common.Hash]UserWasmJson DelayedMsgB64 string StartState validator.GoGlobalState DebugChain bool @@ -43,7 +43,7 @@ func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJso DelayedMsgB64: base64.StdEncoding.EncodeToString(entry.DelayedMsg), StartState: entry.StartState, PreimagesB64: jsonapi.NewPreimagesMapJson(entry.Preimages), - UserWasms: make(map[string]UserWasmJson), + UserWasms: make(map[common.Hash]UserWasmJson), DebugChain: entry.DebugChain, } for _, binfo := range entry.BatchInfo { @@ -51,12 +51,11 @@ func ValidationInputToJson(entry *validator.ValidationInput) *ValidationInputJso res.BatchInfo = append(res.BatchInfo, BatchInfoJson{binfo.Number, encData}) } for moduleHash, info := range entry.UserWasms { - encModuleHash := base64.StdEncoding.EncodeToString(moduleHash[:]) encWasm := UserWasmJson{ Asm: base64.StdEncoding.EncodeToString(info.Asm), Module: base64.StdEncoding.EncodeToString(info.Module), } - res.UserWasms[encModuleHash] = encWasm + res.UserWasms[moduleHash] = encWasm } return res } @@ -88,10 +87,6 @@ func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationI valInput.BatchInfo = append(valInput.BatchInfo, decInfo) } for moduleHash, info := range entry.UserWasms { - decModuleHash, err := base64.StdEncoding.DecodeString(moduleHash) - if err != nil { - return nil, err - } asm, err := base64.StdEncoding.DecodeString(info.Asm) if err != nil { return nil, err @@ -104,7 +99,7 @@ func ValidationInputFromJson(entry *ValidationInputJson) (*validator.ValidationI Asm: asm, Module: module, } - valInput.UserWasms[common.Hash(decModuleHash)] = decInfo + valInput.UserWasms[moduleHash] = decInfo } return valInput, nil } From 8a69f1f232934c1693ddf112628addf3a140eaaa Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 23:11:18 -0600 Subject: [PATCH 16/19] add activation event --- arbos/programs/programs.go | 20 ++++++++++--------- contracts | 2 +- precompiles/ArbWasm.go | 7 +++++-- system_tests/program_test.go | 37 ++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 42cea9dff..8219a03e6 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -168,41 +168,43 @@ func (p Programs) SetCallScalar(scalar uint16) error { return p.callScalar.Set(scalar) } -func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode bool) (uint16, bool, error) { +func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode bool) ( + uint16, common.Hash, common.Hash, bool, error, +) { statedb := evm.StateDB codeHash := statedb.GetCodeHash(address) burner := p.programs.Burner() version, err := p.StylusVersion() if err != nil { - return 0, false, err + return 0, codeHash, common.Hash{}, false, err } latest, err := p.CodehashVersion(codeHash) if err != nil { - return 0, false, err + return 0, codeHash, common.Hash{}, false, err } // Already compiled and found in the machine versions mapping. if latest >= version { - return 0, false, ProgramUpToDateError() + return 0, codeHash, common.Hash{}, false, ProgramUpToDateError() } wasm, err := getWasm(statedb, address) if err != nil { - return 0, false, err + return 0, codeHash, common.Hash{}, false, err } // require the program's footprint not exceed the remaining memory budget pageLimit, err := p.PageLimit() if err != nil { - return 0, false, err + return 0, codeHash, common.Hash{}, false, err } pageLimit = arbmath.SaturatingUSub(pageLimit, statedb.GetStylusPagesOpen()) moduleHash, footprint, err := activateProgram(statedb, address, wasm, pageLimit, version, debugMode, burner) if err != nil { - return 0, true, err + return 0, codeHash, common.Hash{}, true, err } if err := p.moduleHashes.Set(codeHash, moduleHash); err != nil { - return version, true, err + return 0, codeHash, common.Hash{}, true, err } // wasmSize is stored as half kb units, rounding up @@ -213,7 +215,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, debugMode footprint: footprint, version: version, } - return version, false, p.setProgram(codeHash, programData) + return version, codeHash, moduleHash, false, p.setProgram(codeHash, programData) } func (p Programs) CallProgram( diff --git a/contracts b/contracts index 78ecfd5a8..70682f242 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 78ecfd5a8cad6858cd18f75500284756bf854d16 +Subproject commit 70682f242380296c18359a2e4e2b994e5e099cac diff --git a/precompiles/ArbWasm.go b/precompiles/ArbWasm.go index be4ad44fc..ff211bedf 100644 --- a/precompiles/ArbWasm.go +++ b/precompiles/ArbWasm.go @@ -6,6 +6,8 @@ package precompiles type ArbWasm struct { Address addr // 0x71 + ProgramActivated func(ctx, mech, hash, hash, addr, uint16) error + ProgramActivatedGasCost func(hash, hash, addr, uint16) (uint64, error) ProgramNotActivatedError func() error ProgramOutOfDateError func(version uint16) error ProgramUpToDateError func() error @@ -13,17 +15,18 @@ type ArbWasm struct { // Compile a wasm program with the latest instrumentation func (con ArbWasm) ActivateProgram(c ctx, evm mech, program addr) (uint16, error) { + debug := evm.ChainConfig().DebugMode() // charge 3 million up front to begin activation if err := c.Burn(3000000); err != nil { return 0, err } - version, takeAllGas, err := c.State.Programs().ActivateProgram(evm, program, evm.ChainConfig().DebugMode()) + version, codeHash, moduleHash, takeAllGas, err := c.State.Programs().ActivateProgram(evm, program, debug) if takeAllGas { _ = c.BurnOut() return version, err } - return version, err + return version, con.ProgramActivated(c, evm, codeHash, moduleHash, program, version) } // Gets the latest stylus version diff --git a/system_tests/program_test.go b/system_tests/program_test.go index d1d216538..3c89892bf 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -993,6 +993,43 @@ func testSdkStorage(t *testing.T, jit bool) { check() } +func TestProgramAcivationLogs(t *testing.T) { + t.Parallel() + ctx, _, _, l2client, auth, cleanup := setupProgramTest(t, true) + defer cleanup() + + wasm, _ := readWasmFile(t, watFile("memory")) + arbWasm, err := precompilesgen.NewArbWasm(types.ArbWasmAddress, l2client) + Require(t, err) + + nolimitAuth := auth + nolimitAuth.GasLimit = 32000000 + + programAddress := deployContract(t, ctx, nolimitAuth, l2client, wasm) + + tx, err := arbWasm.ActivateProgram(&auth, programAddress) + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + + if len(receipt.Logs) != 1 { + Fatal(t, "expected 1 log while activating, got ", len(receipt.Logs)) + } + log, err := arbWasm.ParseProgramActivated(*receipt.Logs[0]) + if err != nil { + Fatal(t, "parsing activated log: ", err) + } + if log.Version == 0 { + Fatal(t, "activated program with version 0") + } + if log.Program != programAddress { + Fatal(t, "unexpected program in activation log: ", log.Program) + } + if crypto.Keccak256Hash(wasm) != log.Codehash { + Fatal(t, "unexpected codehash in activation log: ", log.Codehash) + } +} + func setupProgramTest(t *testing.T, jit bool) ( context.Context, *arbnode.Node, *BlockchainTestInfo, *ethclient.Client, bind.TransactOpts, func(), ) { From 9dde38e5da3601fc721500f539be981da60f7908 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 23:33:00 -0600 Subject: [PATCH 17/19] move out err --- precompiles/ArbWasm.go | 2 ++ system_tests/program_test.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/precompiles/ArbWasm.go b/precompiles/ArbWasm.go index ff211bedf..902985543 100644 --- a/precompiles/ArbWasm.go +++ b/precompiles/ArbWasm.go @@ -24,6 +24,8 @@ func (con ArbWasm) ActivateProgram(c ctx, evm mech, program addr) (uint16, error version, codeHash, moduleHash, takeAllGas, err := c.State.Programs().ActivateProgram(evm, program, debug) if takeAllGas { _ = c.BurnOut() + } + if err != nil { return version, err } return version, con.ProgramActivated(c, evm, codeHash, moduleHash, program, version) diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 3c89892bf..6339f0293 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -59,9 +59,9 @@ func keccakTest(t *testing.T, jit bool) { Require(t, err) colors.PrintBlue("program deployed to ", programAddress.Hex()) - timed(t, "compile same code", func() { + timed(t, "activate same code", func() { if _, err := arbWasm.ActivateProgram(&auth, otherAddressSameCode); err == nil || !strings.Contains(err.Error(), "ProgramUpToDate") { - Fatal(t, "compile should have failed with ProgramUpToDate") + Fatal(t, "activate should have failed with ProgramUpToDate", err) } }) From a44d8a221ebe60324544a21f4750fab77fcfd463 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 23:35:19 -0600 Subject: [PATCH 18/19] simplify --- arbos/programs/programs.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 8219a03e6..3adef75eb 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -324,14 +324,11 @@ func (p Programs) getProgram(contract *vm.Contract) (Program, error) { func (p Programs) deserializeProgram(codeHash common.Hash) (Program, error) { data, err := p.programs.Get(codeHash) - if err != nil { - return Program{}, err - } return Program{ wasmSize: arbmath.BytesToUint16(data[26:28]), footprint: arbmath.BytesToUint16(data[28:30]), version: arbmath.BytesToUint16(data[30:]), - }, nil + }, err } func (p Programs) setProgram(codehash common.Hash, program Program) error { From 40daaacecee9640c2821fe893baa58483ecdf1ef Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Sun, 15 Oct 2023 23:51:49 -0600 Subject: [PATCH 19/19] address review comments + simplify --- arbitrator/prover/src/machine.rs | 14 ++++++-------- arbos/burn/burn.go | 2 +- arbos/programs/native.go | 1 - arbos/programs/programs.go | 2 +- arbos/programs/wasm.go | 1 - 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 3e6ba1c2a..396744074 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -286,13 +286,7 @@ pub struct Module { } lazy_static! { - static ref USER_IMPORTS: HashMap = Module::calc_user_imports(); -} - -impl Module { - const FORWARDING_PREFIX: &str = "arbitrator_forward__"; - - fn calc_user_imports() -> HashMap { + static ref USER_IMPORTS: HashMap = { let mut imports = HashMap::default(); let forward = include_bytes!("../../../target/machines/latest/forward_stub.wasm"); @@ -309,7 +303,11 @@ impl Module { } } imports - } + }; +} + +impl Module { + const FORWARDING_PREFIX: &str = "arbitrator_forward__"; fn from_binary( bin: &WasmBinary, diff --git a/arbos/burn/burn.go b/arbos/burn/burn.go index 0463588af..7d30ad12e 100644 --- a/arbos/burn/burn.go +++ b/arbos/burn/burn.go @@ -13,7 +13,7 @@ import ( type Burner interface { Burn(amount uint64) error Burned() uint64 - GasLeft() *uint64 // SystemBurner's panic + GasLeft() *uint64 // `SystemBurner`s panic (no notion of GasLeft) BurnOut() error Restrict(err error) HandleError(err error) error diff --git a/arbos/programs/native.go b/arbos/programs/native.go index cd84caf48..f99ae8f4b 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -92,7 +92,6 @@ func activateProgram( func callProgram( address common.Address, - program Program, moduleHash common.Hash, scope *vm.ScopeContext, db vm.StateDB, diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 3adef75eb..2bddb929f 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -301,7 +301,7 @@ func (p Programs) CallProgram( address = *contract.CodeAddr } return callProgram( - address, program, moduleHash, scope, statedb, interpreter, + address, moduleHash, scope, statedb, interpreter, tracingInfo, calldata, evmData, params, model, ) } diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index 44e9bb1c0..cba3f8e1b 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -81,7 +81,6 @@ func activateProgram( func callProgram( address common.Address, - program Program, moduleHash common.Hash, scope *vm.ScopeContext, db vm.StateDB,