/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2023 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ // cargo +nightly test --release --features=event-graph --lib eventgraph_propagation -- --include-ignored use std::sync::Arc; use log::{info, warn}; use rand::{prelude::SliceRandom, rngs::ThreadRng}; use smol::{channel, future, Executor}; use url::Url; use crate::{ event_graph::{ proto::{EventPut, ProtocolEventGraph}, Event, EventGraph, }, net::{P2p, Settings, SESSION_ALL}, system::sleep, }; // Number of nodes to spawn and number of peers each node connects to const N_NODES: usize = 5; const N_CONNS: usize = 2; //const N_NODES: usize = 50; //const N_CONNS: usize = N_NODES / 3; fn init_logger() { let mut cfg = simplelog::ConfigBuilder::new(); cfg.add_filter_ignore("sled".to_string()); cfg.add_filter_ignore("net::protocol_ping".to_string()); cfg.add_filter_ignore("net::channel::subscribe_stop()".to_string()); cfg.add_filter_ignore("net::hosts".to_string()); cfg.add_filter_ignore("net::session".to_string()); cfg.add_filter_ignore("net::message_subscriber".to_string()); cfg.add_filter_ignore("net::protocol_address".to_string()); cfg.add_filter_ignore("net::protocol_version".to_string()); cfg.add_filter_ignore("net::protocol_registry".to_string()); cfg.add_filter_ignore("net::channel::send()".to_string()); cfg.add_filter_ignore("net::channel::start()".to_string()); cfg.add_filter_ignore("net::channel::subscribe_msg()".to_string()); cfg.add_filter_ignore("net::channel::main_receive_loop()".to_string()); cfg.add_filter_ignore("net::tcp".to_string()); // We check this error so we can execute same file tests in parallel, // otherwise second one fails to init logger here. if simplelog::TermLogger::init( simplelog::LevelFilter::Info, //simplelog::LevelFilter::Debug, //simplelog::LevelFilter::Trace, cfg.build(), simplelog::TerminalMode::Mixed, simplelog::ColorChoice::Auto, ) .is_err() { warn!(target: "test_harness", "Logger already initialized"); } } async fn spawn_node( inbound_addrs: Vec, peers: Vec, ex: Arc>, ) -> Arc { let settings = Settings { localnet: true, inbound_addrs, outbound_connections: 0, outbound_connect_timeout: 2, inbound_connections: usize::MAX, peers, allowed_transports: vec!["tcp".to_string()], ..Default::default() }; let p2p = P2p::new(settings, ex.clone()).await; let sled_db = sled::Config::new().temporary(true).open().unwrap(); let event_graph = EventGraph::new(p2p.clone(), sled_db, "dag", 1, ex.clone()).await.unwrap(); *event_graph.synced.write().await = true; let event_graph_ = event_graph.clone(); // Register the P2P protocols let registry = p2p.protocol_registry(); registry .register(SESSION_ALL, move |channel, _| { let event_graph_ = event_graph_.clone(); async move { ProtocolEventGraph::init(event_graph_, channel).await.unwrap() } }) .await; event_graph } async fn bootstrap_nodes( peer_indexes: &[usize], starting_port: usize, rng: &mut ThreadRng, ex: Arc>, ) -> Vec> { let mut eg_instances = vec![]; // Initialize the nodes for i in 0..N_NODES { // Everyone will connect to N_CONNS random peers. let mut peer_indexes_copy = peer_indexes.to_owned(); peer_indexes_copy.remove(i); let peer_indexes_to_connect: Vec<_> = peer_indexes_copy.choose_multiple(rng, N_CONNS).collect(); let mut peers = vec![]; for peer_index in peer_indexes_to_connect { let port = starting_port + peer_index; peers.push(Url::parse(&format!("tcp://127.0.0.1:{}", port)).unwrap()); } let event_graph = spawn_node( vec![Url::parse(&format!("tcp://127.0.0.1:{}", starting_port + i)).unwrap()], peers, ex.clone(), ) .await; eg_instances.push(event_graph); } // Start the P2P network for eg in eg_instances.iter() { eg.p2p.clone().start().await.unwrap(); } info!("Waiting 5s until all peers connect"); sleep(5).await; eg_instances } async fn assert_dags( eg_instances: &Vec>, expected_len: usize, rng: &mut ThreadRng, ) { let random_node = eg_instances.choose(rng).unwrap(); let last_layer_tips = random_node.unreferenced_tips.read().await.last_key_value().unwrap().1.clone(); for (i, eg) in eg_instances.iter().enumerate() { let node_last_layer_tips = eg.unreferenced_tips.read().await.last_key_value().unwrap().1.clone(); assert!( eg.dag.len() == expected_len, "Node {}, expected {} events, have {}", i, expected_len, eg.dag.len() ); assert_eq!( node_last_layer_tips, last_layer_tips, "Node {} contains malformed unreferenced tips", i ); } } macro_rules! test_body { ($real_call:ident) => { init_logger(); let ex = Arc::new(Executor::new()); let ex_ = ex.clone(); let (signal, shutdown) = channel::unbounded::<()>(); // Run a thread for each node. easy_parallel::Parallel::new() .each(0..N_NODES, |_| future::block_on(ex.run(shutdown.recv()))) .finish(|| { future::block_on(async { $real_call(ex_).await; drop(signal); }) }); }; } #[test] fn eventgraph_propagation() { test_body!(eventgraph_propagation_real); } async fn eventgraph_propagation_real(ex: Arc>) { let mut rng = rand::thread_rng(); let peer_indexes: Vec = (0..N_NODES).collect(); // Bootstrap nodes let mut eg_instances = bootstrap_nodes(&peer_indexes, 13200, &mut rng, ex.clone()).await; // Grab genesis event let random_node = eg_instances.choose(&mut rng).unwrap(); let (id, _) = random_node.dag.last().unwrap().unwrap(); let genesis_event_id = blake3::Hash::from_bytes((&id as &[u8]).try_into().unwrap()); // ========================================= // 1. Assert that everyone's DAG is the same // ========================================= assert_dags(&eg_instances, 1, &mut rng).await; // ========================================== // 2. Create an event in one node and publish // ========================================== let random_node = eg_instances.choose(&mut rng).unwrap(); let event = Event::new(vec![1, 2, 3, 4], random_node).await; assert!(event.parents.contains(&genesis_event_id)); // The node adds it to their DAG, on layer 1. let event_id = random_node.dag_insert(&[event.clone()]).await.unwrap()[0]; let tips_layers = random_node.unreferenced_tips.read().await; // Since genesis was referenced, its layer (0) have been removed assert_eq!(tips_layers.len(), 1); assert!(tips_layers.last_key_value().unwrap().1.get(&event_id).is_some()); drop(tips_layers); info!("Broadcasting event {}", event_id); random_node.p2p.broadcast(&EventPut(event)).await; info!("Waiting 5s for event propagation"); sleep(5).await; // ==================================================== // 3. Assert that everyone has the new event in the DAG // ==================================================== assert_dags(&eg_instances, 2, &mut rng).await; // ============================================================== // 4. Create multiple events on a node and broadcast the last one // The `EventPut` logic should manage to fetch all of them, // provided that the last one references the earlier ones. // ============================================================== let random_node = eg_instances.choose(&mut rng).unwrap(); let event0 = Event::new(vec![1, 2, 3, 4, 0], random_node).await; let event0_id = random_node.dag_insert(&[event0.clone()]).await.unwrap()[0]; let event1 = Event::new(vec![1, 2, 3, 4, 1], random_node).await; let event1_id = random_node.dag_insert(&[event1.clone()]).await.unwrap()[0]; let event2 = Event::new(vec![1, 2, 3, 4, 2], random_node).await; let event2_id = random_node.dag_insert(&[event2.clone()]).await.unwrap()[0]; // Genesis event + event from 2. + upper 3 events (layer 4) assert_eq!(random_node.dag.len(), 5); let tips_layers = random_node.unreferenced_tips.read().await; assert_eq!(tips_layers.len(), 1); assert!(tips_layers.get(&4).unwrap().get(&event2_id).is_some()); drop(tips_layers); let event_chain = vec![(event0_id, event0.parents), (event1_id, event1.parents), (event2_id, event2.parents)]; info!("Broadcasting event {}", event2_id); info!("Event chain: {:#?}", event_chain); random_node.p2p.broadcast(&EventPut(event2)).await; info!("Waiting 5s for event propagation"); sleep(5).await; // ========================================== // 5. Assert that everyone has all the events // ========================================== assert_dags(&eg_instances, 5, &mut rng).await; // =========================================== // 6. Create multiple events on multiple nodes // =========================================== // node 1 // ======= let node1 = eg_instances.choose(&mut rng).unwrap(); let event0_1 = Event::new(vec![1, 2, 3, 4, 3], node1).await; node1.dag_insert(&[event0_1.clone()]).await.unwrap(); node1.p2p.broadcast(&EventPut(event0_1)).await; let event1_1 = Event::new(vec![1, 2, 3, 4, 4], node1).await; node1.dag_insert(&[event1_1.clone()]).await.unwrap(); node1.p2p.broadcast(&EventPut(event1_1)).await; let event2_1 = Event::new(vec![1, 2, 3, 4, 5], node1).await; node1.dag_insert(&[event2_1.clone()]).await.unwrap(); node1.p2p.broadcast(&EventPut(event2_1)).await; // ======= // node 2 // ======= let node2 = eg_instances.choose(&mut rng).unwrap(); let event0_2 = Event::new(vec![1, 2, 3, 4, 6], node2).await; node2.dag_insert(&[event0_2.clone()]).await.unwrap(); node2.p2p.broadcast(&EventPut(event0_2)).await; let event1_2 = Event::new(vec![1, 2, 3, 4, 7], node2).await; node2.dag_insert(&[event1_2.clone()]).await.unwrap(); node2.p2p.broadcast(&EventPut(event1_2)).await; let event2_2 = Event::new(vec![1, 2, 3, 4, 8], node2).await; node2.dag_insert(&[event2_2.clone()]).await.unwrap(); node2.p2p.broadcast(&EventPut(event2_2)).await; // ======= // node 3 // ======= let node3 = eg_instances.choose(&mut rng).unwrap(); let event0_3 = Event::new(vec![1, 2, 3, 4, 9], node3).await; node3.dag_insert(&[event0_3.clone()]).await.unwrap(); node2.p2p.broadcast(&EventPut(event0_3)).await; let event1_3 = Event::new(vec![1, 2, 3, 4, 10], node3).await; node3.dag_insert(&[event1_3.clone()]).await.unwrap(); node2.p2p.broadcast(&EventPut(event1_3)).await; let event2_3 = Event::new(vec![1, 2, 3, 4, 11], node3).await; node3.dag_insert(&[event2_3.clone()]).await.unwrap(); node3.p2p.broadcast(&EventPut(event2_3)).await; info!("Waiting 5s for events propagation"); sleep(5).await; // ========================================== // 7. Assert that everyone has all the events // ========================================== // 5 events from 2. and 4. + 9 events from 6. = 14 assert_dags(&eg_instances, 14, &mut rng).await; // ============================================================ // 8. Start a new node and try to sync the DAG from other peers // ============================================================ { // Connect to N_CONNS random peers. let peer_indexes_to_connect: Vec<_> = peer_indexes.choose_multiple(&mut rng, N_CONNS).collect(); let mut peers = vec![]; for peer_index in peer_indexes_to_connect { let port = 13200 + peer_index; peers.push(Url::parse(&format!("tcp://127.0.0.1:{}", port)).unwrap()); } let event_graph = spawn_node( vec![Url::parse(&format!("tcp://127.0.0.1:{}", 13200 + N_NODES + 1)).unwrap()], peers, ex.clone(), ) .await; eg_instances.push(event_graph.clone()); event_graph.p2p.clone().start().await.unwrap(); info!("Waiting 5s for new node connection"); sleep(5).await; event_graph.dag_sync().await.unwrap() } // ============================================================ // 9. Assert the new synced DAG has the same contents as others // ============================================================ // 5 events from 2. and 4. + 9 events from 6. = 14 assert_dags(&eg_instances, 14, &mut rng).await; // Stop the P2P network for eg in eg_instances.iter() { eg.p2p.clone().stop().await; } } #[test] #[ignore] fn eventgraph_chaotic_propagation() { test_body!(eventgraph_chaotic_propagation_real); } async fn eventgraph_chaotic_propagation_real(ex: Arc>) { let mut rng = rand::thread_rng(); let peer_indexes: Vec = (0..N_NODES).collect(); let n_events: usize = 100000; // Bootstrap nodes let mut eg_instances = bootstrap_nodes(&peer_indexes, 14200, &mut rng, ex.clone()).await; // ========================================= // 1. Assert that everyone's DAG is the same // ========================================= assert_dags(&eg_instances, 1, &mut rng).await; // =========================================== // 2. Create multiple events on multiple nodes for i in 0..n_events { let random_node = eg_instances.choose(&mut rng).unwrap(); let event = Event::new(i.to_be_bytes().to_vec(), random_node).await; random_node.dag_insert(&[event.clone()]).await.unwrap(); random_node.p2p.broadcast(&EventPut(event)).await; } info!("Waiting 5s for events propagation"); sleep(5).await; // ========================================== // 3. Assert that everyone has all the events // ========================================== assert_dags(&eg_instances, n_events + 1, &mut rng).await; // ============================================================ // 4. Start a new node and try to sync the DAG from other peers // ============================================================ { // Connect to N_CONNS random peers. let peer_indexes_to_connect: Vec<_> = peer_indexes.choose_multiple(&mut rng, N_CONNS).collect(); let mut peers = vec![]; for peer_index in peer_indexes_to_connect { let port = 14200 + peer_index; peers.push(Url::parse(&format!("tcp://127.0.0.1:{}", port)).unwrap()); } let event_graph = spawn_node( vec![Url::parse(&format!("tcp://127.0.0.1:{}", 14200 + N_NODES + 1)).unwrap()], peers, ex.clone(), ) .await; eg_instances.push(event_graph.clone()); event_graph.p2p.clone().start().await.unwrap(); info!("Waiting 5s for new node connection"); sleep(5).await; event_graph.dag_sync().await.unwrap() } // ============================================================ // 5. Assert the new synced DAG has the same contents as others // ============================================================ assert_dags(&eg_instances, n_events + 1, &mut rng).await; // Stop the P2P network for eg in eg_instances.iter() { eg.p2p.clone().stop().await; } }