aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 13:49:26 +0000
committertv <tv@krebsco.de>2026-11-08 15:03:58 +0100
commitb3526df29412400cfaa8c3a8d8692e37fc44d19b (patch)
treee4f1d60170d2c78d58b33072b7a23a9ce1ad00c4
parent40907ae285317eaaf6449ff17d40b58a1a5df665 (diff)
state: a document swap keeps the signatures of the changes it keeps
Review 2.2: swap_document forgot every signature. After dropping changes taken unjudged (or a rejoin into the same epoch), the node could vouch for none of the changes it kept: under signatures required, those building on an actor waited for that actor's next signature, and for an actor that stopped writing, forever. Signatures::kept_for keeps the signatures of the changes the new document holds, checks each actor's latest again, and keeps those waiting for their changes. This node's replayed changes are signed at once, as before.
-rw-r--r--src/document/signatures.rs22
-rw-r--r--src/state/host.rs7
2 files changed, 26 insertions, 3 deletions
diff --git a/src/document/signatures.rs b/src/document/signatures.rs
index 19c13b8..919e7b7 100644
--- a/src/document/signatures.rs
+++ b/src/document/signatures.rs
@@ -250,6 +250,22 @@ impl Signatures {
signatures
}
+ /// These for `doc`, replacing the document they are of (one without some of its
+ /// changes, or another of its epoch): those of the changes it holds, each actor's
+ /// latest checked again (`from_kept`); those waiting for their changes still wait.
+ pub(crate) fn kept_for(&self, doc: &Document) -> Self {
+ let kept = self
+ .order
+ .iter()
+ .filter(|hash| doc.get_change_meta_by_hash(hash).is_some())
+ .map(|hash| (*hash, self.by_change[hash]))
+ .collect();
+ Self {
+ pending: self.pending.clone(),
+ ..Self::from_kept(doc, kept)
+ }
+ }
+
pub(crate) fn insert(&mut self, hash: ChangeHash, signature: [u8; 64]) {
if self.by_change.insert(hash, signature).is_none() {
self.order.push(hash);
@@ -612,6 +628,12 @@ mod tests {
// Kept in a file and read again: verified as before.
let again = Signatures::from_kept(&ours, signatures.since(0));
assert!(again.verified(&actor, 3));
+
+ // For a document without the last change: those of the changes it holds.
+ let without = ours.fork_at(&[second]).unwrap();
+ let kept = signatures.kept_for(&without);
+ assert!(kept.verified(&actor, 2) && !kept.verified(&actor, 3));
+ assert_eq!(kept.since(0), vec![signed(second)]);
}
/// Protocol data, listed paths (within or above them) and asked-for writes are
diff --git a/src/state/host.rs b/src/state/host.rs
index 3210ee2..38be831 100644
--- a/src/state/host.rs
+++ b/src/state/host.rs
@@ -24,9 +24,10 @@ impl AppState {
/// document. (Lock order doc -> peer, as in sync generation.)
fn swap_document(&self, replica: &mut Replica, epoch: Epoch, doc: Document) -> Document {
let old = std::mem::replace(&mut replica.doc, doc);
- // The old document's signatures are of its changes; this node's replayed into
- // the new one, signed at once.
- replica.signatures = Default::default();
+ // The old document's signatures, of the changes the new one holds too (it may
+ // be the old one without some, or another of its epoch); this node's replayed
+ // into it, signed at once.
+ 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);