Skip to content

Commit 8e3f2ba

Browse files
authored
fix(sync): use microseconds for MAX_TIMESTAMP_FUTURE_SHIFT (#99)
## Description Fixes future timestamp validation so the allowed skew matches the documented ten-minute policy. Entry timestamps and `system_time_now()` are Unix epoch microseconds. `MAX_TIMESTAMP_FUTURE_SHIFT` was derived using milliseconds, so the effective cap was about 600 ms instead of ten minutes. The constant now uses `Duration::from_secs(1).as_micros()` so it is expressed in the same unit as wall time. Adds `test_future_timestamp_accepts_one_second_skew`, a regression test that would fail if the slack constant were still on millisecond scale: it inserts an entry one second in the future, then asserts the stored entry matches (round-trip), not only that insert succeeds. ## Breaking Changes None ## Notes & open questions - Closes #97 - Considered introducing a UnixEpochMicros newtype and threading it through the public API so unit mix-ups are harder to repeat, but that would be a breaking change with little extra correctness beyond fixing the constant, so this PR only corrects the scale of MAX_TIMESTAMP_FUTURE_SHIFT and adds a regression test. ## Change checklist - [x] Self-review. - [ ] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text), if relevant. - [x] Tests if relevant. - [x] All breaking changes documented.
1 parent b4f985d commit 8e3f2ba

1 file changed

Lines changed: 27 additions & 1 deletion

File tree

src/sync.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub type PeerIdBytes = [u8; 32];
4040

4141
/// Max time in the future from our wall clock time that we accept entries for.
4242
/// Value is 10 minutes.
43-
pub const MAX_TIMESTAMP_FUTURE_SHIFT: u64 = 10 * 60 * Duration::from_secs(1).as_millis() as u64;
43+
pub const MAX_TIMESTAMP_FUTURE_SHIFT: u64 = 10 * 60 * Duration::from_secs(1).as_micros() as u64;
4444

4545
/// Callback that may be set on a replica to determine the availability status for a content hash.
4646
pub type ContentStatusCallback =
@@ -1785,6 +1785,32 @@ mod tests {
17851785
Ok(())
17861786
}
17871787

1788+
/// Regression: `MAX_TIMESTAMP_FUTURE_SHIFT` must be on the same unit scale as
1789+
/// `system_time_now()` (Unix epoch **microseconds**). A millisecond-scaled constant
1790+
/// only allows ~0.6s of future slack and would reject this insert.
1791+
#[tokio::test]
1792+
async fn test_future_timestamp_accepts_one_second_skew() -> Result<()> {
1793+
let mut rng = rand::rng();
1794+
let mut store = store::Store::memory();
1795+
let author = Author::new(&mut rng);
1796+
let namespace = NamespaceSecret::new(&mut rng);
1797+
let mut replica = store.new_replica(namespace.clone())?;
1798+
let key = b"skew";
1799+
let skew_micros: u64 = Duration::from_secs(1).as_micros() as u64;
1800+
let now = system_time_now();
1801+
let record = Record::from_data(b"ahead", now + skew_micros);
1802+
let entry = SignedEntry::from_parts(&namespace, &author, key, record);
1803+
replica
1804+
.insert_entry(entry.clone(), InsertOrigin::Local)
1805+
.await?;
1806+
assert_eq!(
1807+
get_entry(&mut store, namespace.id(), author.id(), key)?,
1808+
entry
1809+
);
1810+
store.flush()?;
1811+
Ok(())
1812+
}
1813+
17881814
#[tokio::test]
17891815
async fn test_insert_empty() -> Result<()> {
17901816
let mut store = store::Store::memory();

0 commit comments

Comments
 (0)