diff --git a/Cargo.lock b/Cargo.lock
index b6c0b3ef61f..7629bac4ebc 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)",
@@ -2819,6 +2820,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)",
]
@@ -3774,6 +3776,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 31062d80f3d..b973103e1e7 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 2ce9f9de304..d5571198967 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) {