From a257827f27d1f078e844a53abdf2c8c1b04dd5d8 Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Tue, 9 Jan 2018 13:55:10 +0100 Subject: [PATCH] backports to beta (#7434) * Merge pull request #7368 from paritytech/td-future-blocks Wait for future blocks in AuRa * Fix tracing failed calls. * Problem: sending any Whisper message fails The error is "PoW too low to compete with other messages" This has been previously reported in #7144 Solution: prevent the move semantics The source of the error is in PoolHandle.relay implementation for NetPoolHandle. Because of the move semantics, `res` variable is in fact copied (as it implements Copy) into the closure and for that reason, the returned result is always `false. * Merge pull request #7433 from paritytech/td-strict-config Strict config parsing * Problem: AuRa's unsafeties around step duration (#7282) Firstly, `Step.duration_remaining` casts it to u32, unnecesarily limiting it to 2^32. While theoretically this is "good enough" (at 3 seconds steps it provides room for a little over 400 years), it is still a lossy way to calculate the remaining time until the next step. Secondly, step duration might be zero, triggering division by zero in `Step.calibrate` Solution: rework the code around the fact that duration is typically in single digits and never grows, hence, it can be represented by a much narrower range (u16) and this highlights the fact that multiplying u64 by u16 will only result in an overflow in even further future, at which point we should panic informatively (if anybody's still around) Similarly, panic when it is detected that incrementing the step counter wrapped around on the overflow of usize. As for the division by zero, prevent it by making zero an invalid value for step duration. This will make AuRa log the constraint mismatch and panic (after all, what purpose would zero step duration serve? it makes no sense within the definition of the protocol, as finality can only be achieved as per the specification if messages are received within the step duration, which would violate the speed of light and other physical laws in this case). * Merge pull request #7437 from paritytech/a5-chains-expanse Remove expanse chain * Expanse Byzantium update w/ correct metropolis difficulty increment divisor (#7463) * Byzantium Update for Expanse Here the changes go. Hope I didnt miss anything. * expip2 changes - update duration limit * Fix missing EXPIP-2 fields * Format numbers as hex * Fix compilation errors * Group expanse chain spec fields together * Set metropolisDifficultyIncrementDivisor for Expanse * Revert #7437 * Add Expanse block 900_000 hash checkpoint * Advance AuRa step as far as we can and prevent invalid blocks. (#7451) * Advance AuRa step as far as we can. * Wait for future blocks. * fixed panic when io is not available for export block, closes #7486 (#7495) * Update Parity Mainnet Bootnodes (#7476) * Update Parity Mainnet Bootnodes * Replace the Azure HDD bootnodes with the new ones :) * Use https connection (#7503) Use https when connecting to etherscan.io API for price-info * Expose default gas price percentile configuration in CLI (#7497) * Expose gas price percentile. * Fix light eth_call. * fix gas_price in light client --- ethcore/Cargo.toml | 2 +- ethcore/evm/Cargo.toml | 2 + ethcore/evm/src/interpreter/informant.rs | 10 +- ethcore/evm/src/lib.rs | 1 + ethcore/res/ethereum/expanse.json | 23 +- ethcore/res/ethereum/foundation.json | 10 +- ethcore/src/engines/authority_round/mod.rs | 196 ++++++++++++++---- ethcore/src/engines/validator_set/contract.rs | 18 +- ethcore/src/error.rs | 3 + ethcore/src/ethereum/ethash.rs | 17 +- ethcore/src/executive.rs | 85 +++++++- ethcore/src/tests/helpers.rs | 2 + ethcore/src/verification/queue/mod.rs | 2 +- ethcore/src/verification/verification.rs | 29 ++- json/src/spec/authority_round.rs | 2 +- json/src/spec/ethash.rs | 12 ++ parity/blockchain.rs | 8 +- parity/cli/mod.rs | 41 +++- parity/cli/tests/config.invalid4.toml | 2 + parity/configuration.rs | 2 + parity/rpc_apis.rs | 10 +- parity/run.rs | 3 + parity/whisper.rs | 2 +- price-info/src/lib.rs | 4 +- rpc/src/v1/helpers/dispatch.rs | 25 ++- rpc/src/v1/helpers/fake_sign.rs | 2 +- rpc/src/v1/helpers/light_fetch.rs | 7 +- rpc/src/v1/impls/eth.rs | 5 +- rpc/src/v1/impls/eth_pubsub.rs | 4 +- rpc/src/v1/impls/light/eth.rs | 19 +- rpc/src/v1/impls/light/parity.rs | 6 +- rpc/src/v1/tests/eth.rs | 2 +- rpc/src/v1/tests/mocked/eth.rs | 3 +- rpc/src/v1/tests/mocked/personal.rs | 2 +- rpc/src/v1/tests/mocked/signer.rs | 2 +- rpc/src/v1/tests/mocked/signing.rs | 2 +- util/stats/src/lib.rs | 26 ++- 37 files changed, 483 insertions(+), 108 deletions(-) create mode 100644 parity/cli/tests/config.invalid4.toml diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index a12083b56d1..408f2ae134e 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -83,7 +83,7 @@ native-contracts = { path = "native_contracts", features = ["test_contracts"] } [features] jit = ["evm/jit"] evm-debug = ["slow-blocks"] -evm-debug-tests = ["evm-debug"] +evm-debug-tests = ["evm-debug", "evm/evm-debug-tests"] slow-blocks = [] # Use SLOW_TX_DURATION="50" (compile time!) to track transactions over 50ms json-tests = [] test-heavy = [] diff --git a/ethcore/evm/Cargo.toml b/ethcore/evm/Cargo.toml index 46260c08c6b..fa33255a6a4 100644 --- a/ethcore/evm/Cargo.toml +++ b/ethcore/evm/Cargo.toml @@ -26,3 +26,5 @@ rustc-hex = "1.0" [features] jit = ["evmjit"] +evm-debug = [] +evm-debug-tests = ["evm-debug"] diff --git a/ethcore/evm/src/interpreter/informant.rs b/ethcore/evm/src/interpreter/informant.rs index ac250548982..f07d11ff7aa 100644 --- a/ethcore/evm/src/interpreter/informant.rs +++ b/ethcore/evm/src/interpreter/informant.rs @@ -39,12 +39,12 @@ mod inner { use std::collections::HashMap; use std::time::{Instant, Duration}; - use evm::interpreter::stack::Stack; - use evm::instructions::{Instruction, InstructionInfo, INSTRUCTIONS}; - use evm::{CostType}; - use bigint::prelude::U256; + use interpreter::stack::Stack; + use instructions::{Instruction, InstructionInfo, INSTRUCTIONS}; + use CostType; + macro_rules! evm_debug { ($x: expr) => { $x @@ -110,7 +110,7 @@ mod inner { } pub fn after_instruction(&mut self, instruction: Instruction) { - let mut stats = self.stats.entry(instruction).or_insert_with(|| Stats::default()); + let stats = self.stats.entry(instruction).or_insert_with(|| Stats::default()); let took = self.last_instruction.elapsed(); stats.note(took); } diff --git a/ethcore/evm/src/lib.rs b/ethcore/evm/src/lib.rs index 8c48ad86b7b..1600c1570f0 100644 --- a/ethcore/evm/src/lib.rs +++ b/ethcore/evm/src/lib.rs @@ -33,6 +33,7 @@ extern crate hash; #[macro_use] extern crate lazy_static; +#[cfg_attr(feature = "evm-debug", macro_use)] extern crate log; #[cfg(feature = "jit")] diff --git a/ethcore/res/ethereum/expanse.json b/ethcore/res/ethereum/expanse.json index ec7e737ead6..b9b734e313d 100644 --- a/ethcore/res/ethereum/expanse.json +++ b/ethcore/res/ethereum/expanse.json @@ -6,7 +6,7 @@ "params": { "minimumDifficulty": "0x020000", "difficultyBoundDivisor": "0x0800", - "difficultyIncrementDivisor": "60", + "difficultyIncrementDivisor": "0x3C", "durationLimit": "0x3C", "blockReward": "0x6f05b59d3b200000", "homesteadTransition": "0x30d40", @@ -16,7 +16,13 @@ "eip150Transition": "0x927C0", "eip160Transition": "0x927C0", "eip161abcTransition": "0x927C0", - "eip161dTransition": "0x927C0" + "eip161dTransition": "0x927C0", + "eip100bTransition": "0xC3500", + "metropolisDifficultyIncrementDivisor": "0x1E", + "eip649Transition": "0xC3500", + "eip649Reward": "0x3782DACE9D900000", + "expip2Transition": "0xC3500", + "expip2DurationLimit": "0x1E" } } }, @@ -28,10 +34,16 @@ "minGasLimit": "0x1388", "networkID": "0x1", "chainID": "0x2", + "forkBlock": "0xDBBA0", + "forkCanonHash": "0x8e7bed51e24f5174090408664ac476b90b5e1199a947af7442f1ac88263fc8c7", "subprotocolName": "exp", "eip98Transition": "0x7fffffffffffff", "eip86Transition": "0x7fffffffffffff", - "eip155Transition": "0x927C0" + "eip155Transition": "0x927C0", + "eip140Transition": "0xC3500", + "eip211Transition": "0xC3500", + "eip214Transition": "0xC3500", + "eip658Transition": "0xC3500" }, "genesis": { "seal": { @@ -53,7 +65,6 @@ "enode://96d3919b903e7f5ad59ac2f73c43be9172d9d27e2771355db03fd194732b795829a31fe2ea6de109d0804786c39a807e155f065b4b94c6fce167becd0ac02383@45.55.22.34:42786", "enode://5f6c625bf287e3c08aad568de42d868781e961cbda805c8397cfb7be97e229419bef9a5a25a75f97632787106bba8a7caf9060fab3887ad2cfbeb182ab0f433f@46.101.182.53:42786", "enode://d33a8d4c2c38a08971ed975b750f21d54c927c0bf7415931e214465a8d01651ecffe4401e1db913f398383381413c78105656d665d83f385244ab302d6138414@128.199.183.48:42786", - "enode://df872f81e25f72356152b44cab662caf1f2e57c3a156ecd20e9ac9246272af68a2031b4239a0bc831f2c6ab34733a041464d46b3ea36dce88d6c11714446e06b@178.62.208.109:42786", "enode://f6f0d6b9b7d02ec9e8e4a16e38675f3621ea5e69860c739a65c1597ca28aefb3cec7a6d84e471ac927d42a1b64c1cbdefad75e7ce8872d57548ddcece20afdd1@159.203.64.95:42786" ], "accounts": { @@ -61,6 +72,10 @@ "0000000000000000000000000000000000000002": { "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, "0000000000000000000000000000000000000003": { "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, "0000000000000000000000000000000000000004": { "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000005": { "builtin": { "name": "modexp", "activate_at": "0xC3500", "pricing": { "modexp": { "divisor": 20 } } } }, + "0000000000000000000000000000000000000006": { "builtin": { "name": "alt_bn128_add", "activate_at": "0xC3500", "pricing": { "linear": { "base": 500, "word": 0 } } } }, + "0000000000000000000000000000000000000007": { "builtin": { "name": "alt_bn128_mul", "activate_at": "0xC3500", "pricing": { "linear": { "base": 40000, "word": 0 } } } }, + "0000000000000000000000000000000000000008": { "builtin": { "name": "alt_bn128_pairing", "activate_at": "0xC3500", "pricing": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 } } } }, "bb94f0ceb32257275b2a7a9c094c13e469b4563e": { "balance": "10000000000000000000000000" }, diff --git a/ethcore/res/ethereum/foundation.json b/ethcore/res/ethereum/foundation.json index 95f330cfd33..09d04599839 100644 --- a/ethcore/res/ethereum/foundation.json +++ b/ethcore/res/ethereum/foundation.json @@ -176,12 +176,14 @@ "enode://6a868ced2dec399c53f730261173638a93a40214cf299ccf4d42a76e3fa54701db410669e8006347a4b3a74fa090bb35af0320e4bc8d04cf5b7f582b1db285f5@163.172.131.191:30303", "enode://66a483383882a518fcc59db6c017f9cd13c71261f13c8d7e67ed43adbbc82a932d88d2291f59be577e9425181fc08828dc916fdd053af935a9491edf9d6006ba@212.47.247.103:30303", "enode://cd6611461840543d5b9c56fbf088736154c699c43973b3a1a32390cf27106f87e58a818a606ccb05f3866de95a4fe860786fea71bf891ea95f234480d3022aa3@163.172.157.114:30303", + "enode://78b094cb27ceeecbe311bc278f4fde8b9a265db42d268c88484c94d7a2d19b82a1bd22dfd6c2bd4d90f9b05e6d42255e6eb85de15f73848ff82ed0be9cdf5202@52.233.198.218:30303", + "enode://00526537cb7e1aa6cf49714f0635fd0f608904d8d0693b949eea2dcdfdb0abbe4c794003a5fe57aa662d0a9215e8dfa4d2deb6ef0101c5e185e2617721813d43@40.65.122.44:30303", + "enode://4a456b4b6e6ee1f51389763e51b80fe04782c762445d96c32a96ebd34bd9178c1894924d5101123eacfd4f0fc4da25b5e1ee7f18832ac0bf4c6d6ac81442d698@40.71.6.49:3030", + "enode://68f85e7403976aa92318eff804cbe9bc988e0f5230d9d07ae4def030cbae16603262638e272d19875b7e5c54e296ba88ab6ec6e98face9e2537346c4dce78882@52.243.47.211:30303", + "enode://dc72806c3aa8fda207c8c018aba8d6cf143728b3628b6ded8d5e8cdeb8aa05cbd53f710ecd014c9a8f0d1e98f2874bff8afb15a229202f510a9c0258d1f6d109@159.203.210.80:30303", "enode://5a62f19d35c0da8b576c9414568c728d4744e6e9d436c0f9db27456400011414f515871f13a6b8e0468534b5116cfe765d7630f680f1707a38467940a9f62511@45.55.33.62:30303", "enode://605e04a43b1156966b3a3b66b980c87b7f18522f7f712035f84576016be909a2798a438b2b17b1a8c58db314d88539a77419ca4be36148c086900fba487c9d39@188.166.255.12:30303", - "enode://dc72806c3aa8fda207c8c018aba8d6cf143728b3628b6ded8d5e8cdeb8aa05cbd53f710ecd014c9a8f0d1e98f2874bff8afb15a229202f510a9c0258d1f6d109@159.203.210.80:30303", - "enode://aafde2e81e035f417019a80f5342d1cd0e5bce97f83230fc57e1abbb3a9a5d6fb751446040c67261ed422324ffb69214567e181bb4ac0cc6e817451be0eaad1e@52.178.74.216:30303", - "enode://460e54d7e9a361d326a9e503b3879c6a1075e1bfb7ea919b512ea1fe841e65f82c5f87af028f14a7825be1c1260825d5326b93b43a5bc72e3214a99e0c4c7bd4@52.230.6.166:30303", - "enode://28faaf6b2e86694d8978b8e6986e7813951d7bd25201116fa77de893aabedd2a4a8d5832776905b4c3e616320506516d08239d82aeef4355f6878c3a701a6059@40.71.19.172:30303", + "enode://1d1f7bcb159d308eb2f3d5e32dc5f8786d714ec696bb2f7e3d982f9bcd04c938c139432f13aadcaf5128304a8005e8606aebf5eebd9ec192a1471c13b5e31d49@138.201.223.35:30303", "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://3f1d12044546b76342d59d4a05532c14b85aa669704bfe1f864fe079415aa2c02d743e03218e57a33fb94523adb54032871a6c51b2cc5514cb7c7e35b3ed0a99@13.93.211.84:30303", "enode://78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d@191.235.84.50:30303", diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 48638eda249..75df50e43e7 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -51,8 +51,12 @@ mod finality; /// `AuthorityRound` params. pub struct AuthorityRoundParams { - /// Time to wait before next block or authority switching. - pub step_duration: Duration, + /// Time to wait before next block or authority switching, + /// in seconds. + /// + /// Deliberately typed as u16 as too high of a value leads + /// to slow block issuance. + pub step_duration: u16, /// Starting step, pub start_step: Option, /// Valid validators. @@ -71,10 +75,17 @@ pub struct AuthorityRoundParams { pub maximum_uncle_count: usize, } +const U16_MAX: usize = ::std::u16::MAX as usize; + impl From for AuthorityRoundParams { fn from(p: ethjson::spec::AuthorityRoundParams) -> Self { + let mut step_duration_usize: usize = p.step_duration.into(); + if step_duration_usize > U16_MAX { + step_duration_usize = U16_MAX; + warn!(target: "engine", "step_duration is too high ({}), setting it to {}", step_duration_usize, U16_MAX); + } AuthorityRoundParams { - step_duration: Duration::from_secs(p.step_duration.into()), + step_duration: step_duration_usize as u16, validators: new_validator_set(p.validators), start_step: p.start_step.map(Into::into), validate_score_transition: p.validate_score_transition.map_or(0, Into::into), @@ -92,36 +103,74 @@ impl From for AuthorityRoundParams { struct Step { calibrate: bool, // whether calibration is enabled. inner: AtomicUsize, - duration: Duration, + duration: u16, } impl Step { fn load(&self) -> usize { self.inner.load(AtomicOrdering::SeqCst) } fn duration_remaining(&self) -> Duration { let now = unix_now(); - let step_end = self.duration * (self.load() as u32 + 1); - if step_end > now { - step_end - now - } else { - Duration::from_secs(0) + let expected_seconds = (self.load() as u64) + .checked_add(1) + .and_then(|ctr| ctr.checked_mul(self.duration as u64)) + .map(Duration::from_secs); + + match expected_seconds { + Some(step_end) if step_end > now => step_end - now, + Some(_) => Duration::from_secs(0), + None => { + let ctr = self.load(); + error!(target: "engine", "Step counter is too high: {}, aborting", ctr); + panic!("step counter is too high: {}", ctr) + }, } + } + fn increment(&self) { - self.inner.fetch_add(1, AtomicOrdering::SeqCst); + use std::usize; + // fetch_add won't panic on overflow but will rather wrap + // around, leading to zero as the step counter, which might + // lead to unexpected situations, so it's better to shut down. + if self.inner.fetch_add(1, AtomicOrdering::SeqCst) == usize::MAX { + error!(target: "engine", "Step counter is too high: {}, aborting", usize::MAX); + panic!("step counter is too high: {}", usize::MAX); + } + } + fn calibrate(&self) { if self.calibrate { - let new_step = unix_now().as_secs() / self.duration.as_secs(); + let new_step = unix_now().as_secs() / (self.duration as u64); self.inner.store(new_step as usize, AtomicOrdering::SeqCst); } } - fn is_future(&self, given: usize) -> bool { - if given > self.load() + 1 { - // Make absolutely sure that the given step is correct. - self.calibrate(); - given > self.load() + 1 + + fn check_future(&self, given: usize) -> Result<(), Option>> { + const REJECTED_STEP_DRIFT: usize = 4; + + // Verify if the step is correct. + if given <= self.load() { + return Ok(()); + } + + // Make absolutely sure that the given step is incorrect. + self.calibrate(); + let current = self.load(); + + // reject blocks too far in the future + if given > current + REJECTED_STEP_DRIFT { + Err(None) + // wait a bit for blocks in near future + } else if given > current { + let d = self.duration as u64; + Err(Some(OutOfBounds { + min: None, + max: Some(d * current as u64), + found: d * given as u64, + })) } else { - false + Ok(()) } } } @@ -311,22 +360,28 @@ fn verify_external(header: &Header, validators: &ValidatorSet, st { let header_step = header_step(header)?; - // Give one step slack if step is lagging, double vote is still not possible. - if step.is_future(header_step) { - trace!(target: "engine", "verify_block_external: block from the future"); - report(Report::Benign(*header.author(), header.number())); - Err(BlockError::InvalidSeal)? - } else { - let proposer_signature = header_signature(header)?; - let correct_proposer = validators.get(header.parent_hash(), header_step); - let is_invalid_proposer = *header.author() != correct_proposer || - !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())?; - - if is_invalid_proposer { - trace!(target: "engine", "verify_block_external: bad proposer for step: {}", header_step); - Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? - } else { - Ok(()) + match step.check_future(header_step) { + Err(None) => { + trace!(target: "engine", "verify_block_external: block from the future"); + report(Report::Benign(*header.author(), header.number())); + return Err(BlockError::InvalidSeal.into()) + }, + Err(Some(oob)) => { + trace!(target: "engine", "verify_block_external: block too early"); + return Err(BlockError::TemporarilyInvalid(oob).into()) + }, + Ok(_) => { + let proposer_signature = header_signature(header)?; + let correct_proposer = validators.get(header.parent_hash(), header_step); + let is_invalid_proposer = *header.author() != correct_proposer || + !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())?; + + if is_invalid_proposer { + trace!(target: "engine", "verify_block_external: bad proposer for step: {}", header_step); + Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? + } else { + Ok(()) + } } } } @@ -359,8 +414,12 @@ impl AsMillis for Duration { impl AuthorityRound { /// Create a new instance of AuthorityRound engine. pub fn new(our_params: AuthorityRoundParams, machine: EthereumMachine) -> Result, Error> { + if our_params.step_duration == 0 { + error!(target: "engine", "Authority Round step duration can't be zero, aborting"); + panic!("authority_round: step duration can't be zero") + } let should_timeout = our_params.start_step.is_none(); - let initial_step = our_params.start_step.unwrap_or_else(|| (unix_now().as_secs() / our_params.step_duration.as_secs())) as usize; + let initial_step = our_params.start_step.unwrap_or_else(|| (unix_now().as_secs() / (our_params.step_duration as u64))) as usize; let engine = Arc::new( AuthorityRound { transition_service: IoService::<()>::start()?, @@ -414,9 +473,15 @@ impl IoHandler<()> for TransitionHandler { fn timeout(&self, io: &IoContext<()>, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { - engine.step(); - let remaining = engine.step.duration_remaining(); - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, remaining.as_millis()) + // NOTE we might be lagging by couple of steps in case the timeout + // has not been called fast enough. + // Make sure to advance up to the actual step. + while engine.step.duration_remaining().as_millis() == 0 { + engine.step(); + } + + let next_run_at = engine.step.duration_remaining().as_millis() >> 2; + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, next_run_at) .unwrap_or_else(|e| warn!(target: "engine", "Failed to restart consensus step timer: {}.", e)) } } @@ -489,6 +554,13 @@ impl Engine for AuthorityRound { let expected_diff = calculate_score(parent_step, step.into()); if header.difficulty() != &expected_diff { + debug!(target: "engine", "Aborting seal generation. The step has changed in the meantime. {:?} != {:?}", + header.difficulty(), expected_diff); + return Seal::None; + } + + if parent_step > step.into() { + warn!(target: "engine", "Aborting seal generation for invalid step: {} > {}", parent_step, step); return Seal::None; } @@ -1019,7 +1091,7 @@ mod tests { fn reports_skipped() { let last_benign = Arc::new(AtomicUsize::new(0)); let params = AuthorityRoundParams { - step_duration: Default::default(), + step_duration: 1, start_step: Some(1), validators: Box::new(TestSet::new(Default::default(), last_benign.clone())), validate_score_transition: 0, @@ -1059,7 +1131,7 @@ mod tests { fn test_uncles_transition() { let last_benign = Arc::new(AtomicUsize::new(0)); let params = AuthorityRoundParams { - step_duration: Default::default(), + step_duration: 1, start_step: Some(1), validators: Box::new(TestSet::new(Default::default(), last_benign.clone())), validate_score_transition: 0, @@ -1081,4 +1153,50 @@ mod tests { assert_eq!(aura.maximum_uncle_count(1), 0); assert_eq!(aura.maximum_uncle_count(100), 0); } + + #[test] + #[should_panic(expected="counter is too high")] + fn test_counter_increment_too_high() { + use super::Step; + let step = Step { + calibrate: false, + inner: AtomicUsize::new(::std::usize::MAX), + duration: 1, + }; + step.increment(); + } + + #[test] + #[should_panic(expected="counter is too high")] + fn test_counter_duration_remaining_too_high() { + use super::Step; + let step = Step { + calibrate: false, + inner: AtomicUsize::new(::std::usize::MAX), + duration: 1, + }; + step.duration_remaining(); + } + + #[test] + #[should_panic(expected="authority_round: step duration can't be zero")] + fn test_step_duration_zero() { + let last_benign = Arc::new(AtomicUsize::new(0)); + let params = AuthorityRoundParams { + step_duration: 0, + start_step: Some(1), + validators: Box::new(TestSet::new(Default::default(), last_benign.clone())), + validate_score_transition: 0, + validate_step_transition: 0, + immediate_transitions: true, + maximum_uncle_count_transition: 0, + maximum_uncle_count: 0, + block_reward: Default::default(), + }; + + let mut c_params = ::spec::CommonParams::default(); + c_params.gas_limit_bound_divisor = 5.into(); + let machine = ::machine::EthereumMachine::regular(c_params, Default::default()); + AuthorityRound::new(params, machine).unwrap(); + } } diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 54e86b98bc4..05b85eba4e0 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -171,22 +171,36 @@ mod tests { // Make sure reporting can be done. client.miner().set_gas_floor_target(1_000_000.into()); - client.miner().set_engine_signer(v1, "".into()).unwrap(); + + // Check a block that is a bit in future, reject it but don't report the validator. let mut header = Header::default(); let seal = vec![encode(&5u8).into_vec(), encode(&(&H520::default() as &[u8])).into_vec()]; header.set_seal(seal); header.set_author(v1); header.set_number(2); header.set_parent_hash(client.chain_info().best_block_hash); + assert!(client.engine().verify_block_external(&header).is_err()); + client.engine().step(); + assert_eq!(client.chain_info().best_block_number, 0); + // Now create one that is more in future. That one should be rejected and validator should be reported. + let mut header = Header::default(); + let seal = vec![encode(&8u8).into_vec(), encode(&(&H520::default() as &[u8])).into_vec()]; + header.set_seal(seal); + header.set_author(v1); + header.set_number(2); + header.set_parent_hash(client.chain_info().best_block_hash); // `reportBenign` when the designated proposer releases block from the future (bad clock). assert!(client.engine().verify_block_external(&header).is_err()); // Seal a block. client.engine().step(); assert_eq!(client.chain_info().best_block_number, 1); // Check if the unresponsive validator is `disliked`. - assert_eq!(client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); + assert_eq!( + client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), + "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e" + ); // Simulate a misbehaving validator by handling a double proposal. let header = client.best_block_header().decode(); assert!(client.engine().verify_block_family(&header, &header).is_err()); diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 0146c684aa1..284e033822b 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -168,6 +168,8 @@ pub enum BlockError { InvalidReceiptsRoot(Mismatch), /// Timestamp header field is invalid. InvalidTimestamp(OutOfBounds), + /// Timestamp header field is too far in future. + TemporarilyInvalid(OutOfBounds), /// Log bloom header field is invalid. InvalidLogBloom(Mismatch), /// Parent hash field of header is invalid; this is an invalid error indicating a logic flaw in the codebase. @@ -213,6 +215,7 @@ impl fmt::Display for BlockError { InvalidGasLimit(ref oob) => format!("Invalid gas limit: {}", oob), InvalidReceiptsRoot(ref mis) => format!("Invalid receipts trie root in header: {}", mis), InvalidTimestamp(ref oob) => format!("Invalid timestamp in header: {}", oob), + TemporarilyInvalid(ref oob) => format!("Future timestamp in header: {}", oob), InvalidLogBloom(ref oob) => format!("Invalid log bloom in header: {}", oob), InvalidParentHash(ref mis) => format!("Invalid parent hash: {}", mis), InvalidNumber(ref mis) => format!("Invalid number in header: {}", mis), diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 55f0d58de3a..b7aae55889a 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -90,6 +90,10 @@ pub struct EthashParams { pub eip649_delay: u64, /// EIP-649 base reward. pub eip649_reward: Option, + /// EXPIP-2 block height + pub expip2_transition: u64, + /// EXPIP-2 duration limit + pub expip2_duration_limit: u64, } impl From for EthashParams { @@ -118,6 +122,8 @@ impl From for EthashParams { eip649_transition: p.eip649_transition.map_or(u64::max_value(), Into::into), eip649_delay: p.eip649_delay.map_or(DEFAULT_EIP649_DELAY, Into::into), eip649_reward: p.eip649_reward.map(Into::into), + expip2_transition: p.expip2_transition.map_or(u64::max_value(), Into::into), + expip2_duration_limit: p.expip2_duration_limit.map_or(30, Into::into), } } } @@ -355,7 +361,13 @@ impl Ethash { self.ethash_params.difficulty_bound_divisor }; - let duration_limit = self.ethash_params.duration_limit; + let expip2_hardfork = header.number() >= self.ethash_params.expip2_transition; + let duration_limit = if expip2_hardfork { + self.ethash_params.expip2_duration_limit + } else { + self.ethash_params.duration_limit + }; + let frontier_limit = self.ethash_params.homestead_transition; let mut target = if header.number() < frontier_limit { @@ -364,8 +376,7 @@ impl Ethash { } else { *parent.difficulty() + (*parent.difficulty() / difficulty_bound_divisor) } - } - else { + } else { trace!(target: "ethash", "Calculating difficulty parent.difficulty={}, header.timestamp={}, parent.timestamp={}", parent.difficulty(), header.timestamp(), parent.timestamp()); //block_diff = parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99) let (increment_divisor, threshold) = if header.number() < self.ethash_params.eip100b_transition { diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 8e47035c4d8..f03d0858368 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -486,12 +486,13 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { let traces = subtracer.drain(); match res { - Ok(ref res) => tracer.trace_call( + Ok(ref res) if res.apply_state => tracer.trace_call( trace_info, gas - res.gas_left, trace_output, traces ), + Ok(_) => tracer.trace_failed_call(trace_info, traces, vm::Error::Reverted.into()), Err(ref e) => tracer.trace_failed_call(trace_info, traces, e.into()), }; @@ -574,13 +575,14 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { vm_tracer.done_subtrace(subvmtracer); match res { - Ok(ref res) => tracer.trace_create( + Ok(ref res) if res.apply_state => tracer.trace_create( trace_info, gas - res.gas_left, trace_output.map(|data| output.as_ref().map(|out| out.to_vec()).unwrap_or(data)), created, subtracer.drain() ), + Ok(_) => tracer.trace_failed_create(trace_info, subtracer.drain(), vm::Error::Reverted.into()), Err(ref e) => tracer.trace_failed_create(trace_info, subtracer.drain(), e.into()) }; @@ -936,6 +938,85 @@ mod tests { assert_eq!(vm_tracer.drain().unwrap(), expected_vm_trace); } + #[test] + fn test_trace_reverted_create() { + // code: + // + // 65 60016000fd - push 5 bytes + // 60 00 - push 0 + // 52 mstore + // 60 05 - push 5 + // 60 1b - push 27 + // 60 17 - push 23 + // f0 - create + // 60 00 - push 0 + // 55 sstore + // + // other code: + // + // 60 01 + // 60 00 + // fd - revert + + let code = "6460016000fd6000526005601b6017f0600055".from_hex().unwrap(); + + let sender = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap(); + let address = contract_address(CreateContractAddress::FromSenderAndNonce, &sender, &U256::zero(), &[]).0; + let mut params = ActionParams::default(); + params.address = address.clone(); + params.code_address = address.clone(); + params.sender = sender.clone(); + params.origin = sender.clone(); + params.gas = U256::from(100_000); + params.code = Some(Arc::new(code)); + params.value = ActionValue::Transfer(U256::from(100)); + params.call_type = CallType::Call; + let mut state = get_temp_state(); + state.add_balance(&sender, &U256::from(100), CleanupMode::NoEmpty).unwrap(); + let info = EnvInfo::default(); + let machine = ::ethereum::new_byzantium_test_machine(); + let mut substate = Substate::new(); + let mut tracer = ExecutiveTracer::default(); + let mut vm_tracer = ExecutiveVMTracer::toplevel(); + + let FinalizationResult { gas_left, .. } = { + let mut ex = Executive::new(&mut state, &info, &machine); + let output = BytesRef::Fixed(&mut[0u8;0]); + ex.call(params, &mut substate, output, &mut tracer, &mut vm_tracer).unwrap() + }; + + assert_eq!(gas_left, U256::from(62967)); + + let expected_trace = vec![FlatTrace { + trace_address: Default::default(), + subtraces: 1, + action: trace::Action::Call(trace::Call { + from: "cd1722f3947def4cf144679da39c4c32bdc35681".into(), + to: "b010143a42d5980c7e5ef0e4a4416dc098a4fed3".into(), + value: 100.into(), + gas: 100_000.into(), + input: vec![], + call_type: CallType::Call, + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(37_033), + output: vec![], + }), + }, FlatTrace { + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + action: trace::Action::Create(trace::Create { + from: "b010143a42d5980c7e5ef0e4a4416dc098a4fed3".into(), + value: 23.into(), + gas: 66_917.into(), + init: vec![0x60, 0x01, 0x60, 0x00, 0xfd] + }), + result: trace::Res::FailedCreate(vm::Error::Reverted.into()), + }]; + + assert_eq!(tracer.drain(), expected_trace); + } + #[test] fn test_create_contract() { // Tracing is not supported in JIT diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index cf14915c54b..8d92d3d732c 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -386,5 +386,7 @@ pub fn get_default_ethash_params() -> EthashParams { eip649_transition: u64::max_value(), eip649_delay: 3_000_000, eip649_reward: None, + expip2_transition: u64::max_value(), + expip2_duration_limit: 30, } } diff --git a/ethcore/src/verification/queue/mod.rs b/ethcore/src/verification/queue/mod.rs index 198c6328774..ef673d57e43 100644 --- a/ethcore/src/verification/queue/mod.rs +++ b/ethcore/src/verification/queue/mod.rs @@ -505,7 +505,7 @@ impl VerificationQueue { Err(err) => { match err { // Don't mark future blocks as bad. - Error::Block(BlockError::InvalidTimestamp(ref e)) if e.max.is_some() => {}, + Error::Block(BlockError::TemporarilyInvalid(_)) => {}, _ => { self.verification.bad.lock().insert(h.clone()); } diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index a418236ed1b..05a96e35451 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -273,11 +273,20 @@ pub fn verify_header_params(header: &Header, engine: &EthEngine, is_full: bool) } if is_full { - let max_time = get_time().sec as u64 + 15; - if header.timestamp() > max_time { - return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: Some(max_time), min: None, found: header.timestamp() }))) + const ACCEPTABLE_DRIFT_SECS: u64 = 15; + let max_time = get_time().sec as u64 + ACCEPTABLE_DRIFT_SECS; + let invalid_threshold = max_time + ACCEPTABLE_DRIFT_SECS * 9; + let timestamp = header.timestamp(); + + if timestamp > invalid_threshold { + return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: Some(max_time), min: None, found: timestamp }))) + } + + if timestamp > max_time { + return Err(From::from(BlockError::TemporarilyInvalid(OutOfBounds { max: Some(max_time), min: None, found: timestamp }))) } } + Ok(()) } @@ -359,11 +368,13 @@ mod tests { } } - fn check_fail_timestamp(result: Result<(), Error>) { + fn check_fail_timestamp(result: Result<(), Error>, temp: bool) { + let name = if temp { "TemporarilyInvalid" } else { "InvalidTimestamp" }; match result { - Err(Error::Block(BlockError::InvalidTimestamp(_))) => (), - Err(other) => panic!("Block verification failed.\nExpected: InvalidTimestamp\nGot: {:?}", other), - Ok(_) => panic!("Block verification failed.\nExpected: InvalidTimestamp\nGot: Ok"), + Err(Error::Block(BlockError::InvalidTimestamp(_))) if !temp => (), + Err(Error::Block(BlockError::TemporarilyInvalid(_))) if temp => (), + Err(other) => panic!("Block verification failed.\nExpected: {}\nGot: {:?}", name, other), + Ok(_) => panic!("Block verification failed.\nExpected: {}\nGot: Ok", name), } } @@ -643,11 +654,11 @@ mod tests { header = good.clone(); header.set_timestamp(2450000000); - check_fail_timestamp(basic_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine)); + check_fail_timestamp(basic_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine), false); header = good.clone(); header.set_timestamp(get_time().sec as u64 + 20); - check_fail_timestamp(basic_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine)); + check_fail_timestamp(basic_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine), true); header = good.clone(); header.set_timestamp(get_time().sec as u64 + 10); diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index f39db7344fb..57e354c7268 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -22,7 +22,7 @@ use super::ValidatorSet; /// Authority params deserialization. #[derive(Debug, PartialEq, Deserialize)] pub struct AuthorityRoundParams { - /// Block duration. + /// Block duration, in seconds. #[serde(rename="stepDuration")] pub step_duration: Uint, /// Valid authorities diff --git a/json/src/spec/ethash.rs b/json/src/spec/ethash.rs index 8582a3d95e4..283e24ba067 100644 --- a/json/src/spec/ethash.rs +++ b/json/src/spec/ethash.rs @@ -125,6 +125,14 @@ pub struct EthashParams { /// EIP-649 base reward. #[serde(rename="eip649Reward")] pub eip649_reward: Option, + + /// EXPIP-2 block height + #[serde(rename="expip2Transition")] + pub expip2_transition: Option, + + /// EXPIP-2 duration limit + #[serde(rename="expip2DurationLimit")] + pub expip2_duration_limit: Option, } /// Ethash engine deserialization. @@ -241,6 +249,8 @@ mod tests { eip649_transition: None, eip649_delay: None, eip649_reward: None, + expip2_transition: None, + expip2_duration_limit: None, } }); } @@ -287,6 +297,8 @@ mod tests { eip649_transition: None, eip649_delay: None, eip649_reward: None, + expip2_transition: None, + expip2_duration_limit: None, } }); } diff --git a/parity/blockchain.rs b/parity/blockchain.rs index b1bf8d4dbb6..84a610ef3ef 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -591,8 +591,12 @@ fn execute_export(cmd: ExportBlockchain) -> Result<(), String> { } let b = client.block(BlockId::Number(i)).ok_or("Error exporting incomplete chain")?.into_inner(); match format { - DataFormat::Binary => { out.write(&b).expect("Couldn't write to stream."); } - DataFormat::Hex => { out.write_fmt(format_args!("{}", b.pretty())).expect("Couldn't write to stream."); } + DataFormat::Binary => { + out.write(&b).map_err(|e| format!("Couldn't write to stream. Cause: {}", e))?; + } + DataFormat::Hex => { + out.write_fmt(format_args!("{}", b.pretty())).map_err(|e| format!("Couldn't write to stream. Cause: {}", e))?; + } } } diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 5e0342f3ee6..8ae5b4c4f43 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -342,6 +342,7 @@ usage! { ARG arg_password: (Vec) = Vec::new(), or |c: &Config| otry!(c.account).password.clone(), "--password=[FILE]...", "Provide a file containing a password for unlocking an account. Leading and trailing whitespace is trimmed.", + ["UI options"] FLAG flag_force_ui: (bool) = false, or |c: &Config| otry!(c.ui).force.clone(), "--force-ui", @@ -684,6 +685,10 @@ usage! { "--min-gas-price=[STRING]", "Minimum amount of Wei per GAS to be paid for a transaction to be accepted for mining. Overrides --basic-tx-usd.", + ARG arg_gas_price_percentile: (usize) = 50usize, or |c: &Config| otry!(c.mining).gas_price_percentile, + "--gas-price-percentile=[PCT]", + "Set PCT percentile gas price value from last 100 blocks as default gas price when sending transactions.", + ARG arg_author: (Option) = None, or |c: &Config| otry!(c.mining).author.clone(), "--author=[ADDRESS]", "Specify the block author (aka \"coinbase\") address for sending block rewards from sealed blocks. NOTE: MINING WILL NOT WORK WITHOUT THIS OPTION.", // Sealing/Mining Option @@ -982,6 +987,7 @@ struct Config { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Operating { mode: Option, mode_timeout: Option, @@ -1001,6 +1007,7 @@ struct Operating { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Account { unlock: Option>, password: Option>, @@ -1010,6 +1017,7 @@ struct Account { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Ui { force: Option, disable: Option, @@ -1020,6 +1028,7 @@ struct Ui { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Network { warp: Option, port: Option, @@ -1039,6 +1048,7 @@ struct Network { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Rpc { disable: Option, port: Option, @@ -1051,6 +1061,7 @@ struct Rpc { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Ws { disable: Option, port: Option, @@ -1061,6 +1072,7 @@ struct Ws { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Ipc { disable: Option, path: Option, @@ -1068,6 +1080,7 @@ struct Ipc { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Dapps { disable: Option, port: Option, @@ -1080,6 +1093,7 @@ struct Dapps { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct SecretStore { disable: Option, disable_http: Option, @@ -1095,6 +1109,7 @@ struct SecretStore { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Ipfs { enable: Option, port: Option, @@ -1104,6 +1119,7 @@ struct Ipfs { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Mining { author: Option, engine_signer: Option, @@ -1117,6 +1133,7 @@ struct Mining { tx_time_limit: Option, relay_set: Option, min_gas_price: Option, + gas_price_percentile: Option, usd_per_tx: Option, usd_per_eth: Option, price_update_period: Option, @@ -1135,6 +1152,7 @@ struct Mining { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Stratum { interface: Option, port: Option, @@ -1142,6 +1160,7 @@ struct Stratum { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Footprint { tracing: Option, pruning: Option, @@ -1160,16 +1179,19 @@ struct Footprint { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Snapshots { disable_periodic: Option, } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct VM { jit: Option, } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Misc { ntp_servers: Option>, logging: Option, @@ -1180,6 +1202,7 @@ struct Misc { } #[derive(Default, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] struct Whisper { enabled: Option, pool_size: Option, @@ -1512,6 +1535,7 @@ mod tests { arg_tx_time_limit: Some(100u64), arg_relay_set: "cheap".into(), arg_min_gas_price: Some(0u64), + arg_gas_price_percentile: 50usize, arg_usd_per_tx: "0.0025".into(), arg_usd_per_eth: "auto".into(), arg_price_update_period: "hourly".into(), @@ -1624,11 +1648,17 @@ mod tests { let config1 = Args::parse_config(include_str!("./tests/config.invalid1.toml")); let config2 = Args::parse_config(include_str!("./tests/config.invalid2.toml")); let config3 = Args::parse_config(include_str!("./tests/config.invalid3.toml")); - - match (config1, config2, config3) { - (Err(ArgsError::Decode(_)), Err(ArgsError::Decode(_)), Err(ArgsError::Decode(_))) => {}, - (a, b, c) => { - assert!(false, "Got invalid error types: {:?}, {:?}, {:?}", a, b, c); + let config4 = Args::parse_config(include_str!("./tests/config.invalid4.toml")); + + match (config1, config2, config3, config4) { + ( + Err(ArgsError::Decode(_)), + Err(ArgsError::Decode(_)), + Err(ArgsError::Decode(_)), + Err(ArgsError::Decode(_)), + ) => {}, + (a, b, c, d) => { + assert!(false, "Got invalid error types: {:?}, {:?}, {:?}, {:?}", a, b, c, d); } } } @@ -1751,6 +1781,7 @@ mod tests { work_queue_size: None, relay_set: None, min_gas_price: None, + gas_price_percentile: None, usd_per_tx: None, usd_per_eth: None, price_update_period: Some("hourly".into()), diff --git a/parity/cli/tests/config.invalid4.toml b/parity/cli/tests/config.invalid4.toml new file mode 100644 index 00000000000..a5429b2baa8 --- /dev/null +++ b/parity/cli/tests/config.invalid4.toml @@ -0,0 +1,2 @@ +[account] +invalid = 5 diff --git a/parity/configuration.rs b/parity/configuration.rs index 4f3e6dcbaa6..0ec19a10531 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -337,6 +337,7 @@ impl Configuration { daemon: daemon, logger_config: logger_config.clone(), miner_options: self.miner_options()?, + gas_price_percentile: self.args.arg_gas_price_percentile, ntp_servers: self.ntp_servers(), ws_conf: ws_conf, http_conf: http_conf, @@ -1327,6 +1328,7 @@ mod tests { daemon: None, logger_config: Default::default(), miner_options: Default::default(), + gas_price_percentile: 50, ntp_servers: vec![ "0.parity.pool.ntp.org:123".into(), "1.parity.pool.ntp.org:123".into(), diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index 933b87922db..f794fa1a9c6 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -226,6 +226,7 @@ pub struct FullDependencies { pub fetch: FetchClient, pub remote: parity_reactor::Remote, pub whisper_rpc: Option<::whisper::RpcFactory>, + pub gas_price_percentile: usize, } impl FullDependencies { @@ -241,7 +242,7 @@ impl FullDependencies { ($namespace:ident, $handler:expr, $deps:expr, $nonces:expr) => { { let deps = &$deps; - let dispatcher = FullDispatcher::new(deps.client.clone(), deps.miner.clone(), $nonces); + let dispatcher = FullDispatcher::new(deps.client.clone(), deps.miner.clone(), $nonces, deps.gas_price_percentile); if deps.signer_service.is_enabled() { $handler.extend_with($namespace::to_delegate(SigningQueueClient::new(&deps.signer_service, dispatcher, &deps.secret_store))) } else { @@ -256,6 +257,7 @@ impl FullDependencies { self.client.clone(), self.miner.clone(), nonces.clone(), + self.gas_price_percentile, ); for api in apis { match *api { @@ -277,6 +279,7 @@ impl FullDependencies { pending_nonce_from_queue: self.geth_compatibility, allow_pending_receipt_query: !self.geth_compatibility, send_block_number_in_get_work: !self.geth_compatibility, + gas_price_percentile: self.gas_price_percentile, } ); handler.extend_with(client.to_delegate()); @@ -422,6 +425,7 @@ pub struct LightDependencies { pub geth_compatibility: bool, pub remote: parity_reactor::Remote, pub whisper_rpc: Option<::whisper::RpcFactory>, + pub gas_price_percentile: usize, } impl LightDependencies { @@ -440,6 +444,7 @@ impl LightDependencies { self.cache.clone(), self.transaction_queue.clone(), Arc::new(Mutex::new(dispatch::Reservations::with_pool(self.fetch.pool()))), + self.gas_price_percentile, ); macro_rules! add_signing_methods { @@ -477,6 +482,7 @@ impl LightDependencies { self.transaction_queue.clone(), self.secret_store.clone(), self.cache.clone(), + self.gas_price_percentile, ); handler.extend_with(Eth::to_delegate(client.clone())); @@ -492,6 +498,7 @@ impl LightDependencies { self.sync.clone(), self.cache.clone(), self.remote.clone(), + self.gas_price_percentile, ); self.client.add_listener( Arc::downgrade(&client.handler()) as Weak<::light::client::LightChainNotify> @@ -521,6 +528,7 @@ impl LightDependencies { signer, self.dapps_address.clone(), self.ws_address.clone(), + self.gas_price_percentile, ).to_delegate()); if !for_generic_pubsub { diff --git a/parity/run.rs b/parity/run.rs index e11a176e44b..4d27fb152f3 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -87,6 +87,7 @@ pub struct RunCmd { pub daemon: Option, pub logger_config: LogConfig, pub miner_options: MinerOptions, + pub gas_price_percentile: usize, pub ntp_servers: Vec, pub ws_conf: rpc::WsConfiguration, pub http_conf: rpc::HttpConfiguration, @@ -358,6 +359,7 @@ fn execute_light(cmd: RunCmd, can_restart: bool, logger: Arc) -> geth_compatibility: cmd.geth_compatibility, remote: event_loop.remote(), whisper_rpc: whisper_factory, + gas_price_percentile: cmd.gas_price_percentile, }); let dependencies = rpc::Dependencies { @@ -765,6 +767,7 @@ pub fn execute(cmd: RunCmd, can_restart: bool, logger: Arc) -> R fetch: fetch.clone(), remote: event_loop.remote(), whisper_rpc: whisper_factory, + gas_price_percentile: cmd.gas_price_percentile, }); let dependencies = rpc::Dependencies { diff --git a/parity/whisper.rs b/parity/whisper.rs index f8d33626bcf..ed4812063d6 100644 --- a/parity/whisper.rs +++ b/parity/whisper.rs @@ -51,7 +51,7 @@ impl PoolHandle for NetPoolHandle { fn relay(&self, message: Message) -> bool { let mut res = false; let mut message = Some(message); - self.net.with_proto_context(whisper_net::PROTOCOL_ID, &mut move |ctx| { + self.net.with_proto_context(whisper_net::PROTOCOL_ID, &mut |ctx| { if let Some(message) = message.take() { res = self.handle.post_message(message, ctx); } diff --git a/price-info/src/lib.rs b/price-info/src/lib.rs index 7036cfefc11..ec3c363a42d 100644 --- a/price-info/src/lib.rs +++ b/price-info/src/lib.rs @@ -86,7 +86,7 @@ impl cmp::PartialEq for Client { impl Client { /// Creates a new instance of the `Client` given a `fetch::Client`. pub fn new(fetch: F) -> Client { - let api_endpoint = "http://api.etherscan.io/api?module=stats&action=ethprice".to_owned(); + let api_endpoint = "https://api.etherscan.io/api?module=stats&action=ethprice".to_owned(); Client { api_endpoint, fetch } } @@ -144,7 +144,7 @@ mod test { type Result = FutureResult; fn new() -> Result where Self: Sized { Ok(FakeFetch(None, Default::default())) } fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result { - assert_eq!(url, "http://api.etherscan.io/api?module=stats&action=ethprice"); + assert_eq!(url, "https://api.etherscan.io/api?module=stats&action=ethprice"); let mut val = self.1.lock(); *val = *val + 1; if let Some(ref response) = self.0 { diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 35e16f57388..0d50ca9eca6 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -88,6 +88,7 @@ pub struct FullDispatcher { client: Arc, miner: Arc, nonces: Arc>, + gas_price_percentile: usize, } impl FullDispatcher { @@ -96,11 +97,13 @@ impl FullDispatcher { client: Arc, miner: Arc, nonces: Arc>, + gas_price_percentile: usize, ) -> Self { FullDispatcher { client, miner, nonces, + gas_price_percentile, } } } @@ -111,6 +114,7 @@ impl Clone for FullDispatcher { client: self.client.clone(), miner: self.miner.clone(), nonces: self.nonces.clone(), + gas_price_percentile: self.gas_price_percentile, } } } @@ -148,7 +152,9 @@ impl Dispatcher for FullDispatcher = prices.into(); cache.lock().set_gas_price_corpus(corpus.clone()); corpus @@ -258,6 +264,8 @@ pub struct LightDispatcher { pub transaction_queue: Arc>, /// Nonce reservations pub nonces: Arc>, + /// Gas Price percentile value used as default gas price. + pub gas_price_percentile: usize, } impl LightDispatcher { @@ -271,6 +279,7 @@ impl LightDispatcher { cache: Arc>, transaction_queue: Arc>, nonces: Arc>, + gas_price_percentile: usize, ) -> Self { LightDispatcher { sync, @@ -279,6 +288,7 @@ impl LightDispatcher { cache, transaction_queue, nonces, + gas_price_percentile, } } @@ -345,6 +355,7 @@ impl Dispatcher for LightDispatcher { }; // fast path for known gas price. + let gas_price_percentile = self.gas_price_percentile; let gas_price = match request_gas_price { Some(gas_price) => Either::A(future::ok(with_gas_price(gas_price))), None => Either::B(fetch_gas_price_corpus( @@ -352,8 +363,8 @@ impl Dispatcher for LightDispatcher { self.client.clone(), self.on_demand.clone(), self.cache.clone() - ).and_then(|corp| match corp.median() { - Some(median) => Ok(*median), + ).and_then(move |corp| match corp.percentile(gas_price_percentile) { + Some(percentile) => Ok(*percentile), None => Ok(DEFAULT_GAS_PRICE), // fall back to default on error. }).map(with_gas_price)) }; @@ -738,11 +749,11 @@ fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: S } /// Extract the default gas price from a client and miner. -pub fn default_gas_price(client: &C, miner: &M) -> U256 where +pub fn default_gas_price(client: &C, miner: &M, percentile: usize) -> U256 where C: MiningBlockChainClient, M: MinerService, { - client.gas_price_corpus(100).median().cloned().unwrap_or_else(|| miner.sensible_gas_price()) + client.gas_price_corpus(100).percentile(percentile).cloned().unwrap_or_else(|| miner.sensible_gas_price()) } /// Convert RPC confirmation payload to signer confirmation payload. diff --git a/rpc/src/v1/helpers/fake_sign.rs b/rpc/src/v1/helpers/fake_sign.rs index 02259a9db41..537bfd5a46c 100644 --- a/rpc/src/v1/helpers/fake_sign.rs +++ b/rpc/src/v1/helpers/fake_sign.rs @@ -46,7 +46,7 @@ pub fn sign_call ( nonce: request.nonce.unwrap_or_else(|| client.latest_nonce(&from)), action: request.to.map_or(Action::Create, Action::Call), gas, - gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(&**client, &**miner)), + gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(&**client, &**miner, 0)), value: request.value.unwrap_or(0.into()), data: request.data.unwrap_or_default(), }.fake_sign(from)) diff --git a/rpc/src/v1/helpers/light_fetch.rs b/rpc/src/v1/helpers/light_fetch.rs index 2544f66b012..b77fffff207 100644 --- a/rpc/src/v1/helpers/light_fetch.rs +++ b/rpc/src/v1/helpers/light_fetch.rs @@ -60,6 +60,8 @@ pub struct LightFetch { pub sync: Arc, /// The light data cache. pub cache: Arc>, + /// Gas Price percentile + pub gas_price_percentile: usize, } /// Extract a transaction at given index. @@ -203,6 +205,7 @@ impl LightFetch { None => Either::B(self.account(from, id).map(|acc| acc.map(|a| a.nonce))), }; + let gas_price_percentile = self.gas_price_percentile; let gas_price_fut = match req.gas_price { Some(price) => Either::A(future::ok(price)), None => Either::B(dispatch::fetch_gas_price_corpus( @@ -210,8 +213,8 @@ impl LightFetch { self.client.clone(), self.on_demand.clone(), self.cache.clone(), - ).map(|corp| match corp.median() { - Some(median) => *median, + ).map(move |corp| match corp.percentile(gas_price_percentile) { + Some(percentile) => *percentile, None => DEFAULT_GAS_PRICE.into(), })) }; diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 48ac617c0e2..234fb49f398 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -66,6 +66,8 @@ pub struct EthClientOptions { pub allow_pending_receipt_query: bool, /// Send additional block number when asking for work pub send_block_number_in_get_work: bool, + /// Gas Price Percentile used as default gas price. + pub gas_price_percentile: usize, } impl EthClientOptions { @@ -84,6 +86,7 @@ impl Default for EthClientOptions { pending_nonce_from_queue: false, allow_pending_receipt_query: true, send_block_number_in_get_work: true, + gas_price_percentile: 50, } } } @@ -338,7 +341,7 @@ impl Eth for EthClient where } fn gas_price(&self) -> Result { - Ok(RpcU256::from(default_gas_price(&*self.client, &*self.miner))) + Ok(RpcU256::from(default_gas_price(&*self.client, &*self.miner, self.options.gas_price_percentile))) } fn accounts(&self, meta: Metadata) -> Result, Error> { diff --git a/rpc/src/v1/impls/eth_pubsub.rs b/rpc/src/v1/impls/eth_pubsub.rs index 74b37d7d0bf..bdbc755e1f9 100644 --- a/rpc/src/v1/impls/eth_pubsub.rs +++ b/rpc/src/v1/impls/eth_pubsub.rs @@ -92,12 +92,14 @@ impl EthPubSubClient { sync: Arc, cache: Arc>, remote: Remote, + gas_price_percentile: usize, ) -> Self { let fetch = LightFetch { client, on_demand, sync, - cache + cache, + gas_price_percentile, }; EthPubSubClient::new(Arc::new(fetch), remote) } diff --git a/rpc/src/v1/impls/light/eth.rs b/rpc/src/v1/impls/light/eth.rs index b797e76c2e9..8f3e2fb34be 100644 --- a/rpc/src/v1/impls/light/eth.rs +++ b/rpc/src/v1/impls/light/eth.rs @@ -62,6 +62,7 @@ pub struct EthClient { accounts: Arc, cache: Arc>, polls: Mutex>, + gas_price_percentile: usize, } impl Clone for EthClient { @@ -75,6 +76,7 @@ impl Clone for EthClient { accounts: self.accounts.clone(), cache: self.cache.clone(), polls: Mutex::new(PollManager::new()), + gas_price_percentile: self.gas_price_percentile, } } } @@ -89,15 +91,17 @@ impl EthClient { transaction_queue: Arc>, accounts: Arc, cache: Arc>, + gas_price_percentile: usize, ) -> Self { EthClient { - sync: sync, - client: client, - on_demand: on_demand, - transaction_queue: transaction_queue, - accounts: accounts, - cache: cache, + sync, + client, + on_demand, + transaction_queue, + accounts, + cache, polls: Mutex::new(PollManager::new()), + gas_price_percentile, } } @@ -108,6 +112,7 @@ impl EthClient { on_demand: self.on_demand.clone(), sync: self.sync.clone(), cache: self.cache.clone(), + gas_price_percentile: self.gas_price_percentile, } } @@ -239,7 +244,7 @@ impl Eth for EthClient { fn gas_price(&self) -> Result { Ok(self.cache.lock().gas_price_corpus() - .and_then(|c| c.median().cloned()) + .and_then(|c| c.percentile(self.gas_price_percentile).cloned()) .map(RpcU256::from) .unwrap_or_else(Default::default)) } diff --git a/rpc/src/v1/impls/light/parity.rs b/rpc/src/v1/impls/light/parity.rs index a896a5f4dfd..b7b0b9a95ce 100644 --- a/rpc/src/v1/impls/light/parity.rs +++ b/rpc/src/v1/impls/light/parity.rs @@ -60,6 +60,7 @@ pub struct ParityClient { dapps_address: Option, ws_address: Option, eip86_transition: u64, + gas_price_percentile: usize, } impl ParityClient { @@ -74,6 +75,7 @@ impl ParityClient { signer: Option>, dapps_address: Option, ws_address: Option, + gas_price_percentile: usize, ) -> Self { ParityClient { light_dispatch, @@ -85,7 +87,8 @@ impl ParityClient { dapps_address, ws_address, eip86_transition: client.eip86_transition(), - client: client, + client, + gas_price_percentile, } } @@ -96,6 +99,7 @@ impl ParityClient { on_demand: self.light_dispatch.on_demand.clone(), sync: self.light_dispatch.sync.clone(), cache: self.light_dispatch.cache.clone(), + gas_price_percentile: self.gas_price_percentile, } } } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index dde8008c0ed..8b846fc9289 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -152,7 +152,7 @@ impl EthTester { let reservations = Arc::new(Mutex::new(nonce::Reservations::new())); - let dispatcher = FullDispatcher::new(client.clone(), miner_service.clone(), reservations); + let dispatcher = FullDispatcher::new(client.clone(), miner_service.clone(), reservations, 50); let eth_sign = SigningUnsafeClient::new( &opt_account_provider, dispatcher, diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 5dde34c7c53..957a0fb9663 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -93,11 +93,12 @@ impl EthTester { let snapshot = snapshot_service(); let hashrates = Arc::new(Mutex::new(HashMap::new())); let external_miner = Arc::new(ExternalMiner::new(hashrates.clone())); + let gas_price_percentile = options.gas_price_percentile; let eth = EthClient::new(&client, &snapshot, &sync, &opt_ap, &miner, &external_miner, options).to_delegate(); let filter = EthFilterClient::new(client.clone(), miner.clone()).to_delegate(); let reservations = Arc::new(Mutex::new(nonce::Reservations::new())); - let dispatcher = FullDispatcher::new(client.clone(), miner.clone(), reservations); + let dispatcher = FullDispatcher::new(client.clone(), miner.clone(), reservations, gas_price_percentile); let sign = SigningUnsafeClient::new(&opt_ap, dispatcher).to_delegate(); let mut io: IoHandler = IoHandler::default(); io.extend_with(eth); diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 447cee59a21..e6924619635 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -56,7 +56,7 @@ fn setup() -> PersonalTester { let miner = miner_service(); let reservations = Arc::new(Mutex::new(nonce::Reservations::new())); - let dispatcher = FullDispatcher::new(client, miner.clone(), reservations); + let dispatcher = FullDispatcher::new(client, miner.clone(), reservations, 50); let personal = PersonalClient::new(&opt_accounts, dispatcher, false); let mut io = IoHandler::default(); diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index 3274ec44f6e..954447c1883 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -65,7 +65,7 @@ fn signer_tester() -> SignerTester { let reservations = Arc::new(Mutex::new(nonce::Reservations::new())); let event_loop = EventLoop::spawn(); - let dispatcher = FullDispatcher::new(client, miner.clone(), reservations); + let dispatcher = FullDispatcher::new(client, miner.clone(), reservations, 50); let mut io = IoHandler::default(); io.extend_with(SignerClient::new(&opt_accounts, dispatcher, &signer, event_loop.remote()).to_delegate()); diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index cc470aad82c..e840f0762a2 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -58,7 +58,7 @@ impl Default for SigningTester { let reservations = Arc::new(Mutex::new(nonce::Reservations::new())); let mut io = IoHandler::default(); - let dispatcher = FullDispatcher::new(client.clone(), miner.clone(), reservations); + let dispatcher = FullDispatcher::new(client.clone(), miner.clone(), reservations, 50); let rpc = SigningQueueClient::new(&signer, dispatcher.clone(), &opt_accounts); io.extend_with(EthSigning::to_delegate(rpc)); diff --git a/util/stats/src/lib.rs b/util/stats/src/lib.rs index 01c988232fd..74fda927265 100644 --- a/util/stats/src/lib.rs +++ b/util/stats/src/lib.rs @@ -46,6 +46,18 @@ impl Deref for Corpus { } impl Corpus { + /// Get given percentile (approximated). + pub fn percentile(&self, val: usize) -> Option<&T> { + let len = self.0.len(); + let x = val * len / 100; + let x = ::std::cmp::min(x, len); + if x == 0 { + return None; + } + + self.0.get(x - 1) + } + /// Get the median element, if it exists. pub fn median(&self) -> Option<&T> { self.0.get(self.0.len() / 2) @@ -121,7 +133,19 @@ impl Histogram #[cfg(test)] mod tests { - use super::Histogram; + use super::*; + + #[test] + fn check_corpus() { + let corpus = Corpus::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + assert_eq!(corpus.percentile(0), None); + assert_eq!(corpus.percentile(1), None); + assert_eq!(corpus.percentile(101), Some(&10)); + assert_eq!(corpus.percentile(100), Some(&10)); + assert_eq!(corpus.percentile(50), Some(&5)); + assert_eq!(corpus.percentile(60), Some(&6)); + assert_eq!(corpus.median(), Some(&6)); + } #[test] fn check_histogram() {