//! Signed changes end to end (`document::signatures`, `swarm::signatures`): nodes under //! the identity layer, whose node keys sign, write through the API as a user would, //! and every node comes to see the writes verified, by signatures that came with sync //! messages or on their own. use std::time::Duration; use serde_json::json; use super::{Sim, path}; use crate::document::ActorId; use crate::document::signatures::{Sign, SignEvery}; use crate::document::store; use crate::state::AppState; use crate::swarm::bootstrap::Start; const SOON: Duration = Duration::from_secs(20); /// Writes `value` at `at` on `node` as a write through the API does, signed at once if /// `signed` (`?signed=true`); the change's actor and seq. async fn write_as_user( node: &AppState, at: &str, value: serde_json::Value, signed: bool, ) -> (ActorId, u64) { let at = path(at); let sign = Sign::Write { path: at.clone(), asked: signed, }; node.mutate_doc_for(Some("alice".to_owned()), sign, move |doc| { store::write_path(doc, &at, &value) }) .await .expect("write"); let replica = node.doc.read().await; let change = replica.doc.get_last_local_change().expect("the write"); (change.actor_id().clone(), change.seq()) } /// Whether a signature vouches for `actor`'s change `seq` on `node`. async fn verified(node: &AppState, actor: &ActorId, seq: u64) -> bool { node.doc.read().await.signatures.verified(actor, seq) } /// A write no rule signs is signed as a sync message takes it (`--sign-every sent`, the /// default), and the signature reaches every node with it. #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn writes_leave_signed() { let sim = Sim::with_identity(61); let a = sim.start("a", &[], Start::Genesis); 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 (actor, seq) = write_as_user(&b, "metrics/cpu", json!(42), false).await; sim.wait_converged(&[&a, &b, &c], SOON).await; for node in [&a, &b, &c] { let actor = actor.clone(); sim.wait_until("the write is verified", SOON, move || { let actor = actor.clone(); async move { verified(node, &actor, seq).await } }) .await; } } /// With `--sign-every none`, a write no rule signs stays unverified; the node's next /// signed write vouches for it too, wherever its signature goes. #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn a_signed_write_vouches_for_the_writes_before() { let sim = Sim::with_identity(62); let a = sim.start("a", &[], Start::Genesis); let b = sim.start_with("b", &["a"], Start::Join, |config| { config.sign_every = SignEvery::Never; }); let c = sim.start("c", &["a"], Start::Join); sim.wait_ready(&[&a, &b, &c], SOON).await; let (actor, first) = write_as_user(&b, "metrics/cpu", json!(42), false).await; sim.wait_converged(&[&a, &b, &c], SOON).await; // Signatures go within a second: none came for it. Unless b wrote protocol data // since (always signed), whose signature vouches for it too. tokio::time::sleep(Duration::from_secs(3)).await; let latest = b .doc .read() .await .doc .get_last_local_change() .map(|change| change.seq()); if latest == Some(first) { for node in [&a, &b, &c] { assert!(!verified(node, &actor, first).await); } } let (same, second) = write_as_user(&b, "config/mode", json!("fast"), true).await; assert_eq!(same, actor); sim.wait_converged(&[&a, &b, &c], SOON).await; for node in [&a, &b, &c] { let actor = actor.clone(); sim.wait_until("both writes are verified", SOON, move || { let actor = actor.clone(); async move { verified(node, &actor, first).await && verified(node, &actor, second).await } }) .await; } }