aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/api.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-01 13:30:49 +0100
committertv <tv@krebsco.de>2026-11-01 13:30:49 +0100
commitf68e417555a714bec27fcb877c07bc83cc13fe20 (patch)
treedc5164be0171e61fd50c4a14bb2a967f24aef4fd /src/remake/api.rs
parent53d132ff902ff07198b0e01f8c1a030f06aa562b (diff)
api: off clock refuses aborting expired remake
Diffstat (limited to 'src/remake/api.rs')
-rw-r--r--src/remake/api.rs43
1 files changed, 29 insertions, 14 deletions
diff --git a/src/remake/api.rs b/src/remake/api.rs
index 2cd55e5..7750242 100644
--- a/src/remake/api.rs
+++ b/src/remake/api.rs
@@ -204,7 +204,27 @@ impl From<RetentionRequest> for Retention {
/// What `?force=` may override on a proposal (`force`).
const FORCE: &[&str] = &["switchDelay", "supersede", "coordinatorActive", "clock"];
/// What `?force=` may override on an abort.
-const ABORT_FORCE: &[&str] = &["coordinatorActive", "coordinatorInactive"];
+const ABORT_FORCE: &[&str] = &["coordinatorActive", "coordinatorInactive", "clock"];
+
+/// `409` while this node's clock is off from most of its peers' (and `clock` isn't
+/// forced): it would judge a remake's deadlines wrongly, proposing one or aborting one
+/// as expired (`trust.md`, "What depends on clocks"). `what` is what it would do.
+async fn clock_obstacle<H: Host>(host: &H, force: &Force, what: &str) -> Option<Response> {
+ let skew = host.clock_skew().await;
+ if !skew.off || force.allows("clock") {
+ return None;
+ }
+ let offset = skew.offset_ms.unwrap_or(0);
+ Some(error_response(
+ StatusCode::CONFLICT,
+ format!(
+ "this node's clock is {} {} most of its peers' (more than --clock-skew-warn), so it would judge {what} 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" })),
+ ))
+}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
@@ -298,19 +318,9 @@ async fn start_kind<H: Host>(
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" })),
- );
+ // clock.
+ if let Some(refused) = clock_obstacle(&host, &force, "the remake's deadlines").await {
+ return refused;
}
let request: StartRequest = if is_octet_stream(headers) {
@@ -706,6 +716,11 @@ async fn abort_by_id<H: Host>(host: H, id: String, query: AbortQuery) -> Respons
/// commit it any more (`coordinator`), unless `abort_obstacle` says otherwise. Answers
/// with the final report.
async fn abort_expired<H: Host>(host: &H, current: Report, force: &Force) -> Response {
+ // Whether it expired is judged by this node's clock: one far ahead would abort a
+ // remake its coordinator may still commit (a contested remake).
+ if let Some(refused) = clock_obstacle(host, force, "whether the remake expired").await {
+ return refused;
+ }
let grace = grace(host);
if let Some((why, force)) = abort_obstacle(
&current.record,