Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

fix(extract timestamp_checked_add as lib) #10383

Merged
merged 4 commits into from
Mar 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
1 change: 1 addition & 0 deletions ethcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
17 changes: 14 additions & 3 deletions ethcore/src/engines/authority_round/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should return error on failing checked_add here (same for min but max would have failed before). I do not know the authority round logic enough to say.
At the same time if it goes over max value we got a natural bound check from checked_add so it is probably not an issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we shouldn't because you will get an error message something like:

OutOfBounds { min: None, max: None, found: val }

which should be more informative than TimestampOverflow

However I think it would be better represent those OutOfBounds as Duration instead because they are easier to reason about i.e, will always represent seconds as u64!

Thoughts?

Copy link
Contributor

@cheme cheme Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe saturating_add would fit better, my fear was that we were removing a upper bound by making this change but thinking twice it goes directly into error so it does not matter and you are right it only depends upon what we want to display, so it is ok as it is.
Edit: does not seems to be use for something else than display in trace.

Copy link
Collaborator Author

@niklasad1 niklasad1 Mar 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I get your point saturating_add is a good idea but it is very messy for SystemTime

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(()),
}
Expand Down Expand Up @@ -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((
Expand All @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duration::as_millis is available since rust 1.33, though it returns u128. Do we want to remove this trait? (Maybe out of scope of this PR)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to keep it out this PR. If people agree with this approach it should be easy just to move the AsMillis trait to the time_util library. However, the implementation above truncates Duration into u64 which we don't want in the library.

Seems to be ok for the use case in authority_round

}
}

Expand Down
4 changes: 4 additions & 0 deletions ethcore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.

#![warn(missing_docs, unused_extern_crates)]
#![cfg_attr(feature = "time_checked_add", feature(time_checked_add))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not find a use for this line (just tested stable and nightly with changing checked_add name and commenting this line: nightly pass, stable fail because of changed name).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can remove those if @sorpaas agree


//! Ethcore library
//!
Expand Down Expand Up @@ -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;
Expand Down
38 changes: 10 additions & 28 deletions ethcore/src/verification/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,8 @@ use types::{BlockNumber, header::Header};
use types::transaction::SignedTransaction;
use verification::queue::kind::blocks::Unverified;


/// Returns `Ok<SystemTime>` 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<BlockError::TimestampOver` otherwise.
///
// FIXME: @niklasad1 - remove this when and use `SystemTime::checked_add`
// when https://github.com/rust-lang/rust/issues/55940 is stabilized.
fn timestamp_checked_add(sys: SystemTime, d2: Duration) -> Result<SystemTime, BlockError> {
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 {
Expand Down Expand Up @@ -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 })))
Expand All @@ -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 {
Expand Down Expand Up @@ -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());
}
}
9 changes: 9 additions & 0 deletions util/time-utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "time-utils"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Time utilities for checked arithmetic"
license = "GPL3"
edition = "2018"

[dependencies]
50 changes: 50 additions & 0 deletions util/time-utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<SystemTime>` 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<SystemTime>;
/// Returns `Some<SystemTime>` when the result is successful and `None` when it is not
fn checked_sub(self, _d: Duration) -> Option<SystemTime>;
}

impl CheckedSystemTime for SystemTime {
fn checked_add(self, dur: Duration) -> Option<SystemTime> {
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 {
Copy link
Member

@seunlanlege seunlanlege Feb 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so basically we can only add a maximum of 68 years to SystemTime with this method, just curious why this limit.

Copy link
Collaborator Author

@niklasad1 niklasad1 Feb 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, good question Seun :P

It is because SystemTime represents seconds as i32, i64, u64 or Duration (traditional OS's represent those as time_t i32 or i64).

This basically prevents the overflow in easy-way (but limits all platforms with limit of 2038) i.e, not have to rely on any complicated conditional compilation.

But this should be removed/deprecated when Rust 1.33 is available (2019-02-28)

Some(self + dur)
} else {
None
}
}

fn checked_sub(self, dur: Duration) -> Option<SystemTime> {
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());
}
}
1 change: 1 addition & 0 deletions whisper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions whisper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
25 changes: 15 additions & 10 deletions whisper/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -117,6 +120,7 @@ pub enum Error {
EmptyTopics,
LivesTooLong,
IssuedInFuture,
TimestampOverflow,
ZeroTTL,
}

Expand All @@ -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."),
}
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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<Self, EmptyTopics> {
pub fn create(params: CreateParams) -> Result<Self, Error> {
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();
Expand All @@ -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");

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<SystemTime> {
time::UNIX_EPOCH.checked_add(Duration::from_secs(self.envelope.expiry))
}

/// Get the topics.
Expand Down
9 changes: 6 additions & 3 deletions whisper/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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) {
Expand Down