diff options
| author | tv <tv@krebsco.de> | 2026-11-08 18:17:14 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-08 19:29:10 +0100 |
| commit | 96ba15cf1a6f8725f70f429695a5a9661f706eb0 (patch) | |
| tree | 36feecb42a8bfa5317f27a052266f74489312dfc /src/remake/vote.rs | |
| parent | 4ce3bde36bb8283d82b914f0f73e30d89c935113 (diff) | |
strict: a manifest serves one remake, at its base
Review 2, N1: a strict manifest wasn't bound to its base. Any member
could propose an old, legitimately signed one again at the current
heads; nodes would verify it and vote, and every value at a signed
place changed or added since (a ban, a new user, a rotated key, a
changed userSigned or sealed declaration) would be deleted, nothing
after the base being left to replay.
The manifest now names the epoch and the heads it was made at, and a
node takes it (proposing or voting) only for a remake of that epoch
with exactly that base. GET /admin/strict-manifest makes it so;
continuum-signed strict proposes at those heads already.
Diffstat (limited to 'src/remake/vote.rs')
| -rw-r--r-- | src/remake/vote.rs | 21 |
1 files changed, 15 insertions, 6 deletions
diff --git a/src/remake/vote.rs b/src/remake/vote.rs index 073fcda..7e678e6 100644 --- a/src/remake/vote.rs +++ b/src/remake/vote.rs @@ -294,7 +294,7 @@ pub(super) async fn cast<H: Host>(host: &H) { return None; } let content = record.proposal.content.clone(); - Some((id, content, record.proposal.kind.clone())) + Some((id, content, record.proposal.clone())) }); let swarm = store::read_path( doc, @@ -319,7 +319,7 @@ pub(super) async fn cast<H: Host>(host: &H) { } } } - let Some((chosen, content, kind)) = due else { + let Some((chosen, content, proposal)) = due else { return; }; // Only with the content in hand: a committed remake must have it on a majority. @@ -331,8 +331,14 @@ pub(super) async fn cast<H: Host>(host: &H) { None => None, }; // A strict remake: only with its manifest signed by a key this node was given. - if kind == "strict" - && let Err(why) = strict_taken(host, bytes.as_deref().map(Vec::as_slice), &swarm) + if proposal.kind == "strict" + && let Err(why) = strict_taken( + host, + bytes.as_deref().map(Vec::as_slice), + &swarm, + &proposal.from_epoch, + proposal.base_heads.as_deref(), + ) { if host.remake().refuses_strict(&chosen) { warn!(remake = %chosen, why = %why, "Not voting for a strict remake"); @@ -375,11 +381,14 @@ pub(super) async fn cast<H: Host>(host: &H) { } /// Whether this node takes a strict remake's manifest (`content`, for `swarm`): signed -/// by one of its `--strict-keys`. Why not, if not. +/// by one of its `--strict-keys`, for the remake's epoch and base (`epoch`, `base`). Why +/// not, if not. pub(super) fn strict_taken<H: Host>( host: &H, content: Option<&[u8]>, swarm: &str, + epoch: &EpochRecord, + base: Option<&[String]>, ) -> Result<(), String> { let text = content .and_then(|bytes| std::str::from_utf8(bytes).ok()) @@ -387,7 +396,7 @@ pub(super) fn strict_taken<H: Host>( if host.strict_keys().is_empty() { return Err("this node has no --strict-keys".to_owned()); } - crate::access::strict::verify(text, host.strict_keys(), swarm).map(|_| ()) + crate::access::strict::verify(text, host.strict_keys(), swarm)?.for_base(epoch, base) } /// A random duration below `limit`. |
