Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Init Pricer & Mainnet Constants #234

Merged
merged 12 commits into from
May 2, 2024
9 changes: 6 additions & 3 deletions arbitrator/arbutil/src/evm/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ pub trait EvmApi<D: DataReader>: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
value: Bytes32,
) -> (u32, u64, UserOutcomeKind);

Expand All @@ -113,7 +114,8 @@ pub trait EvmApi<D: DataReader>: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
) -> (u32, u64, UserOutcomeKind);

/// Static-calls the contract at the given address.
Expand All @@ -123,7 +125,8 @@ pub trait EvmApi<D: DataReader>: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
) -> (u32, u64, UserOutcomeKind);

/// Deploys a new contract using the init code provided.
Expand Down
32 changes: 23 additions & 9 deletions arbitrator/arbutil/src/evm/req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ impl<D: DataReader, H: RequestHandler<D>> EvmApiRequestor<D, H> {
call_type: EvmApiMethod,
contract: Bytes20,
input: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
value: Bytes32,
) -> (u32, u64, UserOutcomeKind) {
let mut request = Vec::with_capacity(20 + 32 + 8 + input.len());
let mut request = Vec::with_capacity(20 + 32 + 8 + 8 + input.len());
request.extend(contract);
request.extend(value);
request.extend(gas.to_be_bytes());
request.extend(gas_left.to_be_bytes());
request.extend(gas_req.to_be_bytes());
request.extend(input);

let (res, data, cost) = self.request(call_type, &request);
Expand Down Expand Up @@ -164,23 +166,33 @@ impl<D: DataReader, H: RequestHandler<D>> EvmApi<D> for EvmApiRequestor<D, H> {
&mut self,
contract: Bytes20,
input: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
value: Bytes32,
) -> (u32, u64, UserOutcomeKind) {
self.call_request(EvmApiMethod::ContractCall, contract, input, gas, value)
self.call_request(
EvmApiMethod::ContractCall,
contract,
input,
gas_left,
gas_req,
value,
)
}

fn delegate_call(
&mut self,
contract: Bytes20,
input: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
) -> (u32, u64, UserOutcomeKind) {
self.call_request(
EvmApiMethod::DelegateCall,
contract,
input,
gas,
gas_left,
gas_req,
Bytes32::default(),
)
}
Expand All @@ -189,13 +201,15 @@ impl<D: DataReader, H: RequestHandler<D>> EvmApi<D> for EvmApiRequestor<D, H> {
&mut self,
contract: Bytes20,
input: &[u8],
gas: u64,
gas_left: u64,
gas_req: u64,
) -> (u32, u64, UserOutcomeKind) {
self.call_request(
EvmApiMethod::StaticCall,
contract,
input,
gas,
gas_left,
gas_req,
Bytes32::default(),
)
}
Expand Down
16 changes: 9 additions & 7 deletions arbitrator/jit/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,30 @@ pub fn activate(
wasm_size: u32,
pages_ptr: GuestPtr,
asm_estimate_ptr: GuestPtr,
init_gas_ptr: GuestPtr,
cached_init_gas_ptr: GuestPtr,
init_cost_ptr: GuestPtr,
cached_init_cost_ptr: GuestPtr,
version: u16,
debug: u32,
codehash: GuestPtr,
module_hash_ptr: GuestPtr,
gas_ptr: GuestPtr,
err_buf: GuestPtr,
err_buf_len: u32,
) -> Result<u32, Escape> {
let (mut mem, _) = env.jit_env();
let wasm = mem.read_slice(wasm_ptr, wasm_size as usize);
let codehash = &mem.read_bytes32(codehash);
let debug = debug != 0;

let page_limit = mem.read_u16(pages_ptr);
let gas_left = &mut mem.read_u64(gas_ptr);
match Module::activate(&wasm, version, page_limit, debug, gas_left) {
match Module::activate(&wasm, codehash, version, page_limit, debug, gas_left) {
Ok((module, data)) => {
mem.write_u64(gas_ptr, *gas_left);
mem.write_u16(pages_ptr, data.footprint);
mem.write_u32(asm_estimate_ptr, data.asm_estimate);
mem.write_u16(init_gas_ptr, data.init_gas);
mem.write_u16(cached_init_gas_ptr, data.cached_init_gas);
mem.write_u16(init_cost_ptr, data.init_cost);
mem.write_u16(cached_init_cost_ptr, data.cached_init_cost);
mem.write_bytes32(module_hash_ptr, module.hash());
Ok(0)
}
Expand All @@ -55,8 +57,8 @@ pub fn activate(
mem.write_u64(gas_ptr, 0);
mem.write_u16(pages_ptr, 0);
mem.write_u32(asm_estimate_ptr, 0);
mem.write_u16(init_gas_ptr, 0);
mem.write_u16(cached_init_gas_ptr, 0);
mem.write_u16(init_cost_ptr, 0);
mem.write_u16(cached_init_cost_ptr, 0);
mem.write_bytes32(module_hash_ptr, Bytes32::default());
Ok(err_bytes.len() as u32)
}
Expand Down
113 changes: 79 additions & 34 deletions arbitrator/prover/src/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
},
value::{ArbValueType, FunctionType, IntegerValType, Value},
};
use arbutil::{math::SaturatingSum, Color, DebugColor};
use arbutil::{math::SaturatingSum, Bytes32, Color, DebugColor};
use eyre::{bail, ensure, eyre, Result, WrapErr};
use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet};
use nom::{
Expand All @@ -20,7 +20,7 @@ use nom::{
};
use serde::{Deserialize, Serialize};
use std::{convert::TryInto, fmt::Debug, hash::Hash, mem, path::Path, str::FromStr};
use wasmer_types::{entity::EntityRef, FunctionIndex, LocalFunctionIndex};
use wasmer_types::{entity::EntityRef, ExportIndex, FunctionIndex, LocalFunctionIndex};
use wasmparser::{
Data, Element, ExternalKind, MemoryType, Name, NameSectionReader, Naming, Operator, Parser,
Payload, TableType, TypeRef, ValType, Validator, WasmFeatures,
Expand Down Expand Up @@ -232,17 +232,27 @@ pub enum ExportKind {
Tag,
}

impl TryFrom<ExternalKind> for ExportKind {
type Error = eyre::Error;

fn try_from(kind: ExternalKind) -> Result<Self> {
impl From<ExternalKind> for ExportKind {
fn from(kind: ExternalKind) -> Self {
use ExternalKind as E;
match kind {
E::Func => Ok(Self::Func),
E::Table => Ok(Self::Table),
E::Memory => Ok(Self::Memory),
E::Global => Ok(Self::Global),
E::Tag => Ok(Self::Tag),
E::Func => Self::Func,
E::Table => Self::Table,
E::Memory => Self::Memory,
E::Global => Self::Global,
E::Tag => Self::Tag,
}
}
}

impl From<ExportIndex> for ExportKind {
fn from(value: ExportIndex) -> Self {
use ExportIndex as E;
match value {
E::Function(_) => Self::Func,
E::Table(_) => Self::Table,
E::Memory(_) => Self::Memory,
E::Global(_) => Self::Global,
}
}
}
Expand Down Expand Up @@ -271,7 +281,7 @@ pub type ExportMap = HashMap<String, (u32, ExportKind)>;
pub struct WasmBinary<'a> {
pub types: Vec<FunctionType>,
pub imports: Vec<FuncImport<'a>>,
/// Maps *local* function indices to global type signatures
/// Maps *local* function indices to global type signatures.
pub functions: Vec<u32>,
pub tables: Vec<TableType>,
pub memories: Vec<MemoryType>,
Expand All @@ -282,6 +292,10 @@ pub struct WasmBinary<'a> {
pub codes: Vec<Code<'a>>,
pub datas: Vec<Data<'a>>,
pub names: NameCustomSection,
/// The source wasm, if known.
pub wasm: Option<&'a [u8]>,
/// Consensus data used to make module hashes unique.
pub extra_data: Vec<u8>,
}

pub fn parse<'a>(input: &'a [u8], path: &'_ Path) -> Result<WasmBinary<'a>> {
Expand Down Expand Up @@ -312,7 +326,10 @@ pub fn parse<'a>(input: &'a [u8], path: &'_ Path) -> Result<WasmBinary<'a>> {
.validate_all(input)
.wrap_err_with(|| eyre!("failed to validate {}", path.to_string_lossy().red()))?;

let mut binary = WasmBinary::default();
let mut binary = WasmBinary {
wasm: Some(input),
..Default::default()
};
let sections: Vec<_> = Parser::new(0).parse_all(input).collect::<Result<_, _>>()?;

for section in sections {
Expand Down Expand Up @@ -390,14 +407,7 @@ pub fn parse<'a>(input: &'a [u8], path: &'_ Path) -> Result<WasmBinary<'a>> {
let name = || name.clone();
binary.names.functions.entry(index).or_insert_with(name);
}

// TODO: we'll only support the types also in wasmparser 0.95+
if matches!(kind, E::Func | E::Table | E::Memory | E::Global | E::Tag) {
let kind = kind.try_into()?;
binary.exports.insert(name, (export.index, kind));
} else {
bail!("unsupported export kind {:?}", export)
}
binary.exports.insert(name, (export.index, kind.into()));
}
}
FunctionSection(functions) => process!(binary.functions, functions),
Expand Down Expand Up @@ -508,18 +518,22 @@ impl<'a> Debug for WasmBinary<'a> {

impl<'a> WasmBinary<'a> {
/// Instruments a user wasm, producing a version bounded via configurable instrumentation.
pub fn instrument(&mut self, compile: &CompileConfig) -> Result<StylusData> {
pub fn instrument(
&mut self,
compile: &CompileConfig,
codehash: &Bytes32,
) -> Result<StylusData> {
let start = StartMover::new(compile.debug.debug_info);
let meter = Meter::new(&compile.pricing);
let dygas = DynamicMeter::new(&compile.pricing);
let depth = DepthChecker::new(compile.bounds);
let bound = HeapBound::new(compile.bounds);
let start = StartMover::default();

start.update_module(self)?;
meter.update_module(self)?;
dygas.update_module(self)?;
depth.update_module(self)?;
bound.update_module(self)?;
start.update_module(self)?;

let count = compile.debug.count_ops.then(Counter::new);
if let Some(count) = &count {
Expand Down Expand Up @@ -550,11 +564,11 @@ impl<'a> WasmBinary<'a> {

// add the instrumentation in the order of application
// note: this must be consistent with native execution
apply!(start);
apply!(meter);
apply!(dygas);
apply!(depth);
apply!(bound);
apply!(start);

if let Some(count) = &count {
apply!(*count);
Expand All @@ -563,29 +577,59 @@ impl<'a> WasmBinary<'a> {
code.expr = build;
}

let wasm = self.wasm.take().unwrap();
self.extra_data.extend(*codehash);
self.extra_data.extend(compile.version.to_be_bytes());

// 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)?;

// naively assume for now an upper bound of 5Mb
let asm_estimate = 5 * 1024 * 1024;
// predict costs
let funcs = self.codes.len() as u64;
let globals = self.globals.len() as u64;
let wasm_len = wasm.len() as u64;

let data_len: u64 = self.datas.iter().map(|x| x.range.len() as u64).sum();
let elem_len: u64 = self.elements.iter().map(|x| x.range.len() as u64).sum();
let data_len = data_len + elem_len;

let mut type_len = 0;
for index in &self.functions {
let ty = &self.types[*index as usize];
type_len += (ty.inputs.len() + ty.outputs.len()) as u64;
}

let mut asm_estimate: u64 = 512000;
asm_estimate = asm_estimate.saturating_add(funcs.saturating_mul(996829) / 1000);
asm_estimate = asm_estimate.saturating_add(type_len.saturating_mul(11416) / 1000);
asm_estimate = asm_estimate.saturating_add(wasm_len.saturating_mul(62628) / 10000);

let mut cached_init: u64 = 0;
cached_init = cached_init.saturating_add(funcs.saturating_mul(13420) / 100_000);
cached_init = cached_init.saturating_add(type_len.saturating_mul(89) / 100_000);
cached_init = cached_init.saturating_add(wasm_len.saturating_mul(122) / 100_000);
cached_init = cached_init.saturating_add(globals.saturating_mul(1628) / 1000);
cached_init = cached_init.saturating_add(data_len.saturating_mul(75244) / 100_000);
cached_init = cached_init.saturating_add(footprint as u64 * 5);

// TODO: determine safe value
let init_gas = 4096;
let cached_init_gas = 1024;
let mut init = cached_init;
init = init.saturating_add(funcs.saturating_mul(8252) / 1000);
init = init.saturating_add(type_len.saturating_mul(1059) / 1000);
init = init.saturating_add(wasm_len.saturating_mul(1286) / 10_000);

let [ink_left, ink_status] = meter.globals();
let depth_left = depth.globals();
Ok(StylusData {
ink_left: ink_left.as_u32(),
ink_status: ink_status.as_u32(),
depth_left: depth_left.as_u32(),
init_gas,
cached_init_gas,
asm_estimate,
init_cost: init.try_into()?,
cached_init_cost: cached_init.try_into()?,
asm_estimate: asm_estimate.try_into()?,
footprint,
user_main,
})
Expand All @@ -596,9 +640,10 @@ impl<'a> WasmBinary<'a> {
wasm: &'a [u8],
page_limit: u16,
compile: &CompileConfig,
codehash: &Bytes32,
) -> Result<(WasmBinary<'a>, StylusData)> {
let mut bin = parse(wasm, Path::new("user"))?;
let stylus_data = bin.instrument(compile)?;
let stylus_data = bin.instrument(compile, codehash)?;

let Some(memory) = bin.memories.first() else {
bail!("missing memory with export name \"memory\"")
Expand Down
Loading
Loading