diff options
| author | tv <tv@krebsco.de> | 2026-11-07 22:35:22 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-07 22:35:22 +0100 |
| commit | fa695849c3b854670df7642b91a7937316e32b5b (patch) | |
| tree | e934c093d69e9a9317b425c8dfd7a198b618566d /src/simtest/policy.rs | |
| parent | 56a3dca1874fba17c3d174be451af9478b3bfb9d (diff) | |
simtest: members against the rules, and a policy unknown
A member writing past its API's checks is refused, told, drops its
change and is taken again; a node under a policy it doesn't know
takes no writes and syncs on.
Auth plan M6 (tests).
Diffstat (limited to 'src/simtest/policy.rs')
| -rw-r--r-- | src/simtest/policy.rs | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/src/simtest/policy.rs b/src/simtest/policy.rs index 5ad05fd..68f394f 100644 --- a/src/simtest/policy.rs +++ b/src/simtest/policy.rs @@ -478,3 +478,105 @@ async fn a_change_taken_unjudged() { assert!(!holds(node, "notes/after").await); } } + +/// A member writing against the rules (past its API's checks): refused by every other +/// node (as a whole, its own change being signed, for as long as it takes); told which +/// change broke the rules (REFUSALS), it judges it itself, drops it, its write +/// quarantined, and is taken again with what it writes next. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn a_member_writing_against_the_rules() { + let sim = Sim::with_identity(76); + 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); + let c = sim.start("c", &["a"], Start::Join); + sim.wait_ready(&[&a, &b, &c], SOON).await; + let nodes = [a.clone(), b.clone(), c.clone()]; + sim.wait_until("every node is registered", SOON, || { + let nodes = nodes.clone(); + async move { + for node in &nodes { + if crate::swarm::members::members(&node.doc.read().await.doc).len() < 3 { + return false; + } + } + true + } + }) + .await; + write_as( + &a, + "alice", + "_continuum/access/policy", + json!({"version": 1, "signatures": "required"}), + ) + .await; + sim.wait_converged(&[&a, &b, &c], SOON).await; + + write_as(&c, "mallory", "notes/forged", json!(1)).await; + let rogue = c.local_id().to_owned(); + + // By whichever node judges it first: the other may still be waiting for its + // signature when c drops it, and then never gets to judge it. + sim.wait_until("the change is refused", SOON, || async { + refused(&a).await + refused(&b).await > 0 + }) + .await; + sim.wait_until("c drops its change", SOON, || async { + !holds(&c, "notes/forged").await + }) + .await; + assert_eq!(c.remake.list_quarantine(|entry| entry.id.clone()).len(), 1); + write_as(&c, "alice", "notes/honest", json!(2)).await; + for node in [&a, &b] { + let rogue = rogue.clone(); + sim.wait_until("c's next write is taken, and c with it", SOON, move || { + let rogue = rogue.clone(); + async move { + use crate::locks::MutexExt; + holds(node, "notes/honest").await + && node.screening.lock_or_recover().refusing(&rogue).is_none() + } + }) + .await; + assert!(!holds(node, "notes/forged").await); + } +} + +/// A policy this node doesn't know (another version): it takes no writes through the +/// API, as it can't tell what the others would refuse, and syncs on. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn a_policy_this_node_doesnt_know() { + let sim = Sim::with_identity(77); + 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", "notes/before", json!(1)).await; + write_as( + &a, + "alice", + "_continuum/access/policy", + json!({"version": 2}), + ) + .await; + sim.wait_converged(&[&a, &b], SOON).await; + assert!(holds(&b, "notes/before").await); + let at = path("notes/after"); + let refused = b + .mutate_doc_for( + Some("alice".to_owned()), + Sign::Write { + path: at.clone(), + asked: false, + }, + move |doc| store::write_path(doc, &at, &json!(2)), + ) + .await; + assert!( + matches!(refused, Err(store::StoreError::UnknownPolicy(_))), + "a write under a policy this node doesn't know" + ); +} |
