From 8fde8c1a6b4a5f100815cd1003112879adc2064c Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 17 Jun 2020 21:19:23 +1000 Subject: [PATCH] consensus: Add tests for block node time checks Part of #477. --- zebra-consensus/src/verify/block.rs | 136 ++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/zebra-consensus/src/verify/block.rs b/zebra-consensus/src/verify/block.rs index 4543cff96..6f27c16be 100644 --- a/zebra-consensus/src/verify/block.rs +++ b/zebra-consensus/src/verify/block.rs @@ -40,3 +40,139 @@ fn node_time_check_helper( pub(super) fn node_time_check(block: Arc) -> Result<(), Error> { node_time_check_helper(block.header.time, Utc::now()) } + +#[cfg(test)] +mod tests { + use super::*; + use chrono::offset::{LocalResult, TimeZone}; + use zebra_chain::serialization::ZcashDeserialize; + + #[test] + fn time_check_past_block() { + // This block is also verified as part of the BlockVerifier service + // tests. + let block = + Arc::::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_415000_BYTES[..]) + .expect("block should deserialize"); + + // This check is non-deterministic, but BLOCK_MAINNET_415000 is + // a long time in the past. So it's unlikely that the test machine + // will have a clock that's far enough in the past for the test to + // fail. + node_time_check(block).expect("the header time from a mainnet block should be valid"); + } + + #[test] + fn time_check_now() { + // These checks are deteministic, because all the times are offset + // from the current time. + let now = Utc::now(); + let three_hours_in_the_past = now - Duration::hours(3); + let two_hours_in_the_future = now + Duration::hours(2); + let two_hours_and_one_second_in_the_future = + now + Duration::hours(2) + Duration::seconds(1); + + node_time_check_helper(now, now) + .expect("the current time should be valid as a block header time"); + node_time_check_helper(three_hours_in_the_past, now) + .expect("a past time should be valid as a block header time"); + node_time_check_helper(two_hours_in_the_future, now) + .expect("2 hours in the future should be valid as a block header time"); + node_time_check_helper(two_hours_and_one_second_in_the_future, now).expect_err( + "2 hours and 1 second in the future should be invalid as a block header time", + ); + + // Now invert the tests + // 3 hours in the future should fail + node_time_check_helper(now, three_hours_in_the_past) + .expect_err("3 hours in the future should be invalid as a block header time"); + // The past should succeed + node_time_check_helper(now, two_hours_in_the_future) + .expect("2 hours in the past should be valid as a block header time"); + node_time_check_helper(now, two_hours_and_one_second_in_the_future) + .expect("2 hours and 1 second in the past should be valid as a block header time"); + } + + /// Valid unix epoch timestamps for blocks, in seconds + static BLOCK_HEADER_VALID_TIMESTAMPS: &[i64] = &[ + // These times are currently invalid DateTimes, but they could + // become valid in future chrono versions + i64::MIN, + i64::MIN + 1, + // These times are valid DateTimes + (u32::MIN as i64) - 1, + (u32::MIN as i64), + (u32::MIN as i64) + 1, + (i32::MIN as i64) - 1, + (i32::MIN as i64), + (i32::MIN as i64) + 1, + -1, + 0, + 1, + // maximum nExpiryHeight or lock_time, in blocks + 499_999_999, + // minimum lock_time, in seconds + 500_000_000, + 500_000_001, + ]; + + /// Invalid unix epoch timestamps for blocks, in seconds + static BLOCK_HEADER_INVALID_TIMESTAMPS: &[i64] = &[ + (i32::MAX as i64) - 1, + (i32::MAX as i64), + (i32::MAX as i64) + 1, + (u32::MAX as i64) - 1, + (u32::MAX as i64), + (u32::MAX as i64) + 1, + // These times are currently invalid DateTimes, but they could + // become valid in future chrono versions + i64::MAX - 1, + i64::MAX, + ]; + + #[test] + fn time_check_fixed() { + // These checks are non-deterministic, but the times are all in the + // distant past or far future. So it's unlikely that the test + // machine will have a clock that makes these tests fail. + let now = Utc::now(); + + for valid_timestamp in BLOCK_HEADER_VALID_TIMESTAMPS { + let block_header_time = match Utc.timestamp_opt(*valid_timestamp, 0) { + LocalResult::Single(time) => time, + LocalResult::None => { + // Skip the test if the timestamp is invalid + continue; + } + LocalResult::Ambiguous(_, _) => { + // Utc doesn't have ambiguous times + unreachable!(); + } + }; + node_time_check_helper(block_header_time, now) + .expect("the time should be valid as a block header time"); + // Invert the check, leading to an invalid time + node_time_check_helper(now, block_header_time) + .expect_err("the inverse comparison should be invalid"); + } + + for invalid_timestamp in BLOCK_HEADER_INVALID_TIMESTAMPS { + let block_header_time = match Utc.timestamp_opt(*invalid_timestamp, 0) { + LocalResult::Single(time) => time, + LocalResult::None => { + // Skip the test if the timestamp is invalid + continue; + } + LocalResult::Ambiguous(_, _) => { + // Utc doesn't have ambiguous times + unreachable!(); + } + }; + node_time_check_helper(block_header_time, now) + .expect_err("the time should be invalid as a block header time"); + // Invert the check, leading to a valid time + node_time_check_helper(now, block_header_time) + .expect("the inverse comparison should be valid"); + } + } +}