Backport Core PRs to beta (#7891)

* update back-references more aggressively after answering from cache (#7578)

* Add new EF ropstens nodes. (#7824)

* Add new EF ropstens nodes.

* Fix tests

* Add a timeout for light client sync requests (#7848)

* Add a timeout for light client sync requests

* Adjusting timeout to number of headers

* Flush keyfiles. Resolves #7632 (#7868)

* Fix wallet import (#7873)

* rpc: generate new account id for imported wallets

* ethstore: handle duplicate wallet filenames

* ethstore: simplify deduplication of wallet file names

* ethstore: do not dedup wallet filenames on update

* ethstore: fix minor grumbles

* [WASM] mem_cmp added to the Wasm runtime (#7539)

* mem_cmp added to the Wasm runtime

* schedule.wasm.mem_copy to schedule.wasm.mem_cmp for mem_cmp

* [Wasm] memcmp fix and test added (#7590)

* [Wasm] memcmp fix and test added

* [Wasm] use reqrep_test! macro for memcmp test

* wasmi interpreter (#7796)

* adjust storage update evm-style (#7812)

* disable internal memory (#7842)
This commit is contained in:
Tomasz Drwięga 2018-02-14 16:13:38 +01:00 committed by Afri Schoedon
parent af70a681d5
commit b60511e3d2
24 changed files with 1231 additions and 1308 deletions

34
Cargo.lock generated
View File

@ -2286,12 +2286,12 @@ dependencies = [
[[package]]
name = "parity-wasm"
version = "0.15.3"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2342,6 +2342,15 @@ dependencies = [
"parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "parking_lot_core"
version = "0.2.6"
@ -3477,16 +3486,18 @@ dependencies = [
"ethcore-bigint 0.2.1",
"ethcore-logger 1.9.0",
"ethcore-util 1.9.0",
"libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
"vm 0.1.0",
"wasm-utils 0.1.0 (git+https://github.com/paritytech/wasm-utils)",
"wasmi 0.0.0 (git+https://github.com/pepyakin/wasmi)",
]
[[package]]
name = "wasm-utils"
version = "0.1.0"
source = "git+https://github.com/paritytech/wasm-utils#3d59f7ca0661317bc66894a26b2a5a319fa5d229"
source = "git+https://github.com/paritytech/wasm-utils#6fdc1c4ed47a6acb0a4774da505a416dd637bc6d"
dependencies = [
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.29.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3494,7 +3505,16 @@ dependencies = [
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasmi"
version = "0.0.0"
source = "git+https://github.com/pepyakin/wasmi#551c99273042deaad869c17798060e2212deacab"
dependencies = [
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-wasm 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -3712,9 +3732,10 @@ dependencies = [
"checksum parity-tokio-ipc 0.1.5 (git+https://github.com/nikvolf/parity-tokio-ipc)" = "<none>"
"checksum parity-ui-old-precompiled 1.9.0 (git+https://github.com/js-dist-paritytech/parity-beta-1-9-v1.git)" = "<none>"
"checksum parity-ui-precompiled 1.9.0 (git+https://github.com/js-dist-paritytech/parity-beta-1-9-shell.git)" = "<none>"
"checksum parity-wasm 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8431a184ad88cfbcd71a792aaca319cc7203a94300c26b8dce2d0df0681ea87d"
"checksum parity-wasm 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ba4b1d4236b76694f6ab8d8d00cdbe1e37c6dd1b5c803d26721f27e097d4d9"
"checksum parity-wordlist 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0dec124478845b142f68b446cbee953d14d4b41f1bc0425024417720dce693"
"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e"
"checksum parking_lot 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3e7f7c9857874e54afeb950eebeae662b1e51a2493666d2ea4c0a5d91dcf0412"
"checksum parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f610cb9664da38e417ea3225f23051f589851999535290e077939838ab7a595"
"checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356"
"checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
@ -3836,6 +3857,7 @@ dependencies = [
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum wasm-utils 0.1.0 (git+https://github.com/paritytech/wasm-utils)" = "<none>"
"checksum wasmi 0.0.0 (git+https://github.com/pepyakin/wasmi)" = "<none>"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum ws 0.7.1 (git+https://github.com/tomusdrw/ws-rs)" = "<none>"

View File

@ -90,7 +90,14 @@ impl Pending {
match self.requests[idx].respond_local(cache) {
Some(response) => {
self.requests.supply_response_unchecked(&response);
// update header and back-references after each from-cache
// response to ensure that the requests are left in a consistent
// state and increase the likelihood of being able to answer
// the next request from cache.
self.update_header_refs(idx, &response);
self.fill_unanswered();
self.responses.push(response);
}
None => break,

View File

@ -54,8 +54,8 @@
"nodes": [
"enode://6332792c4a00e3e4ee0926ed89e0d27ef985424d97b6a45bf0f23e51f0dcb5e66b875777506458aea7af6f9e4ffb69f43f3778ee73c81ed9d34c51c4b16b0b0f@52.232.243.152:30303",
"enode://94c15d1b9e2fe7ce56e458b9a3b672ef11894ddedd0c6f247e0f1d3487f52b66208fb4aeb8179fce6e3a749ea93ed147c37976d67af557508d199d9594c35f09@192.81.208.223:30303",
"enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303",
"enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303"
"enode://30b7ab30a01c124a6cceca36863ece12c4f5fa68e3ba9b0b51407ccc002eeed3b3102d20a88f1c1d3c3154e2449317b8ef95090e77b312d5cc39354f86d5d606@52.176.7.10:30303",
"enode://865a63255b3bb68023b6bffd5095118fcc13e79dcf014fe4e47e065c350c7cc72af2e53eff895f11ba1bbb6a2b33271c1116ee870f266618eadfc2e78aa7349c@52.176.100.77:30303"
],
"accounts": {
"0000000000000000000000000000000000000000": { "balance": "1" },

@ -1 +1 @@
Subproject commit d9d6133c1bc5dca4c74c9eb758a39546a0d46b45
Subproject commit fb111c82deff8759f54a5038d07cecc77cb5a663

View File

@ -264,9 +264,9 @@ impl AccountProvider {
Ok(Address::from(account.address).into())
}
/// Import a new presale wallet.
pub fn import_wallet(&self, json: &[u8], password: &str) -> Result<Address, Error> {
let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password)?;
/// Import a new wallet.
pub fn import_wallet(&self, json: &[u8], password: &str, gen_id: bool) -> Result<Address, Error> {
let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password, gen_id)?;
if self.blacklisted_accounts.contains(&account.address) {
self.sstore.remove_account(&account, password)?;
return Err(SSError::InvalidAccount.into());

View File

@ -156,11 +156,7 @@ impl TransactOptions<trace::NoopTracer, trace::NoopVMTracer> {
pub fn executor(machine: &Machine, vm_factory: &Factory, params: &ActionParams) -> Box<vm::Vm> {
if machine.supports_wasm() && params.code.as_ref().map_or(false, |code| code.len() > 4 && &code[0..4] == WASM_MAGIC_NUMBER) {
Box::new(
wasm::WasmInterpreter::new()
// prefer to fail fast
.expect("Failed to create wasm runtime")
)
Box::new(wasm::WasmInterpreter)
} else {
vm_factory.create(params.gas)
}

View File

@ -127,18 +127,19 @@ pub struct WasmCosts {
pub mul: u32,
/// Memory (load/store) operations multiplier.
pub mem: u32,
/// Memory copy operation, per byte.
pub mem_copy: u32,
/// Memory move operation, per byte.
pub mem_move: u32,
/// Memory set operation, per byte.
pub mem_set: u32,
/// Static region charge, per byte.
pub static_region: 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,
/// Memory stipend. Amount of free memory (in 64kb pages) each contract can use for stack.
pub initial_mem: u32,
/// Grow memory cost, per page (64kb)
pub grow_mem: u32,
/// Cost of wasm opcode is calculated as TABLE_ENTRY_COST * `opcodes_mul` / `opcodes_div`
pub opcodes_mul: u32,
/// Cost of wasm opcode is calculated as TABLE_ENTRY_COST * `opcodes_mul` / `opcodes_div`
pub opcodes_div: u32,
}
impl Default for WasmCosts {
@ -148,12 +149,12 @@ impl Default for WasmCosts {
div: 16,
mul: 4,
mem: 2,
mem_copy: 1,
mem_move: 1,
mem_set: 1,
static_region: 1,
static_u256: 64,
static_address: 40,
initial_mem: 4096,
grow_mem: 8192,
opcodes_mul: 3,
opcodes_div: 8,
}
}
}

View File

@ -8,7 +8,9 @@ byteorder = "1.0"
ethcore-util = { path = "../../util" }
ethcore-bigint = { path = "../../util/bigint" }
log = "0.3"
parity-wasm = "0.15"
parity-wasm = "0.23"
libc = "0.2"
wasm-utils = { git = "https://github.com/paritytech/wasm-utils" }
vm = { path = "../vm" }
ethcore-logger = { path = "../../logger" }
wasmi = { git = "https://github.com/pepyakin/wasmi" }

View File

@ -16,7 +16,7 @@ fn load_code<P: AsRef<path::Path>>(p: P) -> io::Result<Vec<u8>> {
}
fn wasm_interpreter() -> WasmInterpreter {
WasmInterpreter::new().expect("wasm interpreter to create without errors")
WasmInterpreter
}
#[derive(Debug)]

View File

@ -14,178 +14,279 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Wasm env module bindings
//! Env module glue for wasmi interpreter
use parity_wasm::elements::ValueType::*;
use parity_wasm::interpreter::{self, UserFunctionDescriptor};
use parity_wasm::interpreter::UserFunctionDescriptor::*;
use super::runtime::{Runtime, UserTrap};
use std::cell::RefCell;
use wasmi::{
self, Signature, Error, FuncRef, FuncInstance, MemoryDescriptor,
MemoryRef, MemoryInstance,
};
pub const SIGNATURES: &'static [UserFunctionDescriptor] = &[
Static(
"_storage_read",
&[I32; 2],
/// Internal ids all functions runtime supports. This is just a glue for wasmi interpreter
/// that lacks high-level api and later will be factored out
pub mod ids {
pub const STORAGE_WRITE_FUNC: usize = 0;
pub const STORAGE_READ_FUNC: usize = 10;
pub const RET_FUNC: usize = 20;
pub const GAS_FUNC: usize = 30;
pub const FETCH_INPUT_FUNC: usize = 40;
pub const INPUT_LENGTH_FUNC: usize = 50;
pub const CCALL_FUNC: usize = 60;
pub const SCALL_FUNC: usize = 70;
pub const DCALL_FUNC: usize = 80;
pub const VALUE_FUNC: usize = 90;
pub const CREATE_FUNC: usize = 100;
pub const SUICIDE_FUNC: usize = 110;
pub const BLOCKHASH_FUNC: usize = 120;
pub const BLOCKNUMBER_FUNC: usize = 130;
pub const COINBASE_FUNC: usize = 140;
pub const DIFFICULTY_FUNC: usize = 150;
pub const GASLIMIT_FUNC: usize = 160;
pub const TIMESTAMP_FUNC: usize = 170;
pub const ADDRESS_FUNC: usize = 180;
pub const SENDER_FUNC: usize = 190;
pub const ORIGIN_FUNC: usize = 200;
pub const ELOG_FUNC: usize = 210;
pub const PANIC_FUNC: usize = 1000;
pub const DEBUG_FUNC: usize = 1010;
}
/// Signatures of all functions runtime supports. The actual dispatch happens at
/// impl runtime::Runtime methods.
pub mod signatures {
use wasmi::{self, ValueType};
use wasmi::ValueType::*;
pub struct StaticSignature(pub &'static [ValueType], pub Option<ValueType>);
pub const STORAGE_READ: StaticSignature = StaticSignature(
&[I32, I32],
None,
),
Static(
"_storage_write",
&[I32; 2],
);
pub const STORAGE_WRITE: StaticSignature = StaticSignature(
&[I32, I32],
None,
),
Static(
"_balance",
&[I32; 2],
);
pub const RET: StaticSignature = StaticSignature(
&[I32, I32],
None,
),
Static(
"_ext_malloc",
);
pub const GAS: StaticSignature = StaticSignature(
&[I32],
None,
);
pub const FETCH_INPUT: StaticSignature = StaticSignature(
&[I32],
None,
);
pub const INPUT_LENGTH: StaticSignature = StaticSignature(
&[],
Some(I32),
),
Static(
"_ext_free",
&[I32],
None,
),
Static(
"gas",
&[I32],
None,
),
Static(
"_debug",
&[I32; 2],
None,
),
Static(
"_suicide",
&[I32],
None,
),
Static(
"_create",
&[I32; 4],
Some(I32),
),
Static(
"_ccall",
);
pub const CCALL: StaticSignature = StaticSignature(
&[I64, I32, I32, I32, I32, I32, I32],
Some(I32),
),
Static(
"_dcall",
);
pub const DCALL: StaticSignature = StaticSignature(
&[I64, I32, I32, I32, I32, I32],
Some(I32),
),
Static(
"_scall",
);
pub const SCALL: StaticSignature = StaticSignature(
&[I64, I32, I32, I32, I32, I32],
Some(I32),
),
Static(
"abort",
);
pub const PANIC: StaticSignature = StaticSignature(
&[I32, I32],
None,
);
pub const DEBUG: StaticSignature = StaticSignature(
&[I32, I32],
None,
);
pub const VALUE: StaticSignature = StaticSignature(
&[I32],
None,
),
Static(
"_emscripten_memcpy_big",
&[I32; 3],
);
pub const CREATE: StaticSignature = StaticSignature(
&[I32, I32, I32, I32],
Some(I32),
),
Static(
"_ext_memcpy",
&[I32; 3],
Some(I32),
),
Static(
"_ext_memset",
&[I32; 3],
Some(I32),
),
Static(
"_ext_memmove",
&[I32; 3],
Some(I32),
),
Static(
"_panic",
&[I32; 2],
);
pub const SUICIDE: StaticSignature = StaticSignature(
&[I32],
None,
),
Static(
"_blockhash",
);
pub const BLOCKHASH: StaticSignature = StaticSignature(
&[I64, I32],
None,
),
Static(
"_coinbase",
&[I32],
None,
),
Static(
"_sender",
&[I32],
None,
),
Static(
"_origin",
&[I32],
None,
),
Static(
"_address",
&[I32],
None,
),
Static(
"_value",
&[I32],
None,
),
Static(
"_timestamp",
);
pub const BLOCKNUMBER: StaticSignature = StaticSignature(
&[],
Some(I64),
),
Static(
"_blocknumber",
);
pub const COINBASE: StaticSignature = StaticSignature(
&[I32],
None,
);
pub const DIFFICULTY: StaticSignature = StaticSignature(
&[I32],
None,
);
pub const GASLIMIT: StaticSignature = StaticSignature(
&[I32],
None,
);
pub const TIMESTAMP: StaticSignature = StaticSignature(
&[],
Some(I64),
),
Static(
"_difficulty",
);
pub const ADDRESS: StaticSignature = StaticSignature(
&[I32],
None,
),
Static(
"_gaslimit",
);
pub const SENDER: StaticSignature = StaticSignature(
&[I32],
None,
),
Static(
"_elog",
&[I32; 4],
);
pub const ORIGIN: StaticSignature = StaticSignature(
&[I32],
None,
),
);
// TODO: Get rid of it also somehow?
Static(
"_llvm_trap",
&[I32; 0],
None
),
pub const ELOG: StaticSignature = StaticSignature(
&[I32, I32, I32, I32],
None,
);
Static(
"_llvm_bswap_i64",
&[I64],
Some(I64)
),
];
pub fn native_bindings<'a>(runtime: &'a mut Runtime) -> interpreter::UserDefinedElements<'a, UserTrap> {
interpreter::UserDefinedElements {
executor: Some(runtime),
globals: ::std::collections::HashMap::new(),
functions: ::std::borrow::Cow::from(SIGNATURES),
impl Into<wasmi::Signature> for StaticSignature {
fn into(self) -> wasmi::Signature {
wasmi::Signature::new(self.0, self.1)
}
}
}
fn host(signature: signatures::StaticSignature, idx: usize) -> FuncRef {
FuncInstance::alloc_host(signature.into(), idx)
}
/// Import resolver for wasmi
/// Maps all functions that runtime support to the corresponding contract import
/// entries.
/// Also manages initial memory request from the runtime.
#[derive(Default)]
pub struct ImportResolver {
max_memory: u32,
memory: RefCell<Option<MemoryRef>>,
}
impl ImportResolver {
/// New import resolver with specifed maximum amount of inital memory (in wasm pages = 64kb)
pub fn with_limit(max_memory: u32) -> ImportResolver {
ImportResolver {
max_memory: max_memory,
memory: RefCell::new(None),
}
}
/// Returns memory that was instantiated during the contract module
/// start. If contract does not use memory at all, the dummy memory of length (0, 0)
/// will be created instead. So this method always returns memory instance
/// unless errored.
pub fn memory_ref(&self) -> MemoryRef {
{
let mut mem_ref = self.memory.borrow_mut();
if mem_ref.is_none() {
*mem_ref = Some(
MemoryInstance::alloc(0, Some(0)).expect("Memory allocation (0, 0) should not fail; qed")
);
}
}
self.memory.borrow().clone().expect("it is either existed or was created as (0, 0) above; qed")
}
/// Returns memory size module initially requested
pub fn memory_size(&self) -> Result<u32, Error> {
Ok(self.memory_ref().size())
}
}
impl wasmi::ModuleImportResolver for ImportResolver {
fn resolve_func(&self, field_name: &str, _signature: &Signature) -> Result<FuncRef, Error> {
let func_ref = match field_name {
"storage_read" => host(signatures::STORAGE_READ, ids::STORAGE_READ_FUNC),
"storage_write" => host(signatures::STORAGE_WRITE, ids::STORAGE_WRITE_FUNC),
"ret" => host(signatures::RET, ids::RET_FUNC),
"gas" => host(signatures::GAS, ids::GAS_FUNC),
"input_length" => host(signatures::INPUT_LENGTH, ids::INPUT_LENGTH_FUNC),
"fetch_input" => host(signatures::FETCH_INPUT, ids::FETCH_INPUT_FUNC),
"panic" => host(signatures::PANIC, ids::PANIC_FUNC),
"debug" => host(signatures::DEBUG, ids::DEBUG_FUNC),
"ccall" => host(signatures::CCALL, ids::CCALL_FUNC),
"dcall" => host(signatures::DCALL, ids::DCALL_FUNC),
"scall" => host(signatures::SCALL, ids::SCALL_FUNC),
"value" => host(signatures::VALUE, ids::VALUE_FUNC),
"create" => host(signatures::CREATE, ids::CREATE_FUNC),
"suicide" => host(signatures::SUICIDE, ids::SUICIDE_FUNC),
"blockhash" => host(signatures::BLOCKHASH, ids::BLOCKHASH_FUNC),
"blocknumber" => host(signatures::BLOCKNUMBER, ids::BLOCKNUMBER_FUNC),
"coinbase" => host(signatures::COINBASE, ids::COINBASE_FUNC),
"difficulty" => host(signatures::DIFFICULTY, ids::DIFFICULTY_FUNC),
"gaslimit" => host(signatures::GASLIMIT, ids::GASLIMIT_FUNC),
"timestamp" => host(signatures::TIMESTAMP, ids::TIMESTAMP_FUNC),
"address" => host(signatures::ADDRESS, ids::ADDRESS_FUNC),
"sender" => host(signatures::SENDER, ids::SENDER_FUNC),
"origin" => host(signatures::ORIGIN, ids::ORIGIN_FUNC),
"elog" => host(signatures::ELOG, ids::ELOG_FUNC),
_ => {
return Err(wasmi::Error::Instantiation(
format!("Export {} not found", field_name),
))
}
};
Ok(func_ref)
}
fn resolve_memory(
&self,
field_name: &str,
descriptor: &MemoryDescriptor,
) -> Result<MemoryRef, Error> {
if field_name == "memory" {
let effective_max = descriptor.maximum().unwrap_or(self.max_memory + 1);
if descriptor.initial() > self.max_memory || effective_max > self.max_memory
{
Err(Error::Instantiation("Module requested too much memory".to_owned()))
} else {
let mem = MemoryInstance::alloc(descriptor.initial(), descriptor.maximum())?;
*self.memory.borrow_mut() = Some(mem.clone());
Ok(mem)
}
} else {
Err(Error::Instantiation("Memory imported under unknown name".to_owned()))
}
}
}

View File

@ -16,34 +16,30 @@
//! Wasm Interpreter
extern crate vm;
extern crate byteorder;
extern crate ethcore_logger;
extern crate ethcore_util as util;
extern crate ethcore_bigint as bigint;
#[macro_use] extern crate log;
extern crate ethcore_logger;
extern crate byteorder;
extern crate libc;
extern crate parity_wasm;
extern crate vm;
extern crate wasm_utils;
extern crate wasmi;
mod runtime;
mod ptr;
mod result;
#[cfg(test)]
mod tests;
mod env;
mod panic_payload;
const DEFAULT_STACK_SPACE: u32 = 5 * 1024 * 1024;
use parity_wasm::{interpreter, elements};
use parity_wasm::interpreter::ModuleInstanceInterface;
mod parser;
use vm::{GasLeft, ReturnData, ActionParams};
use self::runtime::{Runtime, RuntimeContext, UserTrap};
use wasmi::Error as InterpreterError;
pub use self::runtime::InterpreterError;
use runtime::{Runtime, RuntimeContext};
const DEFAULT_RESULT_BUFFER: usize = 1024;
use bigint::uint::U256;
/// Wrapped interpreter error
#[derive(Debug)]
@ -61,139 +57,110 @@ impl From<Error> for vm::Error {
}
}
impl From<UserTrap> for vm::Error {
fn from(e: UserTrap) -> Self { e.into() }
}
/// Wasm interpreter instance
pub struct WasmInterpreter {
program: runtime::InterpreterProgramInstance,
result: Vec<u8>,
}
pub struct WasmInterpreter;
impl WasmInterpreter {
/// New wasm interpreter instance
pub fn new() -> Result<WasmInterpreter, Error> {
Ok(WasmInterpreter {
program: interpreter::ProgramInstance::new()?,
result: Vec::with_capacity(DEFAULT_RESULT_BUFFER),
})
impl From<runtime::Error> for vm::Error {
fn from(e: runtime::Error) -> Self {
vm::Error::Wasm(format!("Wasm runtime error: {:?}", e))
}
}
impl vm::Vm for WasmInterpreter {
fn exec(&mut self, params: ActionParams, ext: &mut vm::Ext) -> vm::Result<GasLeft> {
use parity_wasm::elements::Deserialize;
let (module, data) = parser::payload(&params, ext.schedule())?;
let code = params.code.expect("exec is only called on contract with code; qed");
let loaded_module = wasmi::Module::from_parity_wasm_module(module).map_err(Error)?;
trace!(target: "wasm", "Started wasm interpreter with code.len={:?}", code.len());
let instantiation_resolover = env::ImportResolver::with_limit(16);
let env_instance = self.program.module("env")
// prefer explicit panic here
.expect("Wasm program to contain env module");
let module_instance = wasmi::ModuleInstance::new(
&loaded_module,
&wasmi::ImportsBuilder::new().with_resolver("env", &instantiation_resolover)
).map_err(Error)?;
let env_memory = env_instance.memory(interpreter::ItemIndex::Internal(0))
// prefer explicit panic here
.expect("Linear memory to exist in wasm runtime");
let adjusted_gas = params.gas * U256::from(ext.schedule().wasm.opcodes_div) /
U256::from(ext.schedule().wasm.opcodes_mul);
if params.gas > ::std::u64::MAX.into() {
return Err(vm::Error::Wasm("Wasm interpreter cannot run contracts with gas >= 2^64".to_owned()));
if adjusted_gas > ::std::u64::MAX.into()
{
return Err(vm::Error::Wasm("Wasm interpreter cannot run contracts with gas (wasm adjusted) >= 2^64".to_owned()));
}
let mut runtime = Runtime::with_params(
ext,
env_memory,
DEFAULT_STACK_SPACE,
params.gas.low_u64(),
RuntimeContext {
address: params.address,
sender: params.sender,
origin: params.origin,
code_address: params.code_address,
value: params.value.value(),
},
&self.program,
);
let initial_memory = instantiation_resolover.memory_size().map_err(Error)?;
trace!(target: "wasm", "Contract requested {:?} pages of initial memory", initial_memory);
let (mut cursor, data_position) = match params.params_type {
vm::ParamsType::Embedded => {
let module_size = parity_wasm::peek_size(&*code);
(
::std::io::Cursor::new(&code[..module_size]),
module_size
)
},
vm::ParamsType::Separate => {
(::std::io::Cursor::new(&code[..]), 0)
},
};
let contract_module = wasm_utils::inject_gas_counter(
elements::Module::deserialize(
&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 = {
match params.params_type {
vm::ParamsType::Embedded => {
runtime.write_descriptor(
if data_position < code.len() { &code[data_position..] } else { &[] }
).map_err(Error)?
let (gas_left, result) = {
let mut runtime = Runtime::with_params(
ext,
instantiation_resolover.memory_ref(),
// cannot overflow, checked above
adjusted_gas.low_u64(),
data.to_vec(),
RuntimeContext {
address: params.address,
sender: params.sender,
origin: params.origin,
code_address: params.code_address,
value: params.value.value(),
},
vm::ParamsType::Separate => {
runtime.write_descriptor(&params.data.unwrap_or_default())
.map_err(Error)?
}
}
};
);
{
let execution_params = runtime.execution_params()
.add_argument(interpreter::RuntimeValue::I32(d_ptr.as_raw() as i32));
// cannot overflow if static_region < 2^16,
// initial_memory ∈ [0..2^32)
// total_charge <- static_region * 2^32 * 2^16
// total_charge ∈ [0..2^64) if static_region ∈ [0..2^16)
// qed
assert!(runtime.schedule().wasm.initial_mem < 1 << 16);
runtime.charge(|s| initial_memory as u64 * s.wasm.initial_mem as u64)?;
let module_instance = self.program.add_module("contract", contract_module, Some(&execution_params.externals))
.map_err(|err| {
trace!(target: "wasm", "Error adding contract module: {:?}", err);
vm::Error::from(Error(err))
})?;
let module_instance = module_instance.run_start(&mut runtime).map_err(Error)?;
match module_instance.execute_export("_call", execution_params) {
match module_instance.invoke_export("call", &[], &mut runtime) {
Ok(_) => { },
Err(interpreter::Error::User(UserTrap::Suicide)) => { },
Err(InterpreterError::Host(boxed)) => {
match boxed.downcast_ref::<runtime::Error>() {
None => {
return Err(vm::Error::Wasm("Invalid user error used in interpreter".to_owned()));
}
Some(runtime_err) => {
match *runtime_err {
runtime::Error::Suicide => {
// Suicide uses trap to break execution
}
ref any_err => {
trace!(target: "wasm", "Error executing contract: {:?}", boxed);
return Err(vm::Error::from(Error::from(InterpreterError::Host(Box::new(any_err.clone())))));
}
}
}
}
},
Err(err) => {
trace!(target: "wasm", "Error executing contract: {:?}", err);
return Err(vm::Error::from(Error(err)))
return Err(vm::Error::from(Error::from(err)))
}
}
}
(
runtime.gas_left().expect("Cannot fail since it was not updated since last charge"),
runtime.into_result(),
)
};
let result = result::WasmResult::new(d_ptr);
if result.peek_empty(&*runtime.memory()).map_err(|e| Error(e))? {
let gas_left =
U256::from(gas_left) * U256::from(ext.schedule().wasm.opcodes_mul)
/ U256::from(ext.schedule().wasm.opcodes_div);
if result.is_empty() {
trace!(target: "wasm", "Contract execution result is empty.");
Ok(GasLeft::Known(runtime.gas_left()?.into()))
Ok(GasLeft::Known(gas_left))
} else {
self.result.clear();
// todo: use memory views to avoid copy
self.result.extend(result.pop(&*runtime.memory()).map_err(|e| Error(e.into()))?);
let len = self.result.len();
let len = result.len();
Ok(GasLeft::NeedsReturn {
gas_left: runtime.gas_left().map_err(|e| Error(e.into()))?.into(),
gas_left: gas_left,
data: ReturnData::new(
::std::mem::replace(&mut self.result, Vec::with_capacity(DEFAULT_RESULT_BUFFER)),
result,
0,
len,
),

View File

@ -0,0 +1,89 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! ActionParams parser for wasm
use vm;
use wasm_utils::{self, rules};
use parity_wasm::elements::{self, Deserialize};
use parity_wasm::peek_size;
fn gas_rules(schedule: &vm::Schedule) -> rules::Set {
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
}).with_grow_cost(schedule.wasm.grow_mem)
}
/// Splits payload to code and data according to params.params_type, also
/// loads the module instance from payload and injects gas counter according
/// to schedule.
pub fn payload<'a>(params: &'a vm::ActionParams, schedule: &vm::Schedule)
-> Result<(elements::Module, &'a [u8]), vm::Error>
{
let code = match params.code {
Some(ref code) => &code[..],
None => { return Err(vm::Error::Wasm("Invalid wasm call".to_owned())); }
};
let (mut cursor, data_position) = match params.params_type {
vm::ParamsType::Embedded => {
let module_size = peek_size(&*code);
(
::std::io::Cursor::new(&code[..module_size]),
module_size
)
},
vm::ParamsType::Separate => {
(::std::io::Cursor::new(&code[..]), 0)
},
};
let deserialized_module = elements::Module::deserialize(
&mut cursor
).map_err(|err| {
vm::Error::Wasm(format!("Error deserializing contract code ({:?})", err))
})?;
if deserialized_module.memory_section().map_or(false, |ms| ms.entries().len() > 0) {
// According to WebAssembly spec, internal memory is hidden from embedder and should not
// be interacted with. So we disable this kind of modules at decoding level.
return Err(vm::Error::Wasm(format!("Malformed wasm module: internal memory")));
}
let contract_module = wasm_utils::inject_gas_counter(
deserialized_module,
&gas_rules(schedule),
);
let data = match params.params_type {
vm::ParamsType::Embedded => {
if data_position < code.len() { &code[data_position..] } else { &[] }
},
vm::ParamsType::Separate => {
match params.data {
Some(ref s) => &s[..],
None => &[]
}
}
};
Ok((contract_module, data))
}

View File

@ -1,58 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Wasm bound-checked ptr
use super::runtime::{InterpreterMemoryInstance, InterpreterError, UserTrap};
/// Bound-checked wrapper for webassembly memory
pub struct WasmPtr(u32);
/// Error in bound check
#[derive(Debug)]
pub enum Error {
AccessViolation,
}
impl From<u32> for WasmPtr {
fn from(raw: u32) -> Self {
WasmPtr(raw)
}
}
impl From<Error> for InterpreterError {
fn from(_e: Error) -> Self {
UserTrap::MemoryAccessViolation.into()
}
}
impl WasmPtr {
// todo: use memory view when they are on
/// Check memory range and return data with given length starting from the current pointer value
pub fn slice(&self, len: u32, mem: &InterpreterMemoryInstance) -> Result<Vec<u8>, Error> {
mem.get(self.0, len as usize).map_err(|_| Error::AccessViolation)
}
// todo: maybe 2gb limit can be enhanced
/// Convert i32 from wasm stack to the wrapped pointer
pub fn from_i32(raw_ptr: i32) -> Result<Self, Error> {
if raw_ptr < 0 { return Err(Error::AccessViolation); }
Ok(WasmPtr(raw_ptr as u32))
}
/// Return pointer raw value
pub fn as_raw(&self) -> u32 { self.0 }
}

View File

@ -1,49 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Wasm evm results helper
use byteorder::{LittleEndian, ByteOrder};
use super::ptr::WasmPtr;
use super::runtime::{InterpreterError, InterpreterMemoryInstance};
/// Wrapper for wasm contract call result
pub struct WasmResult {
ptr: WasmPtr,
}
impl WasmResult {
/// New call result from given ptr
pub fn new(descriptor_ptr: WasmPtr) -> WasmResult {
WasmResult { ptr: descriptor_ptr }
}
/// Check if the result contains any data
pub fn peek_empty(&self, mem: &InterpreterMemoryInstance) -> Result<bool, InterpreterError> {
let result_len = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[12..16]);
Ok(result_len == 0)
}
/// Consume the result ptr and return the actual data from wasm linear memory
pub fn pop(self, mem: &InterpreterMemoryInstance) -> Result<Vec<u8>, InterpreterError> {
let result_ptr = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[8..12]);
let result_len = LittleEndian::read_u32(&self.ptr.slice(16, mem)?[12..16]);
trace!(target: "wasm", "contract result: {} bytes at @{}", result_len, result_ptr);
Ok(mem.get(result_ptr, result_len as usize)?)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,34 @@ macro_rules! load_sample {
}
}
macro_rules! reqrep_test {
($name: expr, $input: expr) => {
reqrep_test!($name, $input, vm::EnvInfo::default(), HashMap::new())
};
($name: expr, $input: expr, $info: expr, $block_hashes: expr) => {
{
::ethcore_logger::init_log();
let code = load_sample!($name);
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some($input);
let mut fake_ext = FakeExt::new();
fake_ext.info = $info;
fake_ext.blockhashes = $block_hashes;
let mut interpreter = wasm_interpreter();
interpreter.exec(params, &mut fake_ext)
.map(|result| match result {
GasLeft::Known(_) => { panic!("Test is expected to return payload to check"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
})
}
};
}
fn test_finalize(res: Result<GasLeft, vm::Error>) -> Result<U256, vm::Error> {
match res {
Ok(GasLeft::Known(gas)) => Ok(gas),
@ -40,7 +68,7 @@ fn test_finalize(res: Result<GasLeft, vm::Error>) -> Result<U256, vm::Error> {
}
fn wasm_interpreter() -> WasmInterpreter {
WasmInterpreter::new().expect("wasm interpreter to create without errors")
WasmInterpreter
}
/// Empty contract does almost nothing except producing 1 (one) local node debug log message
@ -60,7 +88,7 @@ fn empty() {
test_finalize(interpreter.exec(params, &mut ext)).unwrap()
};
assert_eq!(gas_left, U256::from(96_678));
assert_eq!(gas_left, U256::from(98462));
}
// This test checks if the contract deserializes payload header properly.
@ -112,7 +140,7 @@ fn logger() {
U256::from(1_000_000_000),
"Logger sets 0x04 key to the trasferred value"
);
assert_eq!(gas_left, U256::from(15_860));
assert_eq!(gas_left, U256::from(17_578));
}
// This test checks if the contract can allocate memory and pass pointer to the result stream properly.
@ -147,7 +175,7 @@ fn identity() {
sender,
"Idenity test contract does not return the sender passed"
);
assert_eq!(gas_left, U256::from(96_540));
assert_eq!(gas_left, U256::from(98_408));
}
// Dispersion test sends byte array and expect the contract to 'disperse' the original elements with
@ -156,6 +184,8 @@ fn identity() {
// This also tests byte-perfect memory allocation and in/out ptr lifecycle.
#[test]
fn dispersion() {
::ethcore_logger::init_log();
let code = load_sample!("dispersion.wasm");
let mut params = ActionParams::default();
@ -175,12 +205,11 @@ fn dispersion() {
}
};
assert_eq!(
result,
vec![0u8, 0, 125, 11, 197, 7, 255, 8, 19, 0]
);
assert_eq!(gas_left, U256::from(96_116));
assert_eq!(gas_left, U256::from(93_972));
}
#[test]
@ -208,7 +237,7 @@ fn suicide_not() {
result,
vec![0u8]
);
assert_eq!(gas_left, U256::from(96_461));
assert_eq!(gas_left, U256::from(94_970));
}
#[test]
@ -240,7 +269,7 @@ fn suicide() {
};
assert!(ext.suicides.contains(&refund));
assert_eq!(gas_left, U256::from(96_429));
assert_eq!(gas_left, U256::from(94_933));
}
#[test]
@ -270,7 +299,7 @@ fn create() {
assert!(ext.calls.contains(
&FakeCall {
call_type: FakeCallType::Create,
gas: U256::from(62_545),
gas: U256::from(60_917),
sender_address: None,
receive_address: None,
value: Some(1_000_000_000.into()),
@ -278,7 +307,7 @@ fn create() {
code_address: None,
}
));
assert_eq!(gas_left, U256::from(62_538));
assert_eq!(gas_left, U256::from(60_903));
}
#[test]
@ -322,7 +351,7 @@ fn call_msg() {
}
));
assert_eq!(gas_left, U256::from(95_699));
assert_eq!(gas_left, U256::from(93_511));
}
#[test]
@ -367,7 +396,7 @@ fn call_code() {
// siphash result
let res = LittleEndian::read_u32(&result[..]);
assert_eq!(res, 4198595614);
assert_eq!(gas_left, U256::from(90_550));
assert_eq!(gas_left, U256::from(92_381));
}
#[test]
@ -415,7 +444,7 @@ fn call_static() {
let res = LittleEndian::read_u32(&result[..]);
assert_eq!(res, 317632590);
assert_eq!(gas_left, U256::from(90_550));
assert_eq!(gas_left, U256::from(92_381));
}
// Realloc test
@ -438,13 +467,37 @@ fn realloc() {
}
};
assert_eq!(result, vec![0u8; 2]);
assert_eq!(gas_left, U256::from(96_445));
assert_eq!(gas_left, U256::from(94_352));
}
#[test]
fn alloc() {
let code = load_sample!("alloc.wasm");
let mut params = ActionParams::default();
params.gas = U256::from(10_000_000);
params.code = Some(Arc::new(code));
params.data = Some(vec![0u8]);
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("alloc test should return payload"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(result, vec![5u8; 1024*450]);
assert_eq!(gas_left, U256::from(6_506_844));
}
// Tests that contract's ability to read from a storage
// Test prepopulates address into storage, than executes a contract which read that address from storage and write this address into result
#[test]
fn storage_read() {
::ethcore_logger::init_log();
let code = load_sample!("storage_read.wasm");
let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap();
@ -464,7 +517,7 @@ fn storage_read() {
};
assert_eq!(Address::from(&result[12..32]), address);
assert_eq!(gas_left, U256::from(96_463));
assert_eq!(gas_left, U256::from(98_298));
}
// Tests keccak calculation
@ -490,124 +543,7 @@ fn keccak() {
};
assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87"));
assert_eq!(gas_left, U256::from(81_067));
}
// memcpy test.
#[test]
fn memcpy() {
::ethcore_logger::init_log();
let code = load_sample!("mem.wasm");
let mut test_payload = Vec::with_capacity(8192);
for i in 0..8192 {
test_payload.push((i % 255) as u8);
}
let mut data = vec![0u8];
data.extend(&test_payload);
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some(data);
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("mem should return payload"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(result, test_payload);
assert_eq!(gas_left, U256::from(71_940));
}
// memmove test.
#[test]
fn memmove() {
::ethcore_logger::init_log();
let code = load_sample!("mem.wasm");
let mut test_payload = Vec::with_capacity(8192);
for i in 0..8192 {
test_payload.push((i % 255) as u8);
}
let mut data = vec![1u8];
data.extend(&test_payload);
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some(data);
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("mem should return payload"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(result, test_payload);
assert_eq!(gas_left, U256::from(71_940));
}
// memset test
#[test]
fn memset() {
::ethcore_logger::init_log();
let code = load_sample!("mem.wasm");
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some(vec![2u8, 228u8]);
let mut ext = FakeExt::new();
let (gas_left, result) = {
let mut interpreter = wasm_interpreter();
let result = interpreter.exec(params, &mut ext).expect("Interpreter to execute without any errors");
match result {
GasLeft::Known(_) => { panic!("mem should return payload"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
}
};
assert_eq!(result, vec![228u8; 8192]);
assert_eq!(gas_left, U256::from(71_921));
}
macro_rules! reqrep_test {
($name: expr, $input: expr) => {
reqrep_test!($name, $input, vm::EnvInfo::default(), HashMap::new())
};
($name: expr, $input: expr, $info: expr, $block_hashes: expr) => {
{
::ethcore_logger::init_log();
let code = load_sample!($name);
let mut params = ActionParams::default();
params.gas = U256::from(100_000);
params.code = Some(Arc::new(code));
params.data = Some($input);
let mut fake_ext = FakeExt::new();
fake_ext.info = $info;
fake_ext.blockhashes = $block_hashes;
let mut interpreter = wasm_interpreter();
interpreter.exec(params, &mut fake_ext)
.map(|result| match result {
GasLeft::Known(_) => { panic!("Test is expected to return payload to check"); },
GasLeft::NeedsReturn { gas_left: gas, data: result, apply_state: _apply } => (gas, result.to_vec()),
})
}
};
assert_eq!(gas_left, U256::from(84_223));
}
// math_* tests check the ability of wasm contract to perform big integer operations
@ -636,7 +572,7 @@ fn math_add() {
U256::from_dec_str("1888888888888888888888888888887").unwrap(),
(&result[..]).into()
);
assert_eq!(gas_left, U256::from(95_384));
assert_eq!(gas_left, U256::from(93_818));
}
// multiplication
@ -658,7 +594,7 @@ fn math_mul() {
U256::from_dec_str("888888888888888888888888888887111111111111111111111111111112").unwrap(),
(&result[..]).into()
);
assert_eq!(gas_left, U256::from(94_374));
assert_eq!(gas_left, U256::from(93_304));
}
// subtraction
@ -680,7 +616,7 @@ fn math_sub() {
U256::from_dec_str("111111111111111111111111111111").unwrap(),
(&result[..]).into()
);
assert_eq!(gas_left, U256::from(95_372));
assert_eq!(gas_left, U256::from(93_831));
}
// subtraction with overflow
@ -722,7 +658,54 @@ fn math_div() {
U256::from_dec_str("1125000").unwrap(),
(&result[..]).into()
);
assert_eq!(gas_left, U256::from(88_356));
assert_eq!(gas_left, U256::from(90_607));
}
#[test]
fn storage_metering() {
::ethcore_logger::init_log();
// #1
let mut ext = FakeExt::new();
let code = Arc::new(load_sample!("setter.wasm"));
let address: Address = "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6".parse().unwrap();
let mut params = ActionParams::default();
params.address = address.clone();
params.gas = U256::from(100_000);
params.code = Some(code.clone());
params.data = Some(vec![
0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
]);
let gas_left = {
let mut interpreter = wasm_interpreter();
test_finalize(interpreter.exec(params, &mut ext)).unwrap()
};
// 0 -> not 0
assert_eq!(gas_left, U256::from(74_410));
// #2
let mut params = ActionParams::default();
params.address = address.clone();
params.gas = U256::from(100_000);
params.code = Some(code.clone());
params.data = Some(vec![
0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
0x6b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
]);
let gas_left = {
let mut interpreter = wasm_interpreter();
test_finalize(interpreter.exec(params, &mut ext)).unwrap()
};
// not 0 -> not 0
assert_eq!(gas_left, U256::from(89_410));
}
// This test checks the ability of wasm contract to invoke
@ -810,7 +793,7 @@ fn externs() {
"Gas limit requested and returned does not match"
);
assert_eq!(gas_left, U256::from(95_321));
assert_eq!(gas_left, U256::from(92_089));
}
#[test]
@ -836,7 +819,7 @@ fn embedded_keccak() {
};
assert_eq!(H256::from_slice(&result), H256::from("68371d7e884c168ae2022c82bd837d51837718a7f7dfb7aa3f753074a35e1d87"));
assert_eq!(gas_left, U256::from(81_067));
assert_eq!(gas_left, U256::from(84_223));
}
/// This test checks the correctness of log extern
@ -871,5 +854,5 @@ fn events() {
assert_eq!(&log_entry.data, b"gnihtemos");
assert_eq!(&result, b"gnihtemos");
assert_eq!(gas_left, U256::from(79_206));
assert_eq!(gas_left, U256::from(81_235));
}

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::{fs, io};
use std::io::Write;
use std::path::{PathBuf, Path};
use std::collections::HashMap;
use time;
@ -152,31 +153,39 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
)
}
/// insert account with given file name
pub fn insert_with_filename(&self, account: SafeAccount, filename: String) -> Result<SafeAccount, Error> {
/// insert account with given filename. if the filename is a duplicate of any stored account and dedup is set to
/// true, a random suffix is appended to the filename.
pub fn insert_with_filename(&self, account: SafeAccount, mut filename: String, dedup: bool) -> Result<SafeAccount, Error> {
// path to keyfile
let mut keyfile_path = self.path.join(filename.as_str());
// check for duplicate filename and append random suffix
if dedup && keyfile_path.exists() {
let suffix = ::random::random_string(4);
filename.push_str(&format!("-{}", suffix));
keyfile_path.set_file_name(&filename);
}
// update account filename
let original_account = account.clone();
let mut account = account;
account.filename = Some(filename.clone());
account.filename = Some(filename);
{
// Path to keyfile
let mut keyfile_path = self.path.clone();
keyfile_path.push(filename.as_str());
// save the file
let mut file = fs::File::create(&keyfile_path)?;
if let Err(err) = self.key_manager.write(original_account, &mut file).map_err(|e| Error::Custom(format!("{:?}", e))) {
drop(file);
fs::remove_file(keyfile_path).expect("Expected to remove recently created file");
return Err(err);
}
// write key content
self.key_manager.write(original_account, &mut file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
file.flush()?;
if let Err(_) = restrict_permissions_to_owner(keyfile_path.as_path()) {
drop(file);
fs::remove_file(keyfile_path).expect("Expected to remove recently created file");
return Err(Error::Io(io::Error::last_os_error()));
}
file.sync_all()?;
}
Ok(account)
@ -199,17 +208,13 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
// Disk store handles updates correctly iff filename is the same
self.insert(account)
let filename = account_filename(&account);
self.insert_with_filename(account, filename, false)
}
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
// build file path
let filename = account.filename.as_ref().cloned().unwrap_or_else(|| {
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
});
self.insert_with_filename(account, filename)
let filename = account_filename(&account);
self.insert_with_filename(account, filename, true)
}
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
@ -285,6 +290,14 @@ impl KeyFileManager for DiskKeyFileManager {
}
}
fn account_filename(account: &SafeAccount) -> String {
// build file path
account.filename.clone().unwrap_or_else(|| {
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
})
}
#[cfg(test)]
mod test {
extern crate tempdir;
@ -316,6 +329,38 @@ mod test {
let _ = fs::remove_dir_all(dir);
}
#[test]
fn should_handle_duplicate_filenames() {
// given
let mut dir = env::temp_dir();
dir.push("ethstore_should_handle_duplicate_filenames");
let keypair = Random.generate().unwrap();
let password = "hello world";
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
// when
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
let filename = "test".to_string();
let dedup = true;
directory.insert_with_filename(account.clone(), "foo".to_string(), dedup).unwrap();
let file1 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
let file2 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
let file3 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
// then
// the first file should have the original names
assert_eq!(file1, filename);
// the following duplicate files should have a suffix appended
assert!(file2 != file3);
assert_eq!(file2.len(), filename.len() + 5);
assert_eq!(file3.len(), filename.len() + 5);
// cleanup
let _ = fs::remove_dir_all(dir);
}
#[test]
fn should_manage_vaults() {
// given

View File

@ -106,7 +106,7 @@ impl VaultDiskDirectory {
fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> {
for account in self.load()? {
let filename = account.filename.clone().expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
vault.insert_with_filename(account, filename)?;
vault.insert_with_filename(account, filename, true)?;
}
Ok(())

View File

@ -166,9 +166,14 @@ impl SecretStore for EthStore {
self.insert_account(vault, keypair.secret().clone(), password)
}
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error> {
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str, gen_id: bool) -> Result<StoreAccountRef, Error> {
let json_keyfile = json::KeyFile::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))?;
let mut safe_account = SafeAccount::from_file(json_keyfile, None);
if gen_id {
safe_account.id = Random::random();
}
let secret = safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword)?;
safe_account.address = KeyPair::from_secret(secret)?.address();
self.store.import(vault, safe_account)

View File

@ -116,7 +116,7 @@ pub trait SecretStore: SimpleSecretStore {
/// Imports presale wallet
fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
/// Imports existing JSON wallet
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str, gen_id: bool) -> Result<StoreAccountRef, Error>;
/// Copies account between stores and vaults.
fn copy_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>;
/// Checks if password matches given account.

View File

@ -95,7 +95,7 @@ impl ParityAccounts for ParityAccountsClient {
let store = self.account_provider()?;
store.import_presale(json.as_bytes(), &pass)
.or_else(|_| store.import_wallet(json.as_bytes(), &pass))
.or_else(|_| store.import_wallet(json.as_bytes(), &pass, true))
.map(Into::into)
.map_err(|e| errors::account("Could not create account.", e))
}

View File

@ -480,7 +480,7 @@ fn should_export_account() {
// given
let tester = setup();
let wallet = r#"{"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","name":"parity-export-test","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}"}"#;
tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test").unwrap();
tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test", false).unwrap();
let accounts = tester.accounts.accounts().unwrap();
assert_eq!(accounts.len(), 1);
@ -501,6 +501,26 @@ fn should_export_account() {
assert_eq!(result, Some(response.into()));
}
#[test]
fn should_import_wallet() {
let tester = setup();
let id = "6a186c80-7797-cff2-bc2e-7c1d6a6cc76e";
let request = r#"{"jsonrpc":"2.0","method":"parity_newAccountFromWallet","params":["{\"id\":\"<ID>\",\"version\":3,\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"478736fb55872c1baf01b27b1998c90b\"},\"ciphertext\":\"fe5a63cc0055d7b0b3b57886f930ad9b63f48950d1348145d95996c41e05f4e0\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":10240,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"658436d6738a19731149a98744e5cf02c8d5aa1f8e80c1a43cc9351c70a984e4\"},\"mac\":\"c7384b26ecf25539d942030230062af9b69de5766cbcc4690bffce1536644631\"},\"address\":\"00bac56a8a27232baa044c03f43bf3648c961735\",\"name\":\"hello world\",\"meta\":\"{}\"}", "himom"],"id":1}"#;
let request = request.replace("<ID>", id);
let response = r#"{"jsonrpc":"2.0","result":"0x00bac56a8a27232baa044c03f43bf3648c961735","id":1}"#;
let res = tester.io.handle_request_sync(&request).unwrap();
assert_eq!(res, response);
let account_meta = tester.accounts.account_meta("0x00bac56a8a27232baa044c03f43bf3648c961735".into()).unwrap();
let account_uuid: String = account_meta.uuid.unwrap().into();
// the RPC should import the account with a new id
assert!(account_uuid != id);
}
#[test]
fn should_sign_message() {
let tester = setup();

View File

@ -35,6 +35,7 @@
use std::collections::{HashMap, HashSet};
use std::mem;
use std::sync::Arc;
use std::time::{Instant, Duration};
use ethcore::encoded;
use light::client::{AsLightClient, LightChainClient};
@ -58,6 +59,13 @@ mod sync_round;
#[cfg(test)]
mod tests;
// Base number of milliseconds for the header request timeout.
const REQ_TIMEOUT_MILLISECS_BASE: u64 = 7000;
// Additional number of milliseconds for each requested header.
// If we request N headers, then the timeout will be:
// REQ_TIMEOUT_MILLISECS_BASE + N * REQ_TIMEOUT_MILLISECS_PER_HEADER
const REQ_TIMEOUT_MILLISECS_PER_HEADER: u64 = 10;
/// Peer chain info.
#[derive(Debug, Clone, PartialEq, Eq)]
struct ChainInfo {
@ -225,12 +233,18 @@ pub struct LightSync<L: AsLightClient> {
start_block_number: u64,
best_seen: Mutex<Option<ChainInfo>>, // best seen block on the network.
peers: RwLock<HashMap<PeerId, Mutex<Peer>>>, // peers which are relevant to synchronization.
pending_reqs: Mutex<HashSet<ReqId>>, // requests from this handler.
pending_reqs: Mutex<HashMap<ReqId, PendingReq>>, // requests from this handler
client: Arc<L>,
rng: Mutex<OsRng>,
state: Mutex<SyncState>,
}
#[derive(Debug, Clone)]
struct PendingReq {
started: Instant,
timeout: Duration,
}
impl<L: AsLightClient + Send + Sync> Handler for LightSync<L> {
fn on_connect(
&self,
@ -353,7 +367,7 @@ impl<L: AsLightClient + Send + Sync> Handler for LightSync<L> {
return
}
if !self.pending_reqs.lock().remove(&req_id) {
if self.pending_reqs.lock().remove(&req_id).is_none() {
return
}
@ -412,7 +426,7 @@ impl<L: AsLightClient> LightSync<L> {
*state = SyncState::AncestorSearch(AncestorSearch::begin(chain_info.best_block_number));
}
// handles request dispatch, block import, and state machine transitions.
// handles request dispatch, block import, state machine transitions, and timeouts.
fn maintain_sync(&self, ctx: &BasicContext) {
const DRAIN_AMOUNT: usize = 128;
@ -502,6 +516,32 @@ impl<L: AsLightClient> LightSync<L> {
}
}
// handle requests timeouts
{
let mut pending_reqs = self.pending_reqs.lock();
let mut unfulfilled = Vec::new();
for (req_id, info) in pending_reqs.iter() {
if info.started.elapsed() >= info.timeout {
debug!(target: "sync", "{} timed out", req_id);
unfulfilled.push(req_id.clone());
}
}
if !unfulfilled.is_empty() {
for unfulfilled in unfulfilled.iter() {
pending_reqs.remove(unfulfilled);
}
drop(pending_reqs);
*state = match mem::replace(&mut *state, SyncState::Idle) {
SyncState::Idle => SyncState::Idle,
SyncState::AncestorSearch(search) =>
SyncState::AncestorSearch(search.requests_abandoned(&unfulfilled)),
SyncState::Rounds(round) => SyncState::Rounds(round.requests_abandoned(&unfulfilled)),
};
}
}
// allow dispatching of requests.
{
let peers = self.peers.read();
@ -535,7 +575,12 @@ impl<L: AsLightClient> LightSync<L> {
if requested_from.contains(peer) { continue }
match ctx.request_from(*peer, request.clone()) {
Ok(id) => {
self.pending_reqs.lock().insert(id.clone());
let timeout_ms = REQ_TIMEOUT_MILLISECS_BASE +
req.max * REQ_TIMEOUT_MILLISECS_PER_HEADER;
self.pending_reqs.lock().insert(id.clone(), PendingReq {
started: Instant::now(),
timeout: Duration::from_millis(timeout_ms),
});
requested_from.insert(peer.clone());
return Some(id)
@ -571,7 +616,7 @@ impl<L: AsLightClient> LightSync<L> {
start_block_number: client.as_light_client().chain_info().best_block_number,
best_seen: Mutex::new(None),
peers: RwLock::new(HashMap::new()),
pending_reqs: Mutex::new(HashSet::new()),
pending_reqs: Mutex::new(HashMap::new()),
client: client,
rng: Mutex::new(OsRng::new()?),
state: Mutex::new(SyncState::Idle),

View File

@ -179,7 +179,7 @@ impl Fetcher {
};
trace!(target: "sync", "Received response for subchain ({} -> {})",
request.subchain_parent.0 + 1, request.subchain_end.0);
request.subchain_parent.0, request.subchain_end.0);
let headers = ctx.data();
@ -241,6 +241,8 @@ impl Fetcher {
}
fn requests_abandoned(mut self, abandoned: &[ReqId]) -> SyncRound {
trace!(target: "sync", "Abandonned requests {:?}", abandoned);
for abandoned in abandoned {
match self.pending.remove(abandoned) {
None => {},
@ -258,12 +260,14 @@ impl Fetcher {
while let Some(pending_req) = self.requests.pop() {
match dispatcher(pending_req.headers_request.clone()) {
Some(req_id) => {
trace!(target: "sync", "Assigned request for subchain ({} -> {})",
pending_req.subchain_parent.0 + 1, pending_req.subchain_end.0);
trace!(target: "sync", "Assigned request {} for subchain ({} -> {})",
req_id, pending_req.subchain_parent.0, pending_req.subchain_end.0);
self.pending.insert(req_id, pending_req);
}
None => {
trace!(target: "sync", "Failed to assign request for subchain ({} -> {})",
pending_req.subchain_parent.0, pending_req.subchain_end.0);
self.requests.push(pending_req);
break;
}