consensus_config/
test_committee.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::net::{TcpListener, TcpStream};
5
6use fastcrypto::traits::{KeyPair as _, ToFromBytes as _};
7use mysten_network::Multiaddr;
8use rand::{SeedableRng as _, rngs::StdRng};
9
10use crate::{Authority, AuthorityName, Committee, Epoch, NetworkKeyPair, ProtocolKeyPair, Stake};
11
12/// Creates a committee for local testing, and the corresponding key pairs for the authorities.
13pub fn local_committee_and_keys(
14    epoch: Epoch,
15    authorities_stake: Vec<Stake>,
16) -> (Committee, Vec<(NetworkKeyPair, ProtocolKeyPair)>) {
17    local_committee_and_keys_with_test_options(epoch, authorities_stake, true)
18}
19
20pub fn local_committee_and_keys_with_test_options(
21    epoch: Epoch,
22    authorities_stake: Vec<Stake>,
23    unused_port: bool,
24) -> (Committee, Vec<(NetworkKeyPair, ProtocolKeyPair)>) {
25    let mut authorities = vec![];
26    let mut key_pairs = vec![];
27    let mut rng = StdRng::from_seed([0; 32]);
28    for (i, stake) in authorities_stake.into_iter().enumerate() {
29        let authority_keypair = fastcrypto::bls12381::min_sig::BLS12381KeyPair::generate(&mut rng);
30        let protocol_keypair = ProtocolKeyPair::generate(&mut rng);
31        let network_keypair = NetworkKeyPair::generate(&mut rng);
32        authorities.push(Authority {
33            stake,
34            address: if unused_port {
35                get_available_local_address()
36            } else {
37                "/ip4/127.0.0.1/udp/8081".parse().unwrap()
38            },
39            hostname: format!("test_host_{i}").to_string(),
40            authority_name: AuthorityName::from_bytes(authority_keypair.public().as_bytes()),
41            protocol_key: protocol_keypair.public(),
42            network_key: network_keypair.public(),
43        });
44        key_pairs.push((network_keypair, protocol_keypair));
45    }
46
47    let committee = Committee::new(epoch, authorities);
48    (committee, key_pairs)
49}
50
51/// Returns a local address with an ephemeral port.
52fn get_available_local_address() -> Multiaddr {
53    let host = "127.0.0.1";
54    let port = get_available_port(host);
55    format!("/ip4/{}/udp/{}", host, port).parse().unwrap()
56}
57
58/// Returns an ephemeral, available port. On unix systems, the port returned will be in the
59/// TIME_WAIT state ensuring that the OS won't hand out this port for some grace period.
60/// Callers should be able to bind to this port given they use SO_REUSEADDR.
61fn get_available_port(host: &str) -> u16 {
62    const MAX_PORT_RETRIES: u32 = 1000;
63
64    for _ in 0..MAX_PORT_RETRIES {
65        if let Ok(port) = get_ephemeral_port(host) {
66            return port;
67        }
68    }
69
70    panic!("Error: could not find an available port");
71}
72
73fn get_ephemeral_port(host: &str) -> std::io::Result<u16> {
74    // Request a random available port from the OS
75    let listener = TcpListener::bind((host, 0))?;
76    let addr = listener.local_addr()?;
77
78    // Create and accept a connection (which we'll promptly drop) in order to force the port
79    // into the TIME_WAIT state, ensuring that the port will be reserved from some limited
80    // amount of time (roughly 60s on some Linux systems)
81    let _sender = TcpStream::connect(addr)?;
82    let _incoming = listener.accept()?;
83
84    Ok(addr.port())
85}