summaryrefslogtreecommitdiffstats
path: root/src/simtest/identity.rs
blob: a33783f75d75c194db896d278b77a1b8c1bfcfa0 (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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! Nodes under the identity layer (`transport::identity`): named by node IDs, found
//! through addresses (seeds, HELLO_REPLYs, the member list), and believed only once a
//! node proved itself at an address.

use std::time::Duration;

use serde_json::json;

use super::{Sim, read, sim_key, write};
use crate::identity::{self, NodeKey};
use crate::state::AppState;
use crate::swarm::bootstrap::Start;
use crate::swarm::members;
use crate::transport::identity::{WHO_LABEL, signed};
use crate::transport::{HeadsSummary, IdentityProof, Keepalive, ParsedMessage};

const SOON: Duration = Duration::from_secs(20);

async fn peers_are_node_ids(node: &AppState) {
    for (peer, _) in node.peers_snapshot().await {
        assert!(identity::is_node_id(&peer), "peer {peer}");
    }
}

/// Seeds by name (resolved by the net) and by address; the third node finds the second
/// only through what the first lists in its HELLO_REPLY.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn nodes_are_their_node_ids() {
    let sim = Sim::with_identity(51);
    let a = sim.start("a", &[], Start::Genesis);
    let b = sim.start("b", &["a"], Start::Join);
    let c = sim.start("c", &["sim:a"], Start::Join);
    sim.wait_ready(&[&a, &b, &c], SOON).await;
    assert_eq!(a.local_id(), sim_key("a").id());

    write(&b, "x", json!(1)).await;
    sim.wait_converged(&[&a, &b, &c], SOON).await;
    assert_eq!(read(&c, "x").await, Some(json!(1)));
    for node in [&a, &b, &c] {
        peers_are_node_ids(node).await;
    }
    let (b_id, c_id) = (b.local_id().to_owned(), c.local_id().to_owned());
    let a = &a;
    sim.wait_until(
        "b and c are registered with their addresses",
        SOON,
        move || {
            let (b_id, c_id) = (b_id.clone(), c_id.clone());
            async move {
                let replica = a.doc.read().await;
                members::addresses(&replica.doc, &b_id) == ["sim:b"]
                    && members::addresses(&replica.doc, &c_id) == ["sim:c"]
            }
        },
    )
    .await;
}

/// With two neighbours each, most pairs of six nodes never meet through seeds: they
/// find each other's addresses in the member list.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn members_find_each_other_through_the_member_list() {
    let sim = Sim::with_identity(52);
    let two = |config: &mut crate::node::NodeConfig| config.neighbours_k = 2;
    let a = sim.start_with("a", &[], Start::Genesis, two);
    let others: Vec<AppState> = ["b", "c", "d", "e", "f"]
        .into_iter()
        .map(|id| sim.start_with(id, &["a"], Start::Join, two))
        .collect();
    let mut all: Vec<&AppState> = vec![&a];
    all.extend(others.iter());
    sim.wait_ready(&all, SOON).await;

    write(&others[4], "from/f", json!("f")).await;
    write(&others[0], "from/b", json!("b")).await;
    sim.wait_converged(&all, SOON).await;
    for node in &all {
        assert_eq!(read(node, "from/f").await, Some(json!("f")));
        assert_eq!(read(node, "from/b").await, Some(json!("b")));
        peers_are_node_ids(node).await;
    }
}

fn ping(node: &AppState) -> ParsedMessage {
    ParsedMessage::Ping(Keepalive {
        heads: HeadsSummary::of(Vec::new()),
        epoch: node.local_epoch(),
        epoch_seq: 0,
        ready: false,
        syncs_with_you: false,
    })
}

fn who(key: &NodeKey, from: &str, to: &str) -> ParsedMessage {
    ParsedMessage::Who(IdentityProof {
        key: key.public(),
        signature: key.sign(&signed(WHO_LABEL, from, to)),
    })
}

/// A proof counts only for the address it was made at: messages from an address with
/// a proof made elsewhere are dropped, and the node never hears of them.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_proof_holds_only_at_its_address() {
    let sim = Sim::with_identity(53);
    let a = sim.start("a", &[], Start::Genesis);
    sim.wait_ready(&[&a], SOON).await;
    let ghost = NodeKey::from_seed([9; 32]);

    // Made for another sender address, or for another recipient.
    for forged in [
        who(&ghost, "sim:elsewhere", "sim:a"),
        who(&ghost, "sim:ghost", "sim:b"),
    ] {
        sim.net.inject("ghost", "a", &forged).await.unwrap();
        sim.net.inject("ghost", "a", &ping(&a)).await.unwrap();
        assert!(a.get_peer_state(&ghost.id()).await.is_none());
    }

    sim.net
        .inject("ghost", "a", &who(&ghost, "sim:ghost", "sim:a"))
        .await
        .unwrap();
    sim.net.inject("ghost", "a", &ping(&a)).await.unwrap();
    assert!(a.get_peer_state(&ghost.id()).await.is_some());
    // Known by its node ID, not by its address.
    assert!(a.get_peer_state("ghost").await.is_none());
}