diff options
| author | tv <tv@krebsco.de> | 2026-11-08 17:07:03 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-08 19:28:22 +0100 |
| commit | f91604095e6f0978c61f66d0b937e36271637e6b (patch) | |
| tree | e05cd2a21dd4122c3a8a3a450b3605b5a094416b /src/remake/coordinator.rs | |
| parent | 5f69ecf6284a8061a2b8fae5b8f98d67a67dad5f (diff) | |
remake: no --follow-strict-remakes; a strict remake failing says who
A strict remake takes a majority of members whose --strict-keys take
its manifest's signer: no node votes for one without, so
--follow-strict-remakes (1173) goes. A node that doesn't vote still
switches once the others commit, as with any remake.
So that an operator can tell why one fails: a strict remake aborted
for want of a majority (the voting period over, or a majority out of
reach) says in its reason (GET /admin/remakes/{id}, and the
coordinator's log) which members didn't vote, and that a member votes
for one only with its manifest signed by a key of its --strict-keys.
Each of those logs why itself (`Not voting for a strict remake`).
The sim a_strict_remake_too_few_take: only a takes the manifest; the
remake aborts, naming b and c.
Diffstat (limited to 'src/remake/coordinator.rs')
| -rw-r--r-- | src/remake/coordinator.rs | 43 |
1 files changed, 41 insertions, 2 deletions
diff --git a/src/remake/coordinator.rs b/src/remake/coordinator.rs index 314cd87..0d7a434 100644 --- a/src/remake/coordinator.rs +++ b/src/remake/coordinator.rs @@ -138,7 +138,10 @@ async fn step<H: Host>(host: &H, id: &str) -> Result<bool, StoreError> { decide_abort( host, id, - format!("no majority in the voting period: {tally}"), + format!( + "no majority in the voting period: {tally}{}", + strict_hint(id, &record, ®ister) + ), ) .await?; return Ok(true); @@ -149,12 +152,48 @@ async fn step<H: Host>(host: &H, id: &str) -> Result<bool, StoreError> { return Ok(true); } if tally.out_of_reach() { - decide_abort(host, id, format!("a majority is out of reach: {tally}")).await?; + decide_abort( + host, + id, + format!( + "a majority is out of reach: {tally}{}", + strict_hint(id, &record, ®ister) + ), + ) + .await?; return Ok(true); } Ok(false) } +/// For a strict remake `id` without a majority: who didn't vote, and why they may not +/// have (`vote::strict_taken`), for its operator to tell from the reason. Nothing for +/// another kind, or if all voted. +fn strict_hint(id: &str, record: &Record, register: &records::Register) -> String { + if record.proposal.kind != "strict" { + return String::new(); + } + let silent: Vec<&str> = record + .proposal + .electorate + .iter() + .filter(|member| { + register + .get(member.as_str()) + .is_none_or(|votes| !votes.iter().any(|vote| vote.remake == id)) + }) + .map(String::as_str) + .collect(); + if silent.is_empty() { + return String::new(); + } + format!( + "; a strict remake, not voted for by {}: a member votes for one only with its \ + manifest signed by a key of its --strict-keys (or it was away)", + silent.join(", ") + ) +} + /// Why remake `id` must be aborted whatever its votes, if it must. fn obstacle(id: &str, record: &Record, all: &[(String, Record)]) -> Option<String> { if let Some(request) = &record.abort_requested { |
