diff options
| author | tv <tv@krebsco.de> | 2026-11-09 15:21:24 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-09 18:07:47 +0100 |
| commit | acef27d042b2b9f7fa1974de0759a1c517cbb942 (patch) | |
| tree | 2ad2f690cb8d178c63853f035c42bd12ade48be7 /src | |
| parent | 9247e3980130ebfed9ef265e996e47bf1f6c852d (diff) | |
signatures: the latest unsigned change of each own actor signed, at start and after a swap
Review 4, F2 (the honest case) and claim 2: a node signed at start its own changes at
the heads that nothing vouched for (1216), and after a swap only its current actor's
latest. An own change left unsigned can sit below the heads: a drop (`drop_taken`)
takes away a signed change and keeps an earlier unsigned one, and the node writes on
under a fresh actor, building on it; or a node's change is built on by another's
concurrently with the signed one the drop took. Nothing signed it again, and under a
policy requiring signatures every node pulling it (a joiner) waited for good, and for
everything built on it.
Now the latest change of each of the node's actors (`RemakeState::own_actors`, the
current one among them) that no signature vouches for is signed
(`Signatures::sign_own_actors`), at start and whenever the document is swapped (a drop,
a switch, a rejoin), the old `sign_own_heads` and `sign_own_latest` going. One
signature per actor vouches for all its changes before it. The case of a member whose
signed change the rules refuse, vouching once for its earlier ones, is
postponed (future-work.md, next).
Diffstat (limited to 'src')
| -rw-r--r-- | src/document/signatures.rs | 82 | ||||
| -rw-r--r-- | src/node.rs | 5 | ||||
| -rw-r--r-- | src/state/host.rs | 10 |
3 files changed, 58 insertions, 39 deletions
diff --git a/src/document/signatures.rs b/src/document/signatures.rs index 3001690..a8b4713 100644 --- a/src/document/signatures.rs +++ b/src/document/signatures.rs @@ -433,43 +433,38 @@ impl Signatures { } } - /// Signs this node's latest change in `doc` with `key` if no signature of it is kept: - /// after a switch or a rejoin, the changes it replayed there (`remake::delta`), made - /// without one, vouched for at once. - pub(crate) fn sign_own_latest(&mut self, doc: &Document, key: &crate::identity::NodeKey) { - if let Some(change) = doc.get_last_local_change() - && !self.by_change.contains_key(&change.hash()) - { - self.sign(doc, change.hash(), key); - } - } - - /// This node's changes at `doc`'s heads that no signature vouches for, signed: - /// those it made and kept before it stopped, under an actor of an earlier run, and - /// never sent (it signs what it sends, as it sends it). Nothing it made since builds - /// on them, so they are heads; peers pulling them unsigned would wait for a - /// signature for good under a policy requiring them, and everything the node writes - /// after, building on them, with them. How many it signed. - pub(crate) fn sign_own_heads( + /// The latest change of each of this node's `actors` in `doc` that no signature + /// vouches for, signed: one it made and kept before it stopped, under an actor of an + /// earlier run, never sent (it signs what it sends, as it sends it); one left + /// unsigned below a signed change a drop took away (`drop_taken`, a fresh actor + /// after it). Peers pulling such a change unsigned would wait for a signature for + /// good under a policy requiring them, and for everything built on it. At start, + /// and after the document is swapped. How many it signed. + pub(crate) fn sign_own_actors( &mut self, - doc: &Document, + doc: &mut Document, key: &crate::identity::NodeKey, + actors: &[super::ActorId], ) -> usize { - let node = key.id(); + let current = doc.get_actor().clone(); let mut signed = 0; - for head in doc.get_heads() { - let Some(meta) = doc.get_change_meta_by_hash(&head) else { + for actor in actors.iter().chain(std::iter::once(¤t)) { + doc.set_actor(actor.clone()); + let Some(change) = doc.get_last_local_change() else { continue; }; - let own = meta - .author - .as_ref() - .is_some_and(|author| author.as_bytes() == node.as_bytes()); - if own && !self.verified(meta.actor.as_ref(), meta.seq) { - self.sign(doc, head, key); + if !self.verified(change.actor_id(), change.seq()) { + self.sign_change(doc, change.hash(), key); signed += 1; } } + doc.set_actor(current.clone()); + if doc + .get_last_local_change() + .is_some_and(|change| self.verified(change.actor_id(), change.seq())) + { + self.unsigned = None; + } signed } @@ -519,10 +514,15 @@ impl Signatures { hash: ChangeHash, key: &crate::identity::NodeKey, ) { + self.sign_change(doc, hash, key); + self.unsigned = None; + } + + /// `hash` signed with `key` and kept, vouching for its actor's changes up to it. + fn sign_change(&mut self, doc: &Document, hash: ChangeHash, key: &crate::identity::NodeKey) { let signature = key.sign(&message(&hash)); self.insert(hash, signature); self.vouch(doc, hash, &signature); - self.unsigned = None; } /// Whether this node's latest change waits for a signature (`sign_latest`, as a sync @@ -569,6 +569,7 @@ pub(crate) fn check( mod tests { use super::*; use crate::clock::UnixMillis; + use crate::document::ActorId; use crate::document::replica::{Writer, transact}; use crate::document::store; use crate::identity::NodeKey; @@ -583,24 +584,33 @@ mod tests { .unwrap(); } - /// Changes of this node at the heads, of an earlier run, unsigned: signed at start; - /// another node's, and those vouched for, left. + /// The latest unsigned change of each of this node's actors is signed, below the + /// heads too (an earlier actor's, built on under a later one); another node's, and + /// those vouched for, left. #[test] - fn own_heads_signed_at_start() { + fn own_actors_latest_signed() { let key = NodeKey::from_seed([1; 32]); let other = NodeKey::from_seed([2; 32]); let mut doc = Document::new(); + let earlier = doc.get_actor().clone(); write(&mut doc, &key.id(), &["a"], json!(1)); - // Another node's, concurrent: a head too. + // Another node's, concurrent. let mut theirs = Document::new(); write(&mut theirs, &other.id(), &["b"], json!(2)); doc.merge(&mut theirs).unwrap(); - assert_eq!(doc.get_heads().len(), 2); + // A later actor of this node builds on the earlier one's change: not a head now. + let mut doc = doc.with_actor(ActorId::random()); + write(&mut doc, &key.id(), &["c"], json!(3)); let mut signatures = Signatures::default(); - assert_eq!(signatures.sign_own_heads(&doc, &key), 1); + assert_eq!( + signatures.sign_own_actors(&mut doc, &key, std::slice::from_ref(&earlier)), + 2 + ); let ours = doc.get_last_local_change().unwrap(); assert!(signatures.verified(ours.actor_id(), ours.seq())); - assert_eq!(signatures.sign_own_heads(&doc, &key), 0); + assert!(signatures.verified(&earlier, 1)); + assert_eq!(signatures.sign_own_actors(&mut doc, &key, &[earlier]), 0); + assert!(signatures.unsigned.is_none()); } /// A signature verifies its actor's changes up to it, by the node its author names; diff --git a/src/node.rs b/src/node.rs index 6f08e69..0744206 100644 --- a/src/node.rs +++ b/src/node.rs @@ -129,7 +129,10 @@ impl Node { // What this node kept unsigned when it stopped: signed before anything leaves. if let Some(key) = &config.node_key { let replica = &mut replica; - let signed = replica.signatures.sign_own_heads(&replica.doc, key); + let signed = + replica + .signatures + .sign_own_actors(&mut replica.doc, key, &config.earlier_actors); if signed > 0 { tracing::info!( signed, diff --git a/src/state/host.rs b/src/state/host.rs index 7b2eb07..8522657 100644 --- a/src/state/host.rs +++ b/src/state/host.rs @@ -30,7 +30,10 @@ impl AppState { replica.signatures = replica.signatures.kept_for(&replica.doc); if let Some(key) = &self.node_key { let replica = &mut *replica; - replica.signatures.sign_own_latest(&replica.doc, key); + let own = self.remake.own_actors(); + replica + .signatures + .sign_own_actors(&mut replica.doc, key, &own); } replica.epoch = epoch; replica.epoch_seq += 1; @@ -505,7 +508,10 @@ impl Host for AppState { // What it replayed, vouched for at once. if let Some(key) = &state.node_key { let replica = &mut *replica; - replica.signatures.sign_own_latest(&replica.doc, key); + let own = state.remake.own_actors(); + replica + .signatures + .sign_own_actors(&mut replica.doc, key, &own); } replica.writable = true; replica.refresh_heads(); |
