From 04ac398f959722fb6b4b83f815743d551176a966 Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Wed, 6 May 2026 12:40:07 +0200 Subject: [PATCH] fix(sync): use microseconds for MAX_TIMESTAMP_FUTURE_SHIFT Wall times are Unix epoch microseconds; deriving the cap from milliseconds limited future slack to ~600ms instead of 10 minutes. Add a regression test that inserts a 1s-ahead entry and asserts round-trip from the store. --- src/sync.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/sync.rs b/src/sync.rs index 570441ac..43a8440d 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -40,7 +40,7 @@ pub type PeerIdBytes = [u8; 32]; /// Max time in the future from our wall clock time that we accept entries for. /// Value is 10 minutes. -pub const MAX_TIMESTAMP_FUTURE_SHIFT: u64 = 10 * 60 * Duration::from_secs(1).as_millis() as u64; +pub const MAX_TIMESTAMP_FUTURE_SHIFT: u64 = 10 * 60 * Duration::from_secs(1).as_micros() as u64; /// Callback that may be set on a replica to determine the availability status for a content hash. pub type ContentStatusCallback = @@ -1781,6 +1781,32 @@ mod tests { Ok(()) } + /// Regression: `MAX_TIMESTAMP_FUTURE_SHIFT` must be on the same unit scale as + /// `system_time_now()` (Unix epoch **microseconds**). A millisecond-scaled constant + /// only allows ~0.6s of future slack and would reject this insert. + #[tokio::test] + async fn test_future_timestamp_accepts_one_second_skew() -> Result<()> { + let mut rng = rand::rng(); + let mut store = store::Store::memory(); + let author = Author::new(&mut rng); + let namespace = NamespaceSecret::new(&mut rng); + let mut replica = store.new_replica(namespace.clone())?; + let key = b"skew"; + let skew_micros: u64 = Duration::from_secs(1).as_micros() as u64; + let now = system_time_now(); + let record = Record::from_data(b"ahead", now + skew_micros); + let entry = SignedEntry::from_parts(&namespace, &author, key, record); + replica + .insert_entry(entry.clone(), InsertOrigin::Local) + .await?; + assert_eq!( + get_entry(&mut store, namespace.id(), author.id(), key)?, + entry + ); + store.flush()?; + Ok(()) + } + #[tokio::test] async fn test_insert_empty() -> Result<()> { let mut store = store::Store::memory();