use crate::{
gossipsub::config::default_gossipsub_config,
heartbeat,
peer_manager::ConnectionState,
TryPeerId,
};
use fuel_core_types::blockchain::consensus::Genesis;
use libp2p::{
gossipsub,
identity::{
secp256k1,
Keypair,
},
noise,
Multiaddr,
PeerId,
};
use std::{
collections::HashSet,
net::{
IpAddr,
Ipv4Addr,
},
sync::{
Arc,
RwLock,
},
time::Duration,
};
use self::{
connection_tracker::ConnectionTracker,
fuel_authenticated::FuelAuthenticated,
fuel_upgrade::Checksum,
};
mod connection_tracker;
mod fuel_authenticated;
pub(crate) mod fuel_upgrade;
const REQ_RES_TIMEOUT: Duration = Duration::from_secs(20);
pub const MAX_RESPONSE_SIZE: usize = 18 * 1024 * 1024;
pub const MAX_HEADERS_PER_REQUEST: u32 = 100;
#[derive(Clone, Debug)]
pub struct Config<State = Initialized> {
pub keypair: Keypair,
pub network_name: String,
pub checksum: Checksum,
pub address: IpAddr,
pub public_address: Option<Multiaddr>,
pub tcp_port: u16,
pub max_block_size: usize,
pub max_headers_per_request: u32,
pub bootstrap_nodes: Vec<Multiaddr>,
pub enable_mdns: bool,
pub allow_private_addresses: bool,
pub random_walk: Option<Duration>,
pub connection_idle_timeout: Option<Duration>,
pub reserved_nodes: Vec<Multiaddr>,
pub reserved_nodes_only_mode: bool,
pub max_peers_connected: u32,
pub max_connections_per_peer: u32,
pub identify_interval: Option<Duration>,
pub info_interval: Option<Duration>,
pub gossipsub_config: gossipsub::Config,
pub heartbeat_config: heartbeat::Config,
pub set_request_timeout: Duration,
pub max_concurrent_streams: usize,
pub set_connection_keep_alive: Duration,
pub heartbeat_check_interval: Duration,
pub heartbeat_max_avg_interval: Duration,
pub heartbeat_max_time_since_last: Duration,
pub metrics: bool,
pub state: State,
}
#[derive(Clone, Debug)]
pub struct Initialized(());
#[derive(Clone, Debug)]
pub struct NotInitialized;
impl Config<NotInitialized> {
pub fn init(self, genesis: Genesis) -> anyhow::Result<Config<Initialized>> {
use fuel_core_chain_config::GenesisCommitment;
Ok(Config {
keypair: self.keypair,
network_name: self.network_name,
checksum: genesis.root()?.into(),
address: self.address,
public_address: self.public_address,
tcp_port: self.tcp_port,
max_block_size: self.max_block_size,
max_headers_per_request: self.max_headers_per_request,
bootstrap_nodes: self.bootstrap_nodes,
enable_mdns: self.enable_mdns,
max_peers_connected: self.max_peers_connected,
max_connections_per_peer: self.max_connections_per_peer,
allow_private_addresses: self.allow_private_addresses,
random_walk: self.random_walk,
connection_idle_timeout: self.connection_idle_timeout,
reserved_nodes: self.reserved_nodes,
reserved_nodes_only_mode: self.reserved_nodes_only_mode,
identify_interval: self.identify_interval,
info_interval: self.info_interval,
gossipsub_config: self.gossipsub_config,
heartbeat_config: self.heartbeat_config,
set_request_timeout: self.set_request_timeout,
max_concurrent_streams: self.max_concurrent_streams,
set_connection_keep_alive: self.set_connection_keep_alive,
heartbeat_check_interval: self.heartbeat_check_interval,
heartbeat_max_avg_interval: self.heartbeat_max_time_since_last,
heartbeat_max_time_since_last: self.heartbeat_max_time_since_last,
metrics: self.metrics,
state: Initialized(()),
})
}
}
pub fn convert_to_libp2p_keypair(
secret_key_bytes: impl AsMut<[u8]>,
) -> anyhow::Result<Keypair> {
let secret_key = secp256k1::SecretKey::try_from_bytes(secret_key_bytes)?;
let keypair: secp256k1::Keypair = secret_key.into();
Ok(keypair.into())
}
impl Config<NotInitialized> {
pub fn default(network_name: &str) -> Self {
let keypair = Keypair::generate_secp256k1();
Self {
keypair,
network_name: network_name.into(),
checksum: Default::default(),
address: IpAddr::V4(Ipv4Addr::from([0, 0, 0, 0])),
public_address: None,
tcp_port: 0,
max_block_size: MAX_RESPONSE_SIZE,
max_headers_per_request: MAX_HEADERS_PER_REQUEST,
bootstrap_nodes: vec![],
enable_mdns: false,
max_peers_connected: 50,
max_connections_per_peer: 3,
allow_private_addresses: true,
random_walk: Some(Duration::from_millis(500)),
connection_idle_timeout: Some(Duration::from_secs(120)),
reserved_nodes: vec![],
reserved_nodes_only_mode: false,
gossipsub_config: default_gossipsub_config(),
heartbeat_config: heartbeat::Config::default(),
set_request_timeout: REQ_RES_TIMEOUT,
max_concurrent_streams: 256,
set_connection_keep_alive: REQ_RES_TIMEOUT,
heartbeat_check_interval: Duration::from_secs(10),
heartbeat_max_avg_interval: Duration::from_secs(20),
heartbeat_max_time_since_last: Duration::from_secs(40),
info_interval: Some(Duration::from_secs(3)),
identify_interval: Some(Duration::from_secs(5)),
metrics: false,
state: NotInitialized,
}
}
}
#[cfg(any(feature = "test-helpers", test))]
impl Config<Initialized> {
pub fn default_initialized(network_name: &str) -> Self {
Config::<NotInitialized>::default(network_name)
.init(Default::default())
.expect("Expected correct initialization of config")
}
}
pub(crate) fn build_transport_function(
p2p_config: &Config,
) -> (
impl FnOnce(&Keypair) -> Result<FuelAuthenticated<ConnectionTracker>, ()> + '_,
Arc<RwLock<ConnectionState>>,
) {
let connection_state = ConnectionState::new();
let kept_connection_state = connection_state.clone();
let transport_function = move |keypair: &Keypair| {
let noise_authenticated =
noise::Config::new(keypair).expect("Noise key generation failed");
let connection_state = if p2p_config.reserved_nodes_only_mode {
None
} else {
Some(connection_state)
};
let connection_tracker =
ConnectionTracker::new(&p2p_config.reserved_nodes, connection_state);
Ok(FuelAuthenticated::new(
noise_authenticated,
connection_tracker,
p2p_config.checksum,
))
};
(transport_function, kept_connection_state)
}
fn peer_ids_set_from(multiaddr: &[Multiaddr]) -> HashSet<PeerId> {
multiaddr
.iter()
.map(|address| address.try_to_peer_id().unwrap())
.collect()
}