Skip to content

Commit ff71dd8

Browse files
authored
Merge pull request openethereum#65 from niklasad1/checked-systemtime
prevent SystemTime panics
2 parents da06739 + ea5d77f commit ff71dd8

File tree

2 files changed

+59
-23
lines changed

2 files changed

+59
-23
lines changed

ethcore/src/engines/clique/block_state.rs

+26-7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ use types::BlockNumber;
2828
use types::header::Header;
2929
use unexpected::Mismatch;
3030

31+
#[cfg(not(feature = "time_checked_add"))]
32+
use time_utils::CheckedSystemTime;
33+
3134
/// Type that keeps track of the state for a given vote
3235
// Votes that go against the proposal aren't counted since it's equivalent to not voting
3336
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
@@ -259,29 +262,44 @@ impl CliqueBlockState {
259262
Ok(())
260263
}
261264

262-
pub fn calc_next_timestamp(&mut self, header: &Header, period: u64) {
263-
let base_time = UNIX_EPOCH + Duration::from_secs(header.timestamp());
265+
/// Calculate the next timestamp for `inturn` and `noturn` fails if any of them can't be represented as
266+
/// `SystemTime`
267+
// TODO(niklasad1): refactor this method to be in constructor of `CliqueBlockState` instead.
268+
// This is a quite bad API because we must mutate both variables even when already `inturn` fails
269+
// That's why we can't return early and must have the `if-else` in the end
270+
pub fn calc_next_timestamp(&mut self, timestamp: u64, period: u64) -> Result<(), Error> {
271+
let inturn = UNIX_EPOCH.checked_add(Duration::from_secs(timestamp.saturating_add(period)));
264272

265-
self.next_timestamp_inturn = Some(base_time + Duration::from_secs(period));
273+
self.next_timestamp_inturn = inturn;
266274

267275
let delay = Duration::from_millis(
268276
rand::thread_rng().gen_range(0u64, (self.signers.len() as u64 / 2 + 1) * SIGNING_DELAY_NOTURN_MS));
269-
self.next_timestamp_noturn = Some(base_time + Duration::from_secs(period) + delay);
277+
self.next_timestamp_noturn = inturn.map(|inturn| {
278+
inturn + delay
279+
});
280+
281+
if self.next_timestamp_inturn.is_some() && self.next_timestamp_noturn.is_some() {
282+
Ok(())
283+
} else {
284+
Err(BlockError::TimestampOverflow)?
285+
}
270286
}
271287

288+
/// Returns true if the block difficulty should be `inturn`
272289
pub fn is_inturn(&self, current_block_number: u64, author: &Address) -> bool {
273290
if let Some(pos) = self.signers.iter().position(|x| *author == *x) {
274291
return current_block_number % self.signers.len() as u64 == pos as u64;
275292
}
276293
false
277294
}
278295

296+
/// Returns whether the signer is authorized to sign a block
279297
pub fn is_authorized(&self, author: &Address) -> bool {
280298
self.signers.contains(author) && !self.recent_signers.contains(author)
281299
}
282300

283-
// Returns whether it makes sense to cast the specified vote in the
284-
// current state (e.g. don't try to add an already authorized signer).
301+
/// Returns whether it makes sense to cast the specified vote in the
302+
/// current state (e.g. don't try to add an already authorized signer).
285303
pub fn is_valid_vote(&self, address: &Address, vote_type: VoteType) -> bool {
286304
let in_signer = self.signers.contains(address);
287305
match vote_type {
@@ -290,11 +308,12 @@ impl CliqueBlockState {
290308
}
291309
}
292310

311+
/// Returns the list of current signers
293312
pub fn signers(&self) -> &BTreeSet<Address> {
294313
&self.signers
295314
}
296315

297-
// Note this method will always return `true` but it is intended for a unifrom `API`
316+
// Note this method will always return `true` but it is intended for a uniform `API`
298317
fn add_vote(&mut self, pending_vote: PendingVote, kind: VoteType) -> bool {
299318

300319
self.votes.entry(pending_vote)

ethcore/src/engines/clique/mod.rs

+33-16
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ use unexpected::{Mismatch, OutOfBounds};
7979
use types::BlockNumber;
8080
use types::header::{ExtendedHeader, Header};
8181

82+
#[cfg(not(feature = "time_checked_add"))]
83+
use time_utils::CheckedSystemTime;
84+
8285
use self::block_state::CliqueBlockState;
8386
use self::params::CliqueParams;
8487
use self::step_service::StepService;
@@ -241,7 +244,8 @@ impl Clique {
241244
let mut state = CliqueBlockState::new(
242245
extract_signers(header)?);
243246

244-
state.calc_next_timestamp(header, self.period);
247+
// TODO(niklasad1): refactor to perform this check in the `CliqueBlockState` constructor instead
248+
state.calc_next_timestamp(header.timestamp(), self.period)?;
245249

246250
Ok(state)
247251
}
@@ -326,7 +330,7 @@ impl Clique {
326330
for item in chain {
327331
new_state.apply(item, false)?;
328332
}
329-
new_state.calc_next_timestamp(header, self.period);
333+
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
330334
block_state_by_hash.insert(header.hash(), new_state.clone());
331335

332336
let elapsed = backfill_start.elapsed();
@@ -435,7 +439,7 @@ impl Engine<EthereumMachine> for Clique {
435439
// locally sealed block don't go through valid_block_family(), so we have to record state here.
436440
let mut new_state = state.clone();
437441
new_state.apply(&header, is_checkpoint)?;
438-
new_state.calc_next_timestamp(&header, self.period);
442+
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
439443
self.block_state_by_hash.write().insert(header.hash(), new_state);
440444

441445
trace!(target: "engine", "on_seal_block: finished, final header: {:?}", header);
@@ -531,15 +535,23 @@ impl Engine<EthereumMachine> for Clique {
531535

532536
// Don't waste time checking blocks from the future
533537
{
534-
// TODO(niklasad): checked_add
535-
let limit = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default() + Duration::from_secs(self.period);
538+
let limit = SystemTime::now().checked_add(Duration::from_secs(self.period))
539+
.ok_or(BlockError::TimestampOverflow)?;
540+
541+
// This should succeed under the contraints that the system clock works
542+
let limit_as_dur = limit.duration_since(UNIX_EPOCH).map_err(|e| {
543+
Box::new(format!("Converting SystemTime to Duration failed: {}", e))
544+
})?;
545+
536546
let hdr = Duration::from_secs(header.timestamp());
537-
if hdr > limit {
538-
return Err(BlockError::TemporarilyInvalid(OutOfBounds {
547+
if hdr > limit_as_dur {
548+
let found = UNIX_EPOCH.checked_add(hdr).ok_or(BlockError::TimestampOverflow)?;
549+
550+
Err(BlockError::TemporarilyInvalid(OutOfBounds {
539551
min: None,
540-
max: Some(UNIX_EPOCH + limit),
541-
found: UNIX_EPOCH + Duration::from_secs(header.timestamp()),
542-
}))?;
552+
max: Some(limit),
553+
found,
554+
}))?
543555
}
544556
}
545557

@@ -642,11 +654,16 @@ impl Engine<EthereumMachine> for Clique {
642654
}
643655

644656
// Ensure that the block's timestamp isn't too close to it's parent
645-
if parent.timestamp().saturating_add(self.period) > header.timestamp() {
657+
let limit = parent.timestamp().saturating_add(self.period);
658+
if limit > header.timestamp() {
659+
let max = UNIX_EPOCH.checked_add(Duration::from_secs(header.timestamp()));
660+
let found = UNIX_EPOCH.checked_add(Duration::from_secs(limit))
661+
.ok_or(BlockError::TimestampOverflow)?;
662+
646663
Err(BlockError::InvalidTimestamp(OutOfBounds {
647-
min: Some(UNIX_EPOCH + Duration::from_secs(parent.timestamp().saturating_add(self.period))),
648-
max: None,
649-
found: UNIX_EPOCH + Duration::from_secs(header.timestamp())
664+
min: None,
665+
max,
666+
found,
650667
}))?
651668
}
652669

@@ -655,15 +672,15 @@ impl Engine<EthereumMachine> for Clique {
655672
// Try to apply current state, apply() will further check signer and recent signer.
656673
let mut new_state = parent_state.clone();
657674
new_state.apply(header, header.number() % self.epoch_length == 0)?;
658-
new_state.calc_next_timestamp(header, self.period);
675+
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
659676
self.block_state_by_hash.write().insert(header.hash(), new_state);
660677

661678
Ok(())
662679
}
663680

664681
fn genesis_epoch_data(&self, header: &Header, _call: &Call) -> Result<Vec<u8>, String> {
665682
let mut state = self.new_checkpoint_state(header).expect("Unable to parse genesis data.");
666-
state.calc_next_timestamp(header, self.period);
683+
state.calc_next_timestamp(header.timestamp(), self.period).map_err(|e| format!("{}", e))?;
667684
self.block_state_by_hash.write().insert(header.hash(), state);
668685

669686
// no proof.

0 commit comments

Comments
 (0)