/ node / bft / tests / narwhal_e2e.rs
narwhal_e2e.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphaos library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  #[allow(dead_code)]
 20  mod common;
 21  
 22  use crate::common::primary::{TestNetwork, TestNetworkConfig};
 23  use alphaos_node_bft::MAX_FETCH_TIMEOUT_IN_MS;
 24  
 25  use std::time::Duration;
 26  
 27  use deadline::deadline;
 28  use tokio::time::sleep;
 29  
 30  #[tokio::test(flavor = "multi_thread")]
 31  #[ignore = "long-running e2e test"]
 32  async fn test_state_coherence() {
 33      const N: u16 = 4;
 34      const TRANSMISSION_INTERVAL_MS: u64 = 10;
 35  
 36      let mut network = tokio::task::spawn_blocking(|| {
 37          TestNetwork::new(TestNetworkConfig {
 38              num_nodes: N,
 39              bft: false,
 40              connect_all: true,
 41              fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
 42              // Set this to Some(0..=4) to see the logs.
 43              log_level: Some(0),
 44              log_connections: true,
 45          })
 46      })
 47      .await
 48      .unwrap();
 49  
 50      network.start().await;
 51  
 52      // TODO(nkls): the easiest would be to assert on the anchor or bullshark's output, once
 53      // implemented.
 54  
 55      std::future::pending::<()>().await;
 56  }
 57  
 58  #[tokio::test(flavor = "multi_thread")]
 59  async fn test_quorum_threshold() {
 60      // Start N nodes but don't connect them.
 61      const N: u16 = 4;
 62      const TRANSMISSION_INTERVAL_MS: u64 = 10;
 63  
 64      let mut network = tokio::task::spawn_blocking(|| {
 65          TestNetwork::new(TestNetworkConfig {
 66              num_nodes: N,
 67              bft: false,
 68              connect_all: false,
 69              fire_transmissions: None,
 70              // Set this to Some(0..=4) to see the logs.
 71              log_level: None,
 72              log_connections: true,
 73          })
 74      })
 75      .await
 76      .unwrap();
 77      network.start().await;
 78  
 79      // Check each node is at round 1 (0 is genesis).
 80      for validators in network.validators.values() {
 81          assert_eq!(validators.primary.current_round(), 1);
 82      }
 83  
 84      // Start the cannons for node 0.
 85      network.fire_transmissions_at(0, TRANSMISSION_INTERVAL_MS);
 86  
 87      sleep(Duration::from_millis(MAX_FETCH_TIMEOUT_IN_MS)).await;
 88  
 89      // Check each node is still at round 1.
 90      for validator in network.validators.values() {
 91          assert_eq!(validator.primary.current_round(), 1);
 92      }
 93  
 94      // Connect the first two nodes and start the cannons for node 1.
 95      network.connect_validators(0, 1).await;
 96      network.fire_transmissions_at(1, TRANSMISSION_INTERVAL_MS);
 97  
 98      sleep(Duration::from_millis(MAX_FETCH_TIMEOUT_IN_MS)).await;
 99  
100      // Check each node is still at round 1.
101      for validator in network.validators.values() {
102          assert_eq!(validator.primary.current_round(), 1);
103      }
104  
105      // Connect the third node and start the cannons for it.
106      network.connect_validators(0, 2).await;
107      network.connect_validators(1, 2).await;
108      network.fire_transmissions_at(2, TRANSMISSION_INTERVAL_MS);
109  
110      // Check the nodes reach quorum and advance through the rounds.
111      const TARGET_ROUND: u64 = 4;
112      let net = network.clone();
113      deadline!(Duration::from_secs(20), move || { net.is_round_reached(TARGET_ROUND) });
114  }
115  
116  #[tokio::test(flavor = "multi_thread")]
117  async fn test_quorum_break() {
118      // Start N nodes, connect them and start the cannons for each.
119      const N: u16 = 4;
120      const TRANSMISSION_INTERVAL_MS: u64 = 10;
121      let mut network = tokio::task::spawn_blocking(|| {
122          TestNetwork::new(TestNetworkConfig {
123              num_nodes: N,
124              bft: false,
125              connect_all: true,
126              fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
127              // Set this to Some(0..=4) to see the logs.
128              log_level: None,
129              log_connections: true,
130          })
131      })
132      .await
133      .unwrap();
134      network.start().await;
135  
136      // Check the nodes have started advancing through the rounds.
137      const TARGET_ROUND: u64 = 4;
138      // Note: cloning the network is fine because the primaries it wraps are `Arc`ed.
139      let network_clone = network.clone();
140      deadline!(Duration::from_secs(20), move || { network_clone.is_round_reached(TARGET_ROUND) });
141  
142      // Break the quorum by disconnecting two nodes.
143      const NUM_NODES: u16 = 2;
144      network.disconnect(NUM_NODES).await;
145  
146      // Check the nodes have stopped advancing through the rounds.
147      assert!(network.is_halted().await);
148  }
149  
150  #[tokio::test(flavor = "multi_thread")]
151  async fn test_storage_coherence() {
152      // Start N nodes, connect them and start the cannons for each.
153      const N: u16 = 4;
154      const TRANSMISSION_INTERVAL_MS: u64 = 10;
155      let mut network = tokio::task::spawn_blocking(|| {
156          TestNetwork::new(TestNetworkConfig {
157              num_nodes: N,
158              bft: false,
159              connect_all: true,
160              fire_transmissions: Some(TRANSMISSION_INTERVAL_MS),
161              // Set this to Some(0..=4) to see the logs.
162              log_level: None,
163              log_connections: true,
164          })
165      })
166      .await
167      .unwrap();
168      network.start().await;
169  
170      // Check the nodes have started advancing through the rounds.
171      const TARGET_ROUND: u64 = 12;
172      // Note: cloning the network is fine because the primaries it wraps are `Arc`ed.
173      let network_clone = network.clone();
174      deadline!(Duration::from_secs(40), move || { network_clone.is_round_reached(TARGET_ROUND) });
175  
176      // Check the committee is coherent across the network up to the target round. We skip the
177      // genesis round.
178      assert!(network.is_committee_coherent(1..TARGET_ROUND));
179  
180      // Check the round certificates are coherent across the network. We skip the genesis round and
181      // check only up to 3 rounds before the the target round, because the network advances when
182      // quorum is reached, not when all the nodes have completed the round and received certificates.
183      assert!(network.is_certificate_round_coherent(1..TARGET_ROUND - 2));
184  }