From 037fd1b309d7769e8eb9f5d8dc54978c158c4fdb Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 19 Mar 2019 23:17:05 +0100 Subject: [PATCH] fix(extract `timestamp_checked_add` as lib) (#10383) * fix(extract `timestamp_checked_add` as lib) * fix(whisper types): remove unused `EmptyTopics` * fix(time-lib): feature-flag to use time-lib or std This commit adds conditional compilation checks that falls back to `our time-lib` when `time_checked_add` is not available in the standard library Note, `time_checked_add` covers both `checked_add` and `checked_sub` * fix(grumble): use cfg_attr to define rustc feature --- Cargo.lock | 6 +++ Cargo.toml | 3 +- ethcore/Cargo.toml | 1 + ethcore/src/engines/authority_round/mod.rs | 17 ++++++-- ethcore/src/lib.rs | 4 ++ ethcore/src/verification/verification.rs | 38 +++++----------- util/time-utils/Cargo.toml | 9 ++++ util/time-utils/src/lib.rs | 50 ++++++++++++++++++++++ whisper/Cargo.toml | 1 + whisper/src/lib.rs | 5 +++ whisper/src/message.rs | 25 ++++++----- whisper/src/net/mod.rs | 9 ++-- 12 files changed, 123 insertions(+), 45 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 36c723bbd81..5902aa786cd 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-db 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "trie-standardmap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2817,6 +2818,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)", ] @@ -3772,6 +3774,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 ee4440dd010..a783175607f 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 2d26ba03814..3b8d60af8e9 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -63,6 +63,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 65ea2f77989..36f25144f73 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -47,6 +47,9 @@ use types::header::{Header, ExtendedHeader}; use types::ancestry_action::AncestryAction; use unexpected::{Mismatch, OutOfBounds}; +#[cfg(not(time_checked_add))] +use time_utils::CheckedSystemTime; + mod finality; /// `AuthorityRound` params. @@ -574,8 +577,15 @@ 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 = now.checked_add(Duration::from_secs(oob.found)).ok_or(BlockError::TimestampOverflow)?; + let max = oob.max.and_then(|m| now.checked_add(Duration::from_secs(m))); + let min = oob.min.and_then(|m| now.checked_add(Duration::from_secs(m))); + + let new_oob = OutOfBounds { min, max, found }; + + Err(BlockError::TemporarilyInvalid(new_oob).into()) }, Ok(_) => Ok(()), } @@ -611,6 +621,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(( @@ -626,7 +637,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 17b33025a1c..eee076b32ea 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -15,6 +15,7 @@ // along with Parity Ethereum. If not, see . #![warn(missing_docs, unused_extern_crates)] +#![cfg_attr(feature = "time_checked_add", feature(time_checked_add))] //! Ethcore library //! @@ -148,6 +149,9 @@ extern crate fetch; #[cfg(all(test, feature = "price-info"))] extern crate parity_runtime; +#[cfg(not(time_checked_add))] +extern crate time_utils; + pub mod block; pub mod builtin; pub mod client; diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 8a11f0ef45b..36a847f64af 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -40,24 +40,8 @@ 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) - } -} +#[cfg(not(time_checked_add))] +use time_utils::CheckedSystemTime; /// Preprocessed block data gathered in `verify_block_unordered` call pub struct PreverifiedBlock { @@ -323,9 +307,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 = UNIX_EPOCH.checked_add(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 +333,11 @@ 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 now = SystemTime::now(); + let min = now.checked_add(Duration::from_secs(parent.timestamp().saturating_add(1))) + .ok_or(BlockError::TimestampOverflow)?; + let found = now.checked_add(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 +824,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..ff9f159cfad --- /dev/null +++ b/util/time-utils/src/lib.rs @@ -0,0 +1,50 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +/// Temporary trait for `checked operations` on SystemTime until these are available in standard library +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 + } + } +} + +#[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..cdc88780d4d 100644 --- a/whisper/src/lib.rs +++ b/whisper/src/lib.rs @@ -17,6 +17,8 @@ //! Whisper P2P messaging system as a DevP2P subprotocol, with RPC and Rust //! interface. +#![cfg_attr(feature = "time_checked_add", feature(time_checked_add))] + extern crate byteorder; extern crate parity_crypto as crypto; extern crate ethcore_network as network; @@ -46,6 +48,9 @@ extern crate log; #[macro_use] extern crate serde_derive; +#[cfg(not(time_checked_add))] +extern crate time_utils; + #[cfg(test)] extern crate serde_json; diff --git a/whisper/src/message.rs b/whisper/src/message.rs index f8e8565a8eb..0fc686e52a8 100644 --- a/whisper/src/message.rs +++ b/whisper/src/message.rs @@ -24,6 +24,9 @@ use rlp::{self, DecoderError, RlpStream, Rlp}; use smallvec::SmallVec; use tiny_keccak::{keccak256, Keccak}; +#[cfg(not(time_checked_add))] +use time_utils::CheckedSystemTime; + /// Work-factor proved. Takes 3 parameters: size of message, time to live, /// and hash. /// @@ -117,6 +120,7 @@ pub enum Error { EmptyTopics, LivesTooLong, IssuedInFuture, + TimestampOverflow, ZeroTTL, } @@ -133,6 +137,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."), } } @@ -226,10 +231,6 @@ impl rlp::Decodable for Envelope { } } -/// Error indicating no topics. -#[derive(Debug, Copy, Clone)] -pub struct EmptyTopics; - /// Message creation parameters. /// Pass this to `Message::create` to make a message. pub struct CreateParams { @@ -255,11 +256,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 +271,8 @@ impl Message { assert!(params.ttl > 0); let expiry = { - let after_mining = SystemTime::now() + Duration::from_millis(params.work); + let after_mining = SystemTime::now().checked_sub(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 +359,10 @@ impl Message { (envelope.expiry - envelope.ttl).saturating_sub(LEEWAY_SECONDS) ); - if time::UNIX_EPOCH + issue_time_adjusted > now { + let issue_time_adjusted = time::UNIX_EPOCH.checked_add(issue_time_adjusted) + .ok_or(Error::TimestampOverflow)?; + + if issue_time_adjusted > now { return Err(Error::IssuedInFuture); } @@ -400,8 +405,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 { + time::UNIX_EPOCH.checked_add(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) {