From d045b0f3e8d519ae6016536f069b26ba65064f1b Mon Sep 17 00:00:00 2001 From: iho Date: Thu, 9 Jul 2026 11:28:30 +0300 Subject: [PATCH] fix: use FutureTimeLimit instead of CorruptedData for skew MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Headers slightly beyond future_time_limit are not corrupted data — clocks differ. Return ser::Error::FutureTimeLimit so the condition is explicit and not lumped with real corruption/POW failures. Addresses #3360. --- core/src/core/block.rs | 14 ++++++++------ core/src/ser.rs | 6 ++++++ core/tests/block.rs | 26 +++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 7198e1e049..8331d04376 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -448,14 +448,16 @@ impl Readable for UntrustedBlockHeader { let header = read_block_header(reader)?; let ftl = global::get_future_time_limit(); if header.timestamp > Utc::now() + Duration::seconds(ftl as i64) { - // refuse blocks whose timestamp is too far in the future - // this future_time_limit (FTL) is specified in grin-server.toml - // TODO add warning in p2p code if local time is too different from peers - error!( - "block header {} validation error: block time is more than {} seconds in the future", + // Refuse blocks whose timestamp is too far in the future. + // FTL is configured in grin-server.toml. This is *not* corrupted data — + // local clocks differ; use a dedicated error so peers are not treated + // as sending garbage (#3360). + // TODO: warn in p2p if local time is systematically different from peers + warn!( + "block header {} rejected: block time is more than {} seconds in the future (clock skew?)", header.hash(), ftl ); - return Err(ser::Error::CorruptedData); + return Err(ser::Error::FutureTimeLimit); } // Check the block version before proceeding any further. diff --git a/core/src/ser.rs b/core/src/ser.rs index 38cd9aa1b7..9e3ee8bf18 100644 --- a/core/src/ser.rs +++ b/core/src/ser.rs @@ -72,6 +72,10 @@ pub enum Error { InvalidBlockVersion, /// Unsupported protocol version UnsupportedProtocolVersion, + /// Block/header timestamp is beyond the future time limit. + /// Not corrupted data — peer clocks may simply disagree; treat as non-fatal + /// for peer reputation (see #3360). + FutureTimeLimit, } impl From for Error { @@ -102,6 +106,7 @@ impl fmt::Display for Error { Error::HexError(ref e) => write!(f, "hex error {:?}", e), Error::InvalidBlockVersion => f.write_str("invalid block version"), Error::UnsupportedProtocolVersion => f.write_str("unsupported protocol version"), + Error::FutureTimeLimit => f.write_str("future time limit exceeded"), } } } @@ -126,6 +131,7 @@ impl error::Error for Error { Error::HexError(_) => "hex error", Error::InvalidBlockVersion => "invalid block version", Error::UnsupportedProtocolVersion => "unsupported protocol version", + Error::FutureTimeLimit => "future time limit exceeded", } } } diff --git a/core/tests/block.rs b/core/tests/block.rs index b2451ffefb..33b68163f4 100644 --- a/core/tests/block.rs +++ b/core/tests/block.rs @@ -26,7 +26,7 @@ use crate::core::core::{Committed, CompactBlock}; use crate::core::libtx::build::{self, input, output}; use crate::core::libtx::ProofBuilder; use crate::core::{global, pow, ser}; -use chrono::Duration; +use chrono::{Duration, Utc}; use grin_core as core; use keychain::{BlindingFactor, ExtKeychain, Keychain}; use util::{secp, ToHex}; @@ -466,6 +466,30 @@ fn deserialize_untrusted_header_weight() { assert!(res.is_ok()); } +/// Future timestamps must not be reported as CorruptedData (#3360). +#[test] +fn deserialize_untrusted_header_future_time() { + test_setup(); + let keychain = ExtKeychain::from_random_seed(false).unwrap(); + let builder = ProofBuilder::new(&keychain); + let prev = BlockHeader::default(); + let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); + let mut b = new_block(&[], &keychain, &builder, &prev, &key_id); + + // Far beyond future_time_limit (default 300s for AutomatedTesting? check global) + b.header.timestamp = Utc::now() + Duration::seconds(3600); + set_pow(&mut b.header); + + let mut vec = Vec::new(); + ser::serialize_default(&mut vec, &b.header).expect("serialization failed"); + let res: Result = ser::deserialize_default(&mut &vec[..]); + assert_eq!( + res.err(), + Some(ser::Error::FutureTimeLimit), + "future time must use FutureTimeLimit, not CorruptedData" + ); +} + #[test] fn serialize_deserialize_block() { test_setup();