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 | 194 ++++++++++++++---- 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 | 39 +++- 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, 481 insertions(+), 106 deletions(-) create mode 100644 parity/cli/tests/config.invalid4.toml diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index a12083b56..408f2ae13 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 46260c08c..fa33255a6 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 ac2505489..f07d11ff7 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 8c48ad86b..1600c1570 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 ec7e737ea..b9b734e31 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 95f330cfd..09d045998 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 48638eda2..75df50e43 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())?; + 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(()) + 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 54e86b98b..05b85eba4 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 0146c684a..284e03382 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 55f0d58de..b7aae5588 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 8e47035c4..f03d08583 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 cf14915c5..8d92d3d73 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 198c63287..ef673d57e 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 a418236ed..05a96e354 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 f39db7344..57e354c72 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 8582a3d95..283e24ba0 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 b1bf8d4db..84a610ef3 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 5e0342f3e..8ae5b4c4f 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")); + let config4 = Args::parse_config(include_str!("./tests/config.invalid4.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); + 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 000000000..a5429b2ba --- /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 4f3e6dcba..0ec19a105 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 933b87922..f794fa1a9 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 e11a176e4..4d27fb152 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 f8d33626b..ed4812063 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 7036cfefc..ec3c363a4 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 35e16f573..0d50ca9ec 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 02259a9db..537bfd5a4 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 2544f66b0..b77fffff2 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 48ac617c0..234fb49f3 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 74b37d7d0..bdbc755e1 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 b797e76c2..8f3e2fb34 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 a896a5f4d..b7b0b9a95 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 dde8008c0..8b846fc92 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 5dde34c7c..957a0fb96 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 447cee59a..e69246196 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 3274ec44f..954447c18 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 cc470aad8..e840f0762 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 01c988232..74fda9272 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() {