From 7bedbda025f03fd5dfca833171c55867d8c0478e Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 31 Oct 2026 12:14:12 +0100 Subject: remake needs sane clock --- src/remake/api.rs | 19 ++++++++++++++++++- src/remake/mod.rs | 4 ++++ 2 files changed, 22 insertions(+), 1 deletion(-) (limited to 'src/remake') diff --git a/src/remake/api.rs b/src/remake/api.rs index f919be7..75d0294 100644 --- a/src/remake/api.rs +++ b/src/remake/api.rs @@ -32,6 +32,8 @@ //! not for real swarms); //! * `supersede`: abort expired remakes of this epoch and propose in their place. //! Without it, an expired remake still holding votes answers `409`; +//! * `clock`: propose although this node's clock is off from its peers' +//! (`swarm::skew`): it would judge the remake's deadlines wrongly. Without it, `409`; //! * for `DELETE`: `coordinatorActive` (abort an expired remake whose coordinator this //! node sees as active) and `coordinatorInactive` (ask for an abort of an undecided //! one whose coordinator this node doesn't reach). @@ -200,7 +202,7 @@ impl From for Retention { } /// What `?force=` may override on a proposal (`force`). -const FORCE: &[&str] = &["switchDelay", "supersede", "coordinatorActive"]; +const FORCE: &[&str] = &["switchDelay", "supersede", "coordinatorActive", "clock"]; /// What `?force=` may override on an abort. const ABORT_FORCE: &[&str] = &["coordinatorActive", "coordinatorInactive"]; @@ -295,6 +297,21 @@ async fn start_kind( Ok(force) => force, Err(reason) => return error_response(StatusCode::BAD_REQUEST, reason, None), }; + // The coordinator stamps and judges the voting period and the deadlines by its + // clock (`trust.md`, "What depends on clocks"). + let skew = host.clock_skew().await; + if skew.off && !force.allows("clock") { + let offset = skew.offset_ms.unwrap_or(0); + return error_response( + StatusCode::CONFLICT, + format!( + "this node's clock is {} {} most of its peers' (more than --clock-skew-warn), so it would judge the remake's deadlines wrongly; fix its time synchronisation, or add ?force=clock", + crate::units::format_duration(Duration::from_millis(offset.unsigned_abs())), + if offset < 0 { "behind" } else { "ahead of" } + ), + Some(json!({ "force": "clock" })), + ); + } let request: StartRequest = if is_octet_stream(headers) { match query.import_request(&body) { diff --git a/src/remake/mod.rs b/src/remake/mod.rs index 5042ce9..c77d56d 100644 --- a/src/remake/mod.rs +++ b/src/remake/mod.rs @@ -123,6 +123,10 @@ pub(crate) trait Host: Clone + Send + Sync + 'static { /// Default basis for the voting period, and the grace margin after it. fn dormant_after(&self) -> Duration; + /// This node's clock against its peers' (`swarm::skew`): a coordinator judges the + /// remake's deadlines by it. + fn clock_skew(&self) -> impl Future + Send; + /// The electorate of a remake proposed now (`vote`): the members that said they are /// alive recently enough, read from `doc`. fn electorate(&self, doc: &AutoCommit, now: crate::clock::UnixMillis) -> Vec; -- cgit v1.3.1