From 1601030081123202150ca1b20ea7d06331994c2e Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Mon, 9 Oct 2017 13:12:58 +0200 Subject: [PATCH] WASM gas schedule (#6638) * some failing tests * finalize * fallable -> fallible * alter mul/div/static_i64 * Update schedule.rs * to u32 * balance charge also * review fixes * remove redundant line --- Cargo.lock | 3 +- ethcore/vm/src/schedule.rs | 45 ++++++++++++ ethcore/wasm/src/lib.rs | 11 ++- ethcore/wasm/src/runtime.rs | 143 ++++++++++++++++++++++++++++++------ ethcore/wasm/src/tests.rs | 45 ++++++------ 5 files changed, 201 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ec655522..3b461566c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3539,8 +3539,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasm-utils" version = "0.1.0" -source = "git+https://github.com/paritytech/wasm-utils#a6b6d75be0680568209813924a5ec0ad89e86697" +source = "git+https://github.com/paritytech/wasm-utils#6a39db802eb6b67a0c4e5cf50741f965e217335a" dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/vm/src/schedule.rs b/ethcore/vm/src/schedule.rs index 04c97b044..21924afea 100644 --- a/ethcore/vm/src/schedule.rs +++ b/ethcore/vm/src/schedule.rs @@ -113,6 +113,49 @@ pub struct Schedule { pub kill_dust: CleanDustMode, /// Enable EIP-86 rules pub eip86: bool, + /// Wasm extra schedule settings + pub wasm: WasmCosts, +} + +/// Wasm cost table +pub struct WasmCosts { + /// Arena allocator cost, per byte + pub alloc: u32, + /// Div operations multiplier. + pub div: u32, + /// Div operations multiplier. + pub mul: u32, + /// Memory (load/store) operations multiplier. + pub mem: u32, + /// Memory copy operation. + pub mem_copy: u32, + /// Static region charge, per byte. + pub static_region: u32, + /// General static query of u64 value from env-info + pub static_u64: u32, + /// General static query of U256 value from env-info + pub static_u256: u32, + /// General static query of Address value from env-info + pub static_address: u32, +} + +impl Default for WasmCosts { + fn default() -> Self { + WasmCosts { + alloc: 2, + div: 16, + mul: 4, + mem: 2, + mem_copy: 1, + static_region: 1, + + // due to runtime issues, this can be slow + static_u64: 32, + + static_u256: 64, + static_address: 40, + } + } } /// Dust accounts cleanup mode. @@ -187,6 +230,7 @@ impl Schedule { have_static_call: false, kill_dust: CleanDustMode::Off, eip86: false, + wasm: Default::default(), } } @@ -249,6 +293,7 @@ impl Schedule { have_static_call: false, kill_dust: CleanDustMode::Off, eip86: false, + wasm: Default::default(), } } } diff --git a/ethcore/wasm/src/lib.rs b/ethcore/wasm/src/lib.rs index a68d40d86..df5a4c043 100644 --- a/ethcore/wasm/src/lib.rs +++ b/ethcore/wasm/src/lib.rs @@ -122,9 +122,18 @@ impl vm::Vm for WasmInterpreter { &mut cursor ).map_err(|err| { vm::Error::Wasm(format!("Error deserializing contract code ({:?})", err)) - })? + })?, + runtime.gas_rules(), ); + let data_section_length = contract_module.data_section() + .map(|section| section.entries().iter().fold(0, |sum, entry| sum + entry.value().len())) + .unwrap_or(0) + as u64; + + let static_segment_cost = data_section_length * runtime.ext().schedule().wasm.static_region as u64; + runtime.charge(|_| static_segment_cost).map_err(Error)?; + let d_ptr = runtime.write_descriptor(¶ms.data.unwrap_or_default()) .map_err(Error)?; diff --git a/ethcore/wasm/src/runtime.rs b/ethcore/wasm/src/runtime.rs index fa30d7753..1f8bf428a 100644 --- a/ethcore/wasm/src/runtime.rs +++ b/ethcore/wasm/src/runtime.rs @@ -22,6 +22,7 @@ use byteorder::{LittleEndian, ByteOrder}; use vm; use parity_wasm::interpreter; +use wasm_utils::rules; use bigint::prelude::U256; use bigint::hash::H256; use util::Address; @@ -111,6 +112,7 @@ pub struct Runtime<'a, 'b> { memory: Arc, context: RuntimeContext, instance: &'b InterpreterProgramInstance, + gas_rules: rules::Set, } impl<'a, 'b> Runtime<'a, 'b> { @@ -123,6 +125,20 @@ impl<'a, 'b> Runtime<'a, 'b> { context: RuntimeContext, program_instance: &'d InterpreterProgramInstance, ) -> Runtime<'c, 'd> { + + let rules = { + let schedule = ext.schedule(); + + rules::Set::new({ + let mut vals = ::std::collections::HashMap::with_capacity(4); + vals.insert(rules::InstructionType::Load, schedule.wasm.mem as u32); + vals.insert(rules::InstructionType::Store, schedule.wasm.mem as u32); + vals.insert(rules::InstructionType::Div, schedule.wasm.div as u32); + vals.insert(rules::InstructionType::Mul, schedule.wasm.mul as u32); + vals + }) + }; + Runtime { gas_counter: 0, gas_limit: gas_limit, @@ -131,6 +147,7 @@ impl<'a, 'b> Runtime<'a, 'b> { ext: ext, context: context, instance: program_instance, + gas_rules: rules, } } @@ -143,6 +160,8 @@ impl<'a, 'b> Runtime<'a, 'b> { let key = self.pop_h256(&mut context)?; trace!(target: "wasm", "storage_write: value {} at @{}", &val, &key); + self.charge(|schedule| schedule.sstore_set_gas as u64)?; + self.ext.set_storage(key, val).map_err(|_| UserTrap::StorageUpdateError)?; Ok(Some(0i32.into())) @@ -155,9 +174,10 @@ impl<'a, 'b> Runtime<'a, 'b> { let mut context = context; let val_ptr = context.value_stack.pop_as::()?; let key = self.pop_h256(&mut context)?; - let val = self.ext.storage_at(&key).map_err(|_| UserTrap::StorageReadError)?; + self.charge(|schedule| schedule.sload_gas as u64)?; + self.memory.set(val_ptr as u32, &*val)?; Ok(Some(0.into())) @@ -170,6 +190,9 @@ impl<'a, 'b> Runtime<'a, 'b> { let mut context = context; let return_ptr = context.value_stack.pop_as::()? as u32; let address = self.pop_address(&mut context)?; + + self.charge(|schedule| schedule.balance_gas as u64)?; + let balance = self.ext.balance(&address).map_err(|_| UserTrap::BalanceQueryError)?; let value: H256 = balance.into(); self.memory.set(return_ptr, &*value)?; @@ -183,12 +206,32 @@ impl<'a, 'b> Runtime<'a, 'b> { let mut context = context; let refund_address = self.pop_address(&mut context)?; + if self.ext.exists(&refund_address).map_err(|_| UserTrap::SuicideAbort)? { + trace!(target: "wasm", "Suicide: refund to existing address {}", refund_address); + self.charge(|schedule| schedule.suicide_gas as u64)?; + } else { + trace!(target: "wasm", "Suicide: refund to new address {}", refund_address); + self.charge(|schedule| schedule.suicide_to_new_account_cost as u64)?; + } + self.ext.suicide(&refund_address).map_err(|_| UserTrap::SuicideAbort)?; // We send trap to interpreter so it should abort further execution Err(UserTrap::Suicide.into()) } + /// Charge gas according to closure + pub fn charge(&mut self, f: F) -> Result<(), InterpreterError> + where F: FnOnce(&vm::Schedule) -> u64 + { + let amount = f(self.ext.schedule()); + if !self.charge_gas(amount as u64) { + Err(UserTrap::GasLimit.into()) + } else { + Ok(()) + } + } + /// Invoke create in the state runtime pub fn create(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> @@ -211,6 +254,9 @@ impl<'a, 'b> Runtime<'a, 'b> { let code = self.memory.get(code_ptr, code_len as usize)?; + self.charge(|schedule| schedule.create_gas as u64)?; + self.charge(|schedule| schedule.create_data_gas as u64 * code.len() as u64)?; + let gas_left = self.gas_left() .map_err(|_| UserTrap::InvalidGasState)? .into(); @@ -312,6 +358,8 @@ impl<'a, 'b> Runtime<'a, 'b> { } } + self.charge(|schedule| schedule.call_gas as u64)?; + let mut result = Vec::with_capacity(result_alloc_len as usize); result.resize(result_alloc_len as usize, 0); let gas = self.gas_left() @@ -369,6 +417,9 @@ impl<'a, 'b> Runtime<'a, 'b> { -> Result, InterpreterError> { let amount = context.value_stack.pop_as::()? as u32; + + self.charge(|schedule| schedule.wasm.alloc as u64 * amount as u64)?; + let previous_top = self.dynamic_top; self.dynamic_top = previous_top + amount; Ok(Some((previous_top as i32).into())) @@ -492,13 +543,15 @@ impl<'a, 'b> Runtime<'a, 'b> { &*self.memory } - fn mem_copy(&self, context: InterpreterCallerContext) + fn mem_copy(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { let len = context.value_stack.pop_as::()? as u32; let dst = context.value_stack.pop_as::()? as u32; let src = context.value_stack.pop_as::()? as u32; + self.charge(|schedule| schedule.wasm.mem_copy as u64 * len as u64)?; + let mem = self.memory().get(src, len as usize)?; self.memory().set(dst, &mem)?; @@ -542,6 +595,8 @@ impl<'a, 'b> Runtime<'a, 'b> { let block_hi = context.value_stack.pop_as::()? as u32; let block_lo = context.value_stack.pop_as::()? as u32; + self.charge(|schedule| schedule.blockhash_gas as u64)?; + let block_num = (block_hi as u64) << 32 | block_lo as u64; trace!("Requesting block hash for block #{}", block_num); @@ -552,44 +607,72 @@ impl<'a, 'b> Runtime<'a, 'b> { Ok(Some(0i32.into())) } + fn return_address_ptr(&mut self, ptr: u32, val: Address) -> Result<(), InterpreterError> + { + self.charge(|schedule| schedule.wasm.static_address as u64)?; + self.memory.set(ptr, &*val)?; + Ok(()) + } + + fn return_u256_ptr(&mut self, ptr: u32, val: U256) -> Result<(), InterpreterError> { + let value: H256 = val.into(); + self.charge(|schedule| schedule.wasm.static_u256 as u64)?; + self.memory.set(ptr, &*value)?; + Ok(()) + } + fn coinbase(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { - let return_ptr = context.value_stack.pop_as::()? as u32; - self.memory.set(return_ptr, &*self.ext.env_info().author)?; + let author = self.ext.env_info().author; + self.return_address_ptr( + context.value_stack.pop_as::()? as u32, + author, + )?; Ok(None) } fn sender(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { - let return_ptr = context.value_stack.pop_as::()? as u32; - self.memory.set(return_ptr, &*self.context.sender)?; - Ok(None) + let sender = self.context.sender; + self.return_address_ptr( + context.value_stack.pop_as::()? as u32, + sender, + )?; + Ok(None) } fn address(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { - let return_ptr = context.value_stack.pop_as::()? as u32; - self.memory.set(return_ptr, &*self.context.address)?; - Ok(None) + let addr = self.context.address; + self.return_address_ptr( + context.value_stack.pop_as::()? as u32, + addr, + )?; + Ok(None) } fn origin(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { - let return_ptr = context.value_stack.pop_as::()? as u32; - self.memory.set(return_ptr, &*self.context.origin)?; - Ok(None) + let origin = self.context.origin; + self.return_address_ptr( + context.value_stack.pop_as::()? as u32, + origin, + )?; + Ok(None) } fn value(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { - let return_ptr = context.value_stack.pop_as::()? as u32; - let value: H256 = self.context.value.clone().into(); - self.memory.set(return_ptr, &*value)?; + let value = self.context.value; + self.return_u256_ptr( + context.value_stack.pop_as::()? as u32, + value, + )?; Ok(None) } @@ -610,22 +693,28 @@ impl<'a, 'b> Runtime<'a, 'b> { fn difficulty(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { - let return_ptr = context.value_stack.pop_as::()? as u32; - let difficulty: H256 = self.ext.env_info().difficulty.into(); - self.memory.set(return_ptr, &*difficulty)?; + let difficulty = self.ext.env_info().difficulty; + self.return_u256_ptr( + context.value_stack.pop_as::()? as u32, + difficulty, + )?; Ok(None) } fn ext_gas_limit(&mut self, context: InterpreterCallerContext) -> Result, InterpreterError> { - let return_ptr = context.value_stack.pop_as::()? as u32; - let gas_limit: H256 = self.ext.env_info().gas_limit.into(); - self.memory.set(return_ptr, &*gas_limit)?; - Ok(None) + let gas_limit = self.ext.env_info().gas_limit; + self.return_u256_ptr( + context.value_stack.pop_as::()? as u32, + gas_limit, + )?; + Ok(None) } fn return_i64(&mut self, val: i64) -> Result, InterpreterError> { + self.charge(|schedule| schedule.wasm.static_u64 as u64)?; + let uval = val as u64; let hi = (uval >> 32) as i32; let lo = (uval << 32 >> 32) as i32; @@ -656,6 +745,14 @@ impl<'a, 'b> Runtime<'a, 'b> { ) ) } + + pub fn gas_rules(&self) -> &rules::Set { + &self.gas_rules + } + + pub fn ext(&mut self) -> &mut vm::Ext { + self.ext + } } impl<'a, 'b> interpreter::UserFunctionExecutor for Runtime<'a, 'b> { diff --git a/ethcore/wasm/src/tests.rs b/ethcore/wasm/src/tests.rs index 5a6be6e2a..57b5ab6a8 100644 --- a/ethcore/wasm/src/tests.rs +++ b/ethcore/wasm/src/tests.rs @@ -60,7 +60,7 @@ fn empty() { test_finalize(interpreter.exec(params, &mut ext)).unwrap() }; - assert_eq!(gas_left, U256::from(99_992)); + assert_eq!(gas_left, U256::from(99_976)); } // This test checks if the contract deserializes payload header properly. @@ -68,6 +68,8 @@ fn empty() { // logger.wasm writes all these provided fixed header fields to some arbitrary storage keys. #[test] fn logger() { + ::ethcore_logger::init_log(); + let code = load_sample!("logger.wasm"); let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap(); let sender: Address = "0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d".parse().unwrap(); @@ -87,8 +89,7 @@ fn logger() { test_finalize(interpreter.exec(params, &mut ext)).unwrap() }; - println!("ext.store: {:?}", ext.store); - assert_eq!(gas_left, U256::from(98_731)); + assert_eq!(gas_left, U256::from(15_177)); let address_val: H256 = address.into(); assert_eq!( ext.store.get(&"0100000000000000000000000000000000000000000000000000000000000000".parse().unwrap()).expect("storage key to exist"), @@ -141,7 +142,7 @@ fn identity() { } }; - assert_eq!(gas_left, U256::from(99_812)); + assert_eq!(gas_left, U256::from(99_695)); assert_eq!( Address::from_slice(&result), @@ -175,7 +176,7 @@ fn dispersion() { } }; - assert_eq!(gas_left, U256::from(99_474)); + assert_eq!(gas_left, U256::from(96_543)); assert_eq!( result, @@ -204,7 +205,7 @@ fn suicide_not() { } }; - assert_eq!(gas_left, U256::from(99_691)); + assert_eq!(gas_left, U256::from(96_822)); assert_eq!( result, @@ -214,6 +215,8 @@ fn suicide_not() { #[test] fn suicide() { + ::ethcore_logger::init_log(); + let code = load_sample!("suicidal.wasm"); let refund: Address = "01030507090b0d0f11131517191b1d1f21232527".parse().unwrap(); @@ -238,7 +241,7 @@ fn suicide() { } }; - assert_eq!(gas_left, U256::from(99_490)); + assert_eq!(gas_left, U256::from(96_580)); assert!(ext.suicides.contains(&refund)); } @@ -269,7 +272,7 @@ fn create() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Create, - gas: U256::from(99_144), + gas: U256::from(62_324), sender_address: None, receive_address: None, value: Some(1_000_000_000.into()), @@ -277,7 +280,7 @@ fn create() { code_address: None, } )); - assert_eq!(gas_left, U256::from(99_113)); + assert_eq!(gas_left, U256::from(62_289)); } @@ -311,7 +314,7 @@ fn call_code() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Call, - gas: U256::from(99_138), + gas: U256::from(95_585), sender_address: Some(sender), receive_address: Some(receiver), value: None, @@ -319,7 +322,7 @@ fn call_code() { code_address: Some("0d13710000000000000000000000000000000000".parse().unwrap()), } )); - assert_eq!(gas_left, U256::from(94_269)); + assert_eq!(gas_left, U256::from(90_665)); // siphash result let res = LittleEndian::read_u32(&result[..]); @@ -356,7 +359,7 @@ fn call_static() { assert!(ext.calls.contains( &FakeCall { call_type: FakeCallType::Call, - gas: U256::from(99_138), + gas: U256::from(95_585), sender_address: Some(sender), receive_address: Some(receiver), value: None, @@ -364,7 +367,7 @@ fn call_static() { code_address: Some("13077bfb00000000000000000000000000000000".parse().unwrap()), } )); - assert_eq!(gas_left, U256::from(94_269)); + assert_eq!(gas_left, U256::from(90_665)); // siphash result let res = LittleEndian::read_u32(&result[..]); @@ -390,7 +393,7 @@ fn realloc() { GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()), } }; - assert_eq!(gas_left, U256::from(99_614)); + assert_eq!(gas_left, U256::from(96_811)); assert_eq!(result, vec![0u8; 2]); } @@ -416,7 +419,7 @@ fn storage_read() { } }; - assert_eq!(gas_left, U256::from(99_695)); + assert_eq!(gas_left, U256::from(96_645)); assert_eq!(Address::from(&result[12..32]), address); } @@ -443,7 +446,7 @@ fn keccak() { }; assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87")); - assert_eq!(gas_left, U256::from(84_026)); + assert_eq!(gas_left, U256::from(80_452)); } @@ -497,7 +500,7 @@ fn math_add() { } ).expect("Interpreter to execute without any errors"); - assert_eq!(gas_left, U256::from(98_241)); + assert_eq!(gas_left, U256::from(94_666)); assert_eq!( U256::from_dec_str("1888888888888888888888888888887").unwrap(), (&result[..]).into() @@ -519,7 +522,7 @@ fn math_mul() { } ).expect("Interpreter to execute without any errors"); - assert_eq!(gas_left, U256::from(97_390)); + assert_eq!(gas_left, U256::from(93_719)); assert_eq!( U256::from_dec_str("888888888888888888888888888887111111111111111111111111111112").unwrap(), (&result[..]).into() @@ -541,7 +544,7 @@ fn math_sub() { } ).expect("Interpreter to execute without any errors"); - assert_eq!(gas_left, U256::from(98_285)); + assert_eq!(gas_left, U256::from(94_718)); assert_eq!( U256::from_dec_str("111111111111111111111111111111").unwrap(), (&result[..]).into() @@ -580,7 +583,7 @@ fn math_div() { } ).expect("Interpreter to execute without any errors"); - assert_eq!(gas_left, U256::from(91_574)); + assert_eq!(gas_left, U256::from(86_996)); assert_eq!( U256::from_dec_str("1125000").unwrap(), (&result[..]).into() @@ -672,5 +675,5 @@ fn externs() { "Gas limit requested and returned does not match" ); - assert_eq!(gas_left, U256::from(96_284)); + assert_eq!(gas_left, U256::from(91_857)); }