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:
parent
af70a681d5
commit
b60511e3d2
34
Cargo.lock
generated
34
Cargo.lock
generated
@ -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>"
|
||||
|
@ -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,
|
||||
|
@ -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
|
@ -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());
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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" }
|
@ -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)]
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
@ -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(¶ms, 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(¶ms.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,
|
||||
),
|
||||
|
89
ethcore/wasm/src/parser.rs
Normal file
89
ethcore/wasm/src/parser.rs
Normal 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))
|
||||
}
|
@ -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 }
|
||||
}
|
@ -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
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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(())
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user