diff options
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" + ); +} |
