diff options
| author | tv <tv@krebsco.de> | 2026-11-08 09:33:41 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-08 10:35:31 +0100 |
| commit | b758c93a1887ea36b0f52ccd4a6f9824de77d819 (patch) | |
| tree | 96f7f4ca3007e393b185739a66b0474323b50872 /src/simtest/policy.rs | |
| parent | af3869df9ff3a0266857bd7c7534b75b36d04aa5 (diff) | |
access: an epoch's root is never dropped
A REFUSALS naming the swarm's first change made the node receiving it
judge that change again in a document that isn't empty, refuse it as
"a change without dependencies that isn't the epoch's root", and drop
it with everything built on it: its whole document.
The first epoch's root is now the first change a document holds, as it
comes to an empty one or as it is in one; a change judged again on a
peer's word is never refused for having no dependencies; and
drop_taken refuses to drop an epoch's root, or anything that would
leave nothing kept, logging an error instead.
Review finding 1 (critical).
Diffstat (limited to 'src/simtest/policy.rs')
| -rw-r--r-- | src/simtest/policy.rs | 33 |
1 files changed, 33 insertions, 0 deletions
diff --git a/src/simtest/policy.rs b/src/simtest/policy.rs index 68f394f..da046bb 100644 --- a/src/simtest/policy.rs +++ b/src/simtest/policy.rs @@ -580,3 +580,36 @@ async fn a_policy_this_node_doesnt_know() { "a write under a policy this node doesn't know" ); } + +/// A REFUSALS naming the swarm's first change, as a member could send: the node +/// receiving it keeps its document, as no rule refuses an epoch's root. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn a_refusal_naming_the_root() { + let sim = Sim::with_identity(78); + let a = sim.start_with("a", &[], Start::Genesis, |config| { + config.first_users = [("alice".to_owned(), Default::default())].into(); + }); + let b = sim.start("b", &["a"], Start::Join); + sim.wait_ready(&[&a, &b], SOON).await; + write_as( + &a, + "alice", + "_continuum/access/policy", + json!({"version": 1}), + ) + .await; + write_as(&a, "alice", "notes/x", json!(1)).await; + sim.wait_converged(&[&a, &b], SOON).await; + let (root, epoch, epoch_seq) = { + let replica = b.doc.read().await; + ( + replica.doc.get_changes(&[])[0].hash(), + replica.epoch, + replica.epoch_seq, + ) + }; + crate::swarm::refusals::tell(&a, b.local_id(), epoch, epoch_seq, vec![root]).await; + tokio::time::sleep(Duration::from_secs(3)).await; + assert!(holds(&b, "notes/x").await, "b dropped its document"); + assert_eq!(b.remake.list_quarantine(|entry| entry.id.clone()).len(), 0); +} |
