aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake')
-rw-r--r--src/remake/api.rs19
-rw-r--r--src/remake/mod.rs4
2 files changed, 22 insertions, 1 deletions
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<RetentionRequest> 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<H: Host>(
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<Output = crate::swarm::skew::Skew> + 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<String>;