diff options
| -rw-r--r-- | docs/lifecycle.md | 8 | ||||
| -rw-r--r-- | src/access/screen.rs | 72 | ||||
| -rw-r--r-- | src/state/host.rs | 6 | ||||
| -rw-r--r-- | src/swarm/sync.rs | 10 |
4 files changed, 92 insertions, 4 deletions
diff --git a/docs/lifecycle.md b/docs/lifecycle.md index d703fe3..900349d 100644 --- a/docs/lifecycle.md +++ b/docs/lifecycle.md @@ -95,7 +95,13 @@ document file holding a document is an error with no override. each start, unless `--keep-actor` and it stopped cleanly (the file marks a clean stop, and the mark is undone as the node starts on it, before it decides its actor): then it writes on as the actor of before, as nothing it wrote is missing. Where the - mark can't be undone (the disk full, say), it writes as a fresh one. Fewer actors mean fewer signatures in every + mark can't be undone (the disk full, say), it writes as a fresh one. Two nodes + started on copies of one file would both write on as that actor, and each one's + changes would be refused as forged by peers holding the other's: don't start a copy + of a node's data directory while the node runs, or after it, with `--keep-actor`. A + node receiving a change of its own actor that it didn't make writes on as a fresh + actor at once, and logs a warning; what both wrote before stays refused where the + other's came first. Fewer actors mean fewer signatures in every transfer of the epoch's history (a writer each); a crash, a drop, or a switch or a rejoin still starts a fresh one. * **A backup** (`GET /admin/document`) is the whole document with its history, naming diff --git a/src/access/screen.rs b/src/access/screen.rs index 3ea03e0..010ef49 100644 --- a/src/access/screen.rs +++ b/src/access/screen.rs @@ -684,6 +684,9 @@ pub(crate) struct Outcome { pub(crate) roots: Vec<ChangeHash>, /// Changes the bytes carried forged under another node's actor: their sender's. pub(crate) forged: usize, + /// The fresh actor the node writes as from now on, where the bytes carried a change + /// of its own actor that it doesn't hold: for its own actors (`add_own_actor`). + pub(crate) fresh_actor: Option<crate::document::ActorId>, } /// [`apply`] of the changes in `bytes`, as sync messages and downloads carry them, with @@ -710,6 +713,27 @@ pub(crate) fn apply_bytes( } let mut changes = crate::document::changes::changes_in(bytes) .ok_or_else(|| anyhow::anyhow!("Refused changes that don't load"))?; + // A change of the actor this node writes as, and not one it made: another node writes + // as it too (two started from one document file with `--keep-actor`, a copied data + // directory). Each would make changes the other's peers refuse as forged; the node + // writes on as a fresh actor at once. The change is judged as any other. + let fresh_actor = changes + .iter() + .find(|change| { + change.actor_id() == doc.get_actor() + && !crate::document::changes::holds(&*doc, &change.hash()) + }) + .map(|change| { + let actor = crate::document::ActorId::random(); + tracing::warn!( + actor = %doc.get_actor(), + seq = change.seq(), + fresh = %actor, + "A change of this node's actor that it didn't make: another node writes as it (its files copied?); writing as a fresh actor" + ); + doc.set_actor(actor.clone()); + actor + }); let mut screening = screening.lock_or_recover(); let screening = screening.of(epoch_seq); // Into an empty document of the first epoch, a root only as pinned: one given or @@ -752,7 +776,10 @@ pub(crate) fn apply_bytes( } screening.record(refused_roots); if changes.is_empty() && screening.waiting.len() == 0 { - return Ok(Outcome::default()); + return Ok(Outcome { + fresh_actor, + ..Outcome::default() + }); } let received = changes.len(); let policed = screening.policed(doc); @@ -770,6 +797,7 @@ pub(crate) fn apply_bytes( refused_again: received > 0 && screened.refused_again == received, roots: screened.roots.iter().copied().collect(), forged: screened.forged, + fresh_actor, }; screening.judge_members(&screened); screening.record(screened.refused); @@ -1100,6 +1128,48 @@ mod tests { assert!(building.refused[0].why.contains("builds on refused change")); } + /// A change of the node's own actor that it didn't make: it writes on as a fresh + /// actor. Its own changes coming back change nothing. + #[test] + fn another_writing_as_this_node() { + let mut theirs = genesis(); + let mut ours = theirs.fork().with_actor(theirs.get_actor().clone()); + let screening = std::sync::Mutex::new(Screening::default()); + let take = |doc: &mut Document, from: &Document, since: &[ChangeHash]| { + let bytes: Vec<u8> = received(from, since) + .iter() + .flat_map(|change| change.raw_bytes().to_vec()) + .collect(); + apply_bytes( + doc, + Epoch::GENESIS, + 0, + &bytes, + &screening, + None, + &mut crate::document::PatchLog::inactive(), + ) + .unwrap() + }; + let heads = ours.get_heads(); + write(&mut ours, "a", None, "_continuum/members/a/alive", json!(1)); + let echo = ours.clone(); + assert!(take(&mut ours, &echo, &heads).fresh_actor.is_none()); + + let actor = ours.get_actor().clone(); + let before = theirs.get_heads(); + write( + &mut theirs, + "a", + None, + "_continuum/members/a/alive", + json!(2), + ); + let outcome = take(&mut ours, &theirs, &before); + assert_eq!(outcome.fresh_actor.as_ref(), Some(ours.get_actor())); + assert_ne!(ours.get_actor(), &actor); + } + /// Judged as its writer saw the registry: a grant earlier in the same batch counts. #[test] fn a_grant_in_the_same_batch_counts() { diff --git a/src/state/host.rs b/src/state/host.rs index 8522657..5b52e1d 100644 --- a/src/state/host.rs +++ b/src/state/host.rs @@ -590,7 +590,11 @@ impl Host for AppState { Some(&mut replica.signatures), &mut crate::document::PatchLog::inactive(), ) - .map(|_| ()) + .map(|outcome| { + if let Some(actor) = outcome.fresh_actor { + state.remake.add_own_actor(actor); + } + }) .map_err(|error| format!("{error:#}")) }; replica.refresh_heads(); diff --git a/src/swarm/sync.rs b/src/swarm/sync.rs index 3a3d92c..5550c27 100644 --- a/src/swarm/sync.rs +++ b/src/swarm/sync.rs @@ -597,6 +597,7 @@ pub(crate) async fn handle_sync( let peer = peer.to_owned(); let clock = Arc::clone(&state.clock); let ahead_ms = state.clock_ahead_ms.load(Ordering::Relaxed); + let remake = Arc::clone(&state.remake); // Some((caught_up, heads changed, carried changes, the new changes' authors with // theirs, and whether any has none, how long to wait before answering if it @@ -681,6 +682,9 @@ pub(crate) async fn handle_sync( if outcome.forged > 0 { tracing::warn!(peer = %peer, forged = outcome.forged, "A peer brought changes forged under another node's actor: refused"); } + if let Some(actor) = outcome.fresh_actor { + remake.add_own_actor(actor); + } } sync.receive_logged(doc, message, log) })(); @@ -965,6 +969,7 @@ async fn download_and_apply_delta( let clock = Arc::clone(&state.clock); let ahead_ms = state.clock_ahead_ms.load(Ordering::Relaxed); let screening = Arc::clone(&state.screening); + let remake = Arc::clone(&state.remake); tokio::task::spawn_blocking(move || -> Result<bool> { let mut replica = doc.blocking_write(); if replica.epoch != epoch { @@ -976,7 +981,7 @@ async fn download_and_apply_delta( // not. let epoch_seq = replica.epoch_seq; let replica = &mut *replica; - crate::access::screen::apply_bytes( + let outcome = crate::access::screen::apply_bytes( &mut replica.doc, epoch, epoch_seq, @@ -985,6 +990,9 @@ async fn download_and_apply_delta( Some(&mut replica.signatures), &mut crate::document::PatchLog::inactive(), )?; + if let Some(actor) = outcome.fresh_actor { + remake.add_own_actor(actor); + } } replica.refresh_heads(); // Signatures waiting for what it brought (`Signatures::offer`). |
