#[allow(dead_code)]
mod common;
use crate::common::{sample_account, sample_genesis_block};
use snarkos_account::Account;
use snarkos_node::{
BootstrapClient,
bft::events::{DisconnectReason, Event, HANDSHAKE_DOMAIN, HandshakeHint, InitiatorInfo, PeerInfo, ResponderProof},
network::{
PeerPoolHandling,
noise::{NoiseSession, Role, binding_message, write_noise_magic},
},
tcp::P2P,
};
use snarkvm::{
ledger::narwhal::Data,
prelude::{FromBytes, MainnetV0 as CurrentNetwork, TestRng, ToBytes},
};
use std::{net::SocketAddr, time::Duration};
use deadline::deadline;
use tokio::net::TcpStream;
async fn new_test_bootstrap_client() -> BootstrapClient<CurrentNetwork> {
let rng = &mut TestRng::default();
let listener_addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
let genesis_header = *sample_genesis_block().header();
BootstrapClient::new(listener_addr, sample_account(rng), genesis_header, Some(0)).await.unwrap()
}
fn dial_addr(client: &BootstrapClient<CurrentNetwork>) -> SocketAddr {
SocketAddr::from(([127, 0, 0, 1], client.tcp().listening_addr().unwrap().port()))
}
async fn handshake_with(
client: &BootstrapClient<CurrentNetwork>,
account: &Account<CurrentNetwork>,
version: u32,
) -> std::io::Result<Option<(ResponderProof<CurrentNetwork>, NoiseSession<TcpStream>)>> {
let mut stream = TcpStream::connect(dial_addr(client)).await?;
write_noise_magic(&mut stream).await?;
let mut noise = NoiseSession::new(stream, Role::Initiator)?;
let hint = HandshakeHint { version, listener_port: 5000, address: account.address() };
noise.send(&hint.to_bytes_le().unwrap()).await?;
let Ok(peer_info) = noise.recv().await else {
return Ok(None);
};
let peer_info = PeerInfo::<CurrentNetwork>::from_bytes_le(&peer_info).unwrap();
let binding = binding_message(HANDSHAKE_DOMAIN, Role::Initiator, &noise.handshake_hash()?);
let signature = account.sign_bytes(&binding, &mut rand::rng()).unwrap();
let our_info = PeerInfo::new(5000, account.address(), peer_info.restrictions_id, None);
let our_message = InitiatorInfo { info: our_info, signature: Data::Object(signature) };
noise.send(&our_message.to_bytes_le().unwrap()).await?;
let mut noise = noise.into_transport_mode()?;
let verdict = ResponderProof::from_bytes_le(&noise.recv().await?).unwrap();
Ok(Some((verdict, noise)))
}
#[tokio::test(flavor = "multi_thread")]
async fn a_validator_completes_a_noise_handshake_with_a_bootstrap_client() {
let client = new_test_bootstrap_client().await;
let rng = &mut TestRng::default();
let validator = Account::<CurrentNetwork>::new(rng).unwrap();
let version = Event::<CurrentNetwork>::VERSION;
let verdict = handshake_with(&client, &validator, version).await.unwrap();
let Some((ResponderProof::Accepted { signature }, _session)) = verdict else {
panic!("expected the handshake to be accepted");
};
assert!(signature.deserialize_blocking().is_ok());
let client_ = client.clone();
let address = validator.address();
deadline!(Duration::from_secs(5), move || {
client_.get_connected_peers().iter().any(|peer| peer.aleo_addr == address)
});
}
#[tokio::test(flavor = "multi_thread")]
async fn an_outdated_validator_is_dropped_before_the_bootstrap_client_replies() {
let client = new_test_bootstrap_client().await;
let rng = &mut TestRng::default();
let validator = Account::<CurrentNetwork>::new(rng).unwrap();
let verdict = handshake_with(&client, &validator, 0).await.unwrap();
assert!(verdict.is_none(), "the bootstrap client should not have replied to an outdated peer");
assert_eq!(client.tcp().num_connected(), 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn a_bootstrap_client_rejects_an_unprovable_identity() {
let client = new_test_bootstrap_client().await;
let rng = &mut TestRng::default();
let validator = Account::<CurrentNetwork>::new(rng).unwrap();
let impostor = Account::<CurrentNetwork>::new(rng).unwrap();
let mut stream = TcpStream::connect(dial_addr(&client)).await.unwrap();
write_noise_magic(&mut stream).await.unwrap();
let mut noise = NoiseSession::new(stream, Role::Initiator).unwrap();
let version = Event::<CurrentNetwork>::VERSION;
let hint = HandshakeHint { version, listener_port: 5001, address: validator.address() };
noise.send(&hint.to_bytes_le().unwrap()).await.unwrap();
let peer_info = PeerInfo::<CurrentNetwork>::from_bytes_le(&noise.recv().await.unwrap()).unwrap();
let binding = binding_message(HANDSHAKE_DOMAIN, Role::Initiator, &noise.handshake_hash().unwrap());
let signature = impostor.sign_bytes(&binding, &mut rand::rng()).unwrap();
let our_info = PeerInfo::new(5001, validator.address(), peer_info.restrictions_id, None);
let our_message = InitiatorInfo { info: our_info, signature: Data::Object(signature) };
noise.send(&our_message.to_bytes_le().unwrap()).await.unwrap();
let mut noise = noise.into_transport_mode().unwrap();
let verdict = ResponderProof::<CurrentNetwork>::from_bytes_le(&noise.recv().await.unwrap()).unwrap();
assert_eq!(verdict, ResponderProof::Rejected { reason: DisconnectReason::InvalidChallengeResponse });
}