From 5de1e97faa29ee06bc6176f105154a43d6d7da55 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 19 Feb 2019 20:11:06 +0100 Subject: [PATCH] fix(extract `timestamp_checked_add` as lib) --- Cargo.lock | 6 +++ Cargo.toml | 3 +- ethcore/Cargo.toml | 1 + ethcore/src/engines/authority_round/mod.rs | 16 ++++-- ethcore/src/lib.rs | 1 + ethcore/src/verification/verification.rs | 37 +++---------- util/time-utils/Cargo.toml | 9 ++++ util/time-utils/src/lib.rs | 62 ++++++++++++++++++++++ whisper/Cargo.toml | 1 + whisper/src/lib.rs | 1 + whisper/src/message.rs | 19 ++++--- whisper/src/net/mod.rs | 9 ++-- 12 files changed, 123 insertions(+), 42 deletions(-) create mode 100644 util/time-utils/Cargo.toml create mode 100644 util/time-utils/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 569e99065a0..3b2d54042d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -752,6 +752,7 @@ dependencies = [ "serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "stats 0.1.0", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "time-utils 0.1.0", "trace-time 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "trie-standardmap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "triehash-ethereum 0.2.0", @@ -2825,6 +2826,7 @@ dependencies = [ "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "time-utils 0.1.0", "tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3791,6 +3793,10 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "time-utils" +version = "0.1.0" + [[package]] name = "timer" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 18a9f55e1a2..ec5d5828d33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -138,7 +138,8 @@ members = [ "util/triehash-ethereum", "util/keccak-hasher", "util/patricia-trie-ethereum", - "util/fastmap" + "util/fastmap", + "util/time-utils" ] [patch.crates-io] diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index b6174984434..ef8cbc3991e 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -64,6 +64,7 @@ serde = "1.0" serde_derive = "1.0" stats = { path = "../util/stats" } tempdir = {version="0.3", optional = true} +time-utils = { path = "../util/time-utils" } trace-time = "0.1" triehash-ethereum = { version = "0.2", path = "../util/triehash-ethereum" } unexpected = { path = "../util/unexpected" } diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 2a42acfe23f..638c811d07d 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -42,6 +42,7 @@ use itertools::{self, Itertools}; use rlp::{encode, Decodable, DecoderError, Encodable, RlpStream, Rlp}; use ethereum_types::{H256, H520, Address, U128, U256}; use parking_lot::{Mutex, RwLock}; +use time_utils::CheckedSystemTime; use types::BlockNumber; use types::header::{Header, ExtendedHeader}; use types::ancestry_action::AncestryAction; @@ -570,8 +571,16 @@ fn verify_timestamp(step: &Step, header_step: u64) -> Result<(), BlockError> { // NOTE This error might be returned only in early stage of verification (Stage 1). // Returning it further won't recover the sync process. trace!(target: "engine", "verify_timestamp: block too early"); - let oob = oob.map(|n| SystemTime::now() + Duration::from_secs(n)); - Err(BlockError::TemporarilyInvalid(oob).into()) + + let now = SystemTime::now(); + let found = CheckedSystemTime::checked_add(now, Duration::from_secs(oob.found)) + .ok_or(BlockError::TimestampOverflow)?; + let max = oob.max.and_then(|m| CheckedSystemTime::checked_add(now, Duration::from_secs(m))); + let min = oob.max.and_then(|m| CheckedSystemTime::checked_add(now, Duration::from_secs(m))); + + let new_oob = OutOfBounds { min, max, found }; + + Err(BlockError::TemporarilyInvalid(new_oob).into()) }, Ok(_) => Ok(()), } @@ -607,6 +616,7 @@ fn combine_proofs(signal_number: BlockNumber, set_proof: &[u8], finality_proof: stream.out() } + fn destructure_proofs(combined: &[u8]) -> Result<(BlockNumber, &[u8], &[u8]), Error> { let rlp = Rlp::new(combined); Ok(( @@ -622,7 +632,7 @@ trait AsMillis { impl AsMillis for Duration { fn as_millis(&self) -> u64 { - self.as_secs()*1_000 + (self.subsec_nanos()/1_000_000) as u64 + self.as_secs() * 1_000 + (self.subsec_nanos() / 1_000_000) as u64 } } diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 0fe2e0db19c..05067f84810 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -100,6 +100,7 @@ extern crate rlp; extern crate rustc_hex; extern crate serde; extern crate stats; +extern crate time_utils; extern crate triehash_ethereum as triehash; extern crate unexpected; extern crate using_queue; diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 3f5008a2b86..9438aab1d4e 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -36,29 +36,11 @@ use call_contract::CallContract; use client::BlockInfo; use engines::EthEngine; use error::{BlockError, Error}; +use time_utils::CheckedSystemTime; use types::{BlockNumber, header::Header}; use types::transaction::SignedTransaction; use verification::queue::kind::blocks::Unverified; - -/// Returns `Ok` when the result less or equal to `i32::max_value` to prevent `SystemTime` to panic because -/// it is platform specific, may be i32 or i64. -/// -/// `Err Result { - let d1 = sys.duration_since(UNIX_EPOCH).map_err(|_| BlockError::TimestampOverflow)?; - let total_time = d1.checked_add(d2).ok_or(BlockError::TimestampOverflow)?; - - if total_time.as_secs() <= i32::max_value() as u64 { - Ok(sys + d2) - } else { - Err(BlockError::TimestampOverflow) - } -} - /// Preprocessed block data gathered in `verify_block_unordered` call pub struct PreverifiedBlock { /// Populated block header @@ -323,9 +305,11 @@ pub fn verify_header_params(header: &Header, engine: &EthEngine, is_full: bool, if is_full { const ACCEPTABLE_DRIFT: Duration = Duration::from_secs(15); + // this will resist overflow until `year 2037` let max_time = SystemTime::now() + ACCEPTABLE_DRIFT; let invalid_threshold = max_time + ACCEPTABLE_DRIFT * 9; - let timestamp = timestamp_checked_add(UNIX_EPOCH, Duration::from_secs(header.timestamp()))?; + let timestamp = CheckedSystemTime::checked_add(UNIX_EPOCH, Duration::from_secs(header.timestamp())) + .ok_or(BlockError::TimestampOverflow)?; if timestamp > invalid_threshold { return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: Some(max_time), min: None, found: timestamp }))) @@ -347,8 +331,10 @@ fn verify_parent(header: &Header, parent: &Header, engine: &EthEngine) -> Result let gas_limit_divisor = engine.params().gas_limit_bound_divisor; if !engine.is_timestamp_valid(header.timestamp(), parent.timestamp()) { - let min = timestamp_checked_add(SystemTime::now(), Duration::from_secs(parent.timestamp().saturating_add(1)))?; - let found = timestamp_checked_add(SystemTime::now(), Duration::from_secs(header.timestamp()))?; + let min = CheckedSystemTime::checked_add(SystemTime::now(), Duration::from_secs(parent.timestamp().saturating_add(1))) + .ok_or(BlockError::TimestampOverflow)?; + let found = CheckedSystemTime::checked_add(SystemTime::now(), Duration::from_secs(header.timestamp())) + .ok_or(BlockError::TimestampOverflow)?; return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: None, min: Some(min), found }))) } if header.number() != parent.number() + 1 { @@ -835,11 +821,4 @@ mod tests { check_fail(unordered_test(&create_test_block_with_data(&header, &bad_transactions, &[]), &engine), TooManyTransactions(keypair.address())); unordered_test(&create_test_block_with_data(&header, &good_transactions, &[]), &engine).unwrap(); } - - #[test] - fn checked_add_systime_dur() { - assert!(timestamp_checked_add(UNIX_EPOCH, Duration::new(i32::max_value() as u64 + 1, 0)).is_err()); - assert!(timestamp_checked_add(UNIX_EPOCH, Duration::new(i32::max_value() as u64, 0)).is_ok()); - assert!(timestamp_checked_add(UNIX_EPOCH, Duration::new(i32::max_value() as u64 - 1, 1_000_000_000)).is_ok()); - } } diff --git a/util/time-utils/Cargo.toml b/util/time-utils/Cargo.toml new file mode 100644 index 00000000000..38e7dd02c68 --- /dev/null +++ b/util/time-utils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "time-utils" +version = "0.1.0" +authors = ["Parity Technologies "] +description = "Time utilities for checked arithmetic" +license = "GPL3" +edition = "2018" + +[dependencies] diff --git a/util/time-utils/src/lib.rs b/util/time-utils/src/lib.rs new file mode 100644 index 00000000000..6af5424936b --- /dev/null +++ b/util/time-utils/src/lib.rs @@ -0,0 +1,62 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +/// Temporary trait for `SystemTime::checked_add` until `Rust 1.33` is available +pub trait CheckedSystemTime { + /// Returns `Some` when the result less or equal to `i32::max_value` to prevent `SystemTime` to panic because + /// it is platform specific, possible representations are i32, i64, u64 and Duration. `None` otherwise + fn checked_add(self, _d: Duration) -> Option; + /// Returns `Some` when the result is successful and `None` when it is not + fn checked_sub(self, _d: Duration) -> Option; +} + +impl CheckedSystemTime for SystemTime { + fn checked_add(self, dur: Duration) -> Option { + let this_dur = self.duration_since(UNIX_EPOCH).ok()?; + let total_time = this_dur.checked_add(dur)?; + + if total_time.as_secs() <= i32::max_value() as u64 { + Some(self + dur) + } else { + None + } + } + + fn checked_sub(self, dur: Duration) -> Option { + let this_dur = self.duration_since(UNIX_EPOCH).ok()?; + let total_time = this_dur.checked_sub(dur)?; + + if total_time.as_secs() <= i32::max_value() as u64 { + Some(self - dur) + } else { + None + } + } +} + +/// Temporary trait for `Duration::as_millis` until `Rust 1.33` is available +pub trait AsMillis { + /// Duration represented as milliseconds + fn as_millis(&self) -> u128; +} + +impl AsMillis for Duration { + fn as_millis(&self) -> u128 { + self.as_secs() as u128 * 1_000 + (self.subsec_nanos() / 1_000_000) as u128 + } +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + use super::CheckedSystemTime; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; + + assert!(CheckedSystemTime::checked_add(UNIX_EPOCH, Duration::new(i32::max_value() as u64 + 1, 0)).is_none()); + assert!(CheckedSystemTime::checked_add(UNIX_EPOCH, Duration::new(i32::max_value() as u64, 0)).is_some()); + assert!(CheckedSystemTime::checked_add(UNIX_EPOCH, Duration::new(i32::max_value() as u64 - 1, 1_000_000_000)).is_some()); + + assert!(CheckedSystemTime::checked_sub(UNIX_EPOCH, Duration::from_secs(120)).is_none()); + assert!(CheckedSystemTime::checked_sub(SystemTime::now(), Duration::from_secs(1000)).is_some()); + } +} diff --git a/whisper/Cargo.toml b/whisper/Cargo.toml index 5c3548a50c8..97aa8f23ac3 100644 --- a/whisper/Cargo.toml +++ b/whisper/Cargo.toml @@ -24,6 +24,7 @@ serde_json = "1.0" slab = "0.3" smallvec = "0.6" tiny-keccak = "1.4" +time-utils = { path = "../util/time-utils" } jsonrpc-core = "10.0.1" jsonrpc-derive = "10.0.2" diff --git a/whisper/src/lib.rs b/whisper/src/lib.rs index 0e79946d9a5..ee2c6d21362 100644 --- a/whisper/src/lib.rs +++ b/whisper/src/lib.rs @@ -32,6 +32,7 @@ extern crate serde; extern crate slab; extern crate smallvec; extern crate tiny_keccak; +extern crate time_utils; extern crate jsonrpc_core; extern crate jsonrpc_derive; diff --git a/whisper/src/message.rs b/whisper/src/message.rs index f8e8565a8eb..d559bfdb0a9 100644 --- a/whisper/src/message.rs +++ b/whisper/src/message.rs @@ -23,6 +23,7 @@ use ethereum_types::{H256, H512}; use rlp::{self, DecoderError, RlpStream, Rlp}; use smallvec::SmallVec; use tiny_keccak::{keccak256, Keccak}; +use time_utils::CheckedSystemTime; /// Work-factor proved. Takes 3 parameters: size of message, time to live, /// and hash. @@ -117,6 +118,7 @@ pub enum Error { EmptyTopics, LivesTooLong, IssuedInFuture, + TimestampOverflow, ZeroTTL, } @@ -133,6 +135,7 @@ impl fmt::Display for Error { Error::LivesTooLong => write!(f, "Message claims to be issued before the unix epoch."), Error::IssuedInFuture => write!(f, "Message issued in future."), Error::ZeroTTL => write!(f, "Message live for zero time."), + Error::TimestampOverflow => write!(f, "Timestamp overflow"), Error::EmptyTopics => write!(f, "Message has no topics."), } } @@ -255,11 +258,11 @@ pub struct Message { impl Message { /// Create a message from creation parameters. /// Panics if TTL is 0. - pub fn create(params: CreateParams) -> Result { + pub fn create(params: CreateParams) -> Result { use byteorder::{BigEndian, ByteOrder}; use rand::{Rng, SeedableRng, XorShiftRng}; - if params.topics.is_empty() { return Err(EmptyTopics) } + if params.topics.is_empty() { return Err(Error::EmptyTopics) } let mut rng = { let mut thread_rng = ::rand::thread_rng(); @@ -270,7 +273,8 @@ impl Message { assert!(params.ttl > 0); let expiry = { - let after_mining = SystemTime::now() + Duration::from_millis(params.work); + let after_mining = CheckedSystemTime::checked_add(SystemTime::now(), Duration::from_millis(params.work)). + ok_or(Error::TimestampOverflow)?; let since_epoch = after_mining.duration_since(time::UNIX_EPOCH) .expect("time after now is after unix epoch; qed"); @@ -357,7 +361,10 @@ impl Message { (envelope.expiry - envelope.ttl).saturating_sub(LEEWAY_SECONDS) ); - if time::UNIX_EPOCH + issue_time_adjusted > now { + let issue_time_adjusted = CheckedSystemTime::checked_add(time::UNIX_EPOCH, issue_time_adjusted) + .ok_or(Error::TimestampOverflow)?; + + if issue_time_adjusted > now { return Err(Error::IssuedInFuture); } @@ -400,8 +407,8 @@ impl Message { } /// Get the expiry time. - pub fn expiry(&self) -> SystemTime { - time::UNIX_EPOCH + Duration::from_secs(self.envelope.expiry) + pub fn expiry(&self) -> Option { + CheckedSystemTime::checked_add(time::UNIX_EPOCH, Duration::from_secs(self.envelope.expiry)) } /// Get the topics. diff --git a/whisper/src/net/mod.rs b/whisper/src/net/mod.rs index 64431d14263..d263f6cfbd9 100644 --- a/whisper/src/net/mod.rs +++ b/whisper/src/net/mod.rs @@ -223,7 +223,10 @@ impl Messages { } } - let expiry = message.expiry(); + let expiry = match message.expiry() { + Some(time) => time, + _ => return false, + }; self.cumulative_size += message.encoded_size(); @@ -232,8 +235,8 @@ impl Messages { let sorted_entry = SortedEntry { slab_id: id, - work_proved: work_proved, - expiry: expiry, + work_proved, + expiry, }; match self.sorted.binary_search(&sorted_entry) {