aboutsummaryrefslogtreecommitdiffstats
path: root/src/simtest/signatures.rs
blob: 2425a16a6d43e284cccfa6de713866082cdbace1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! 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 a while after it (`--sign-every`, 1 s by default),
/// and the signature reaches every node on its own (SIGNATURES): the change went before
/// it was signed.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn unsigned_writes_are_verified_within_the_interval() {
    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;
    }
}