Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Add clone test with seed acting as relay
Alexis Sellier committed 3 years ago
commit 3ae826dee28839af341f221beedbc34f96986a42
parent 3e7761740fa2a39c53e1593ed9a37f5984870920
3 files changed +96 -7
modified radicle-cli/tests/commands.rs
@@ -1,11 +1,15 @@
use std::env;
use std::path::Path;
+
use std::str::FromStr;
use std::{thread, time};

use radicle::git;
+
use radicle::prelude::Id;
use radicle::profile::Home;
+
use radicle::storage::{ReadRepository, WriteStorage};
use radicle::test::fixtures;

+
use radicle_node::service::tracking::Policy;
use radicle_node::test::{
    environment::{Config, Environment},
    logger,
@@ -267,3 +271,76 @@ fn rad_init_sync_and_clone() {
    )
    .unwrap();
}
+

+
#[test]
+
//
+
//     alice -- seed -- bob
+
//
+
fn test_replication_via_seed() {
+
    logger::init(log::Level::Debug);
+

+
    let mut environment = Environment::new();
+
    let alice = environment.node("alice");
+
    let bob = environment.node("bob");
+
    let seed = environment.node("seed");
+
    let working = environment.tmp().join("working");
+
    let rid = Id::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+

+
    let mut alice = alice.spawn(Config::default());
+
    let mut bob = bob.spawn(Config::default());
+
    let seed = seed.spawn(Config {
+
        policy: Policy::Track,
+
        ..Config::default()
+
    });
+

+
    alice.connect(&seed);
+
    bob.connect(&seed);
+

+
    // Enough time for the next inventory from Seed to not be considered stale by Bob.
+
    thread::sleep(time::Duration::from_secs(1));
+

+
    alice.routes_to(&[]);
+
    seed.routes_to(&[]);
+
    bob.routes_to(&[]);
+

+
    // Initialize a repo as Alice.
+
    fixtures::repository(working.join("alice"));
+
    alice
+
        .rad(
+
            "init",
+
            &[
+
                "--name",
+
                "heartwood",
+
                "--description",
+
                "Radicle Heartwood Protocol & Stack",
+
                "--default-branch",
+
                "master",
+
            ],
+
            working.join("alice"),
+
        )
+
        .unwrap();
+

+
    alice.routes_to(&[(rid, alice.id), (rid, seed.id)]);
+
    seed.routes_to(&[(rid, alice.id), (rid, seed.id)]);
+
    bob.routes_to(&[(rid, alice.id), (rid, seed.id)]);
+

+
    bob.rad("clone", &[rid.to_string().as_str()], working.join("bob"))
+
        .unwrap();
+

+
    alice.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]);
+
    seed.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]);
+
    bob.routes_to(&[(rid, alice.id), (rid, seed.id), (rid, bob.id)]);
+

+
    // Enough time for the Seed to be able to fetch Bob's fork.
+
    thread::sleep(time::Duration::from_secs(1));
+

+
    seed.storage
+
        .repository(rid)
+
        .unwrap()
+
        .remote(&bob.id)
+
        .unwrap();
+

+
    // TODO: Seed should send Bob's ref announcement to Alice, after the fetch.
+
    // Currently, it is relayed when received from Bob, before the fetch completes,
+
    // which means that Alice fetches too early from Seed, and nothing is fetched.
+
}
modified radicle-node/src/service.rs
@@ -754,7 +754,7 @@ where
                // Discard inventory messages we've already seen, otherwise update
                // out last seen time.
                if !peer.inventory_announced(timestamp) {
-
                    debug!(target: "service", "Ignoring stale inventory announcement from {announcer}");
+
                    debug!(target: "service", "Ignoring stale inventory announcement from {announcer} (t={})", self.clock.as_secs());
                    return Ok(false);
                }

modified radicle-node/src/test/environment.rs
@@ -162,12 +162,21 @@ impl<G: Signer + cyphernet::EcSign> NodeHandle<G> {
    }

    /// Wait until this node's routing table contains the given routes.
+
    #[track_caller]
    pub fn routes_to(&self, routes: &[(Id, NodeId)]) {
-
        let mut remaining: BTreeSet<_> = routes.iter().collect();
+
        loop {
+
            let mut remaining: BTreeSet<_> = routes.iter().collect();

-
        while !remaining.is_empty() {
            for (rid, nid) in self.handle.routing().unwrap() {
-
                remaining.remove(&(rid, nid));
+
                if !remaining.remove(&(rid, nid)) {
+
                    panic!(
+
                        "Node::routes_to: unexpected route for {}: ({rid}, {nid})",
+
                        self.id
+
                    );
+
                }
+
            }
+
            if remaining.is_empty() {
+
                break;
            }
            thread::sleep(Duration::from_millis(100));
        }
@@ -309,9 +318,12 @@ pub fn converge<'a, G: Signer + cyphernet::EcSign + 'static>(

    // First build the set of all routes.
    for node in &nodes {
-
        let inv = node.storage.inventory().unwrap();
-

-
        for rid in inv {
+
        // Routes from the routing table.
+
        for (rid, seed_id) in node.handle.routing().unwrap() {
+
            all_routes.insert((rid, seed_id));
+
        }
+
        // Routes from the local inventory.
+
        for rid in node.storage.inventory().unwrap() {
            all_routes.insert((rid, node.id));
        }
    }