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