aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/document/signatures.rs49
-rw-r--r--src/node.rs11
2 files changed, 60 insertions, 0 deletions
diff --git a/src/document/signatures.rs b/src/document/signatures.rs
index 2f408d5..102cad2 100644
--- a/src/document/signatures.rs
+++ b/src/document/signatures.rs
@@ -443,6 +443,35 @@ impl Signatures {
}
}
+ /// 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(
+ &mut self,
+ doc: &Document,
+ key: &crate::identity::NodeKey,
+ ) -> usize {
+ let node = key.id();
+ let mut signed = 0;
+ for head in doc.get_heads() {
+ let Some(meta) = doc.get_change_meta_by_hash(&head) 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);
+ signed += 1;
+ }
+ }
+ signed
+ }
+
/// Whether a valid signature of `hash` is kept: it vouches for its change and the
/// changes of its actor it builds on.
pub(crate) fn signed(&self, hash: &ChangeHash) -> bool {
@@ -552,6 +581,26 @@ 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.
+ #[test]
+ fn own_heads_signed_at_start() {
+ let key = NodeKey::from_seed([1; 32]);
+ let other = NodeKey::from_seed([2; 32]);
+ let mut doc = Document::new();
+ write(&mut doc, &key.id(), &["a"], json!(1));
+ // Another node's, concurrent: a head too.
+ 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);
+ let mut signatures = Signatures::default();
+ assert_eq!(signatures.sign_own_heads(&doc, &key), 1);
+ 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);
+ }
+
/// A signature verifies its actor's changes up to it, by the node its author names;
/// another node's key, or another change, doesn't.
#[test]
diff --git a/src/node.rs b/src/node.rs
index 91a9c62..314433c 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -126,6 +126,17 @@ impl Node {
&replica.doc,
config.initial_signatures,
);
+ // 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);
+ if signed > 0 {
+ tracing::info!(
+ signed,
+ "Signed changes this node kept unsigned when it stopped"
+ );
+ }
+ }
let stamp = replica.stamps();
let state = AppState {
doc: Arc::new(crate::document::lock::DocumentLock::new(replica)),