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() {