TAPLE Core uses libp2p for peer-to-peer networking, providing flexible transport options and efficient peer discovery through Kademlia DHT.
Network Settings
Configure the P2P network through NetworkSettings (from core/src/commons/settings/mod.rs:18-38):
pub struct NetworkSettings {
/// Multiaddr to listen on
pub listen_addr: Vec<ListenAddr>,
/// Bootstrap nodes to connect to
pub known_nodes: Vec<String>,
/// External addresses to advertise
pub external_address: Vec<String>,
}
impl Default for NetworkSettings {
fn default() -> Self {
Self {
listen_addr: vec![ListenAddr::default()],
known_nodes: Vec::<String>::new(),
external_address: vec![],
}
}
}
Listen Addresses
TAPLE supports multiple transport protocols through the ListenAddr enum.
Available Transports
From core/src/commons/settings/mod.rs:44-57:
pub enum ListenAddr {
/// In-memory addressing (for testing)
Memory { port: Option<u32> },
/// IPv4 address
IP4 {
addr: Option<std::net::Ipv4Addr>,
port: Option<u32>,
},
/// IPv6 address
IP6 {
addr: Option<std::net::Ipv6Addr>,
port: Option<u32>,
},
}
Default Configuration
The default listen address is 0.0.0.0:40040 (from core/src/commons/settings/mod.rs:59-66):
impl Default for ListenAddr {
fn default() -> Self {
Self::IP4 {
addr: Some(std::net::Ipv4Addr::new(0, 0, 0, 0)),
port: Some(40040),
}
}
}
Addresses are represented as MultiAddr strings:
// IPv4
"/ip4/0.0.0.0/tcp/40040"
"/ip4/192.168.1.100/tcp/50000"
// IPv6
"/ip6/::/tcp/40040"
"/ip6/::1/tcp/50000"
// Memory (testing only)
"/memory/12345"
Parse from string (from core/src/commons/settings/mod.rs:124-223):
let listen_addr = ListenAddr::try_from("/ip4/0.0.0.0/tcp/40040".to_string())?;
Network Processor
The NetworkProcessor manages the libp2p swarm and handles network events.
Initialization
From core/src/network/network.rs:173-235:
pub fn new(
addr: Vec<ListenAddr>,
bootstrap_nodes: Vec<(PeerId, Multiaddr)>,
event_sender: mpsc::Sender<NetworkEvent>,
controller_mc: KeyPair,
token: CancellationToken,
notification_tx: tokio::sync::mpsc::Sender<Notification>,
external_addresses: Vec<Multiaddr>,
) -> Result<Self, Box<dyn Error>> {
// Validate transport protocol
let transport_protocol = check_listen_addr_integrity(&addr)?;
// Get public key and create libp2p keypair
let public_key = controller_mc.public_key_bytes();
let local_key = {
let sk = ed25519::SecretKey::from_bytes(controller_mc.secret_key_bytes())
.expect("we always pass 32 bytes");
Keypair::Ed25519(sk.into())
};
// Create authenticated noise keypair
let noise_key: noise::AuthenticKeypair<noise::X25519Spec> =
noise::Keypair::<noise::X25519Spec>::new()
.into_authentic(&local_key)
.expect("Signing libp2p-noise static DH keypair failed.");
// Create transport
let transport = create_transport_by_protocol(transport_protocol, noise_key);
let peer_id = local_key.public().to_peer_id();
// Build swarm
let swarm = SwarmBuilder::new(
transport,
TapleNetworkBehavior::new(local_key, bootstrap_nodes.clone()),
peer_id,
)
.executor(Box::new(|fut| {
tokio::spawn(fut);
}))
.build();
// Create channels
let (command_sender, command_receiver) = mpsc::channel(10000);
Ok(Self {
node_public_key: public_key,
addr,
swarm,
command_sender,
command_receiver,
event_sender,
pendings: HashMap::new(),
active_get_querys: HashSet::new(),
token,
notification_tx,
bootstrap_nodes,
pending_bootstrap_nodes: HashMap::new(),
bootstrap_retries_steam: futures::stream::futures_unordered::FuturesUnordered::new(),
external_addresses,
})
}
Transport Protocols
TAPLE creates different transports based on the listen address type.
TCP Transport (Production)
From core/src/network/network.rs:666-702:
fn create_ip4_ip6_transport(
noise_key: noise::AuthenticKeypair<noise::X25519Spec>,
) -> Boxed<(PeerId, StreamMuxerBox)> {
let transport = TokioTcpConfig::new()
.nodelay(true)
.upgrade(upgrade::Version::V1)
.authenticate(noise::NoiseConfig::xx(noise_key.clone()).into_authenticated())
.multiplex(mplex::MplexConfig::new())
.boxed();
// Add DNS resolution
match dns::GenDnsConfig::system(transport) {
Ok(t) => t.boxed(),
Err(_) => {
// Fallback to Cloudflare DNS
let transport = TokioTcpConfig::new()
.nodelay(true)
.upgrade(upgrade::Version::V1)
.authenticate(noise::NoiseConfig::xx(noise_key.clone()).into_authenticated())
.multiplex(mplex::MplexConfig::new())
.boxed();
match dns::GenDnsConfig::custom(
transport,
dns::ResolverConfig::cloudflare(),
dns::ResolverOpts::default(),
) {
Ok(t) => t.boxed(),
Err(_) => TokioTcpConfig::new()
.nodelay(true)
.upgrade(upgrade::Version::V1)
.authenticate(noise::NoiseConfig::xx(noise_key.clone()).into_authenticated())
.multiplex(mplex::MplexConfig::new())
.boxed(),
}
}
}
}
Memory Transport (Testing)
From core/src/network/network.rs:704-716:
ListenProtocols::Memory => MemoryTransport
.upgrade(upgrade::Version::V1)
.authenticate(noise::NoiseConfig::xx(noise_key.clone()).into_authenticated())
.multiplex(yamux::YamuxConfig::default())
.boxed()
Network Behavior
TAPLE uses a composed behavior combining routing (Kademlia) and messaging (Tell protocol).
From core/src/network/network.rs:63-95:
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "NetworkComposedEvent")]
pub struct TapleNetworkBehavior {
routing: RoutingBehaviour,
tell: TellBehaviour,
}
impl TapleNetworkBehavior {
pub fn new(local_key: Keypair, bootstrap_nodes: Vec<(PeerId, Multiaddr)>) -> Self {
let routing = RoutingBehaviour::new(local_key, bootstrap_nodes);
let tell = TellBehaviour::new(
100000,
Duration::from_secs(10),
Duration::from_secs(10)
);
TapleNetworkBehavior { routing, tell }
}
}
Peer Discovery
TAPLE uses Kademlia DHT for peer discovery and routing.
Bootstrap Nodes
Connect to bootstrap nodes on startup (from core/src/network/network.rs:262-266):
for (_peer_id, addr) in self.bootstrap_nodes.iter() {
let Ok(()) = self.swarm.dial(addr.to_owned()) else {
panic!("Connection with bootstrap failed");
};
}
Bootstrap node format:
let bootstrap_nodes = vec![
"/ip4/35.181.1.20/tcp/40040/p2p/12D3KooWLXexpg81PjdjnrhmHUxN7U5EtfXJgr9cahei1SJ9Ub3B"
.to_string(),
];
Automatic Retry
Failed bootstrap connections are automatically retried (from core/src/network/network.rs:349-367):
SwarmEvent::OutgoingConnectionError { error, peer_id } => {
if let Some(peer_id) = peer_id {
// Remove failed route
self.swarm.behaviour_mut().tell.remove_route(&peer_id);
// Check if this was a bootstrap node
if let Some((id, multiaddr)) =
self.bootstrap_nodes.iter().find(|(id, _)| *id == peer_id)
{
// Add to pending retry list
self.pending_bootstrap_nodes
.insert(*id, multiaddr.to_owned());
// Schedule retry if not already scheduled
if self.bootstrap_retries_steam.len() == 0 {
self.bootstrap_retries_steam
.push(tokio::time::sleep(Duration::from_millis(30000)));
}
}
}
}
Closest Peer Query
Find peers through DHT queries (from core/src/network/network.rs:612-625):
if let None = self.active_get_querys.get(&peer_id) {
// Make query to find peer
self.active_get_querys.insert(peer_id.clone());
let query_id = self
.swarm
.behaviour_mut()
.routing
.get_closest_peers(peer_id.clone());
debug!(
"Query get_record {:?} to send request to {:?}",
query_id, peer_id
);
}
Message Sending
Messages are sent through the Tell protocol with automatic routing.
Send Command
From core/src/network/network.rs:575-640:
Command::SendMessage { receptor, message } => {
// Check if we are the receptor (loopback)
if receptor == self.node_public_key {
self.event_sender
.send(NetworkEvent::MessageReceived { message })
.await
.expect("Event receiver not to be dropped.");
return;
}
// Convert public key to PeerId
let peer_id = match libp2p::identity::ed25519::PublicKey::decode(&receptor) {
Ok(public_key) => {
let public_key = libp2p::core::PublicKey::Ed25519(public_key);
PeerId::from_public_key(&public_key)
}
Err(_error) => {
log::error!("Invalid controller ID");
return;
}
};
// Check if we have the peer's address
let addresses_of_peer = self.swarm.behaviour_mut().addresses_of_peer(&peer_id);
if !addresses_of_peer.is_empty() {
// Send immediately
self.swarm
.behaviour_mut()
.tell
.send_message(&peer_id, &message);
return;
}
// Query DHT for peer if not already querying
if let None = self.active_get_querys.get(&peer_id) {
self.active_get_querys.insert(peer_id.clone());
self.swarm
.behaviour_mut()
.routing
.get_closest_peers(peer_id.clone());
}
// Queue message until peer is found
match self.pendings.get_mut(&peer_id) {
Some(pending_list) => {
if pending_list.len() >= 100 {
pending_list.pop_front();
}
pending_list.push_back(message);
}
None => {
let mut pendings = VecDeque::new();
pendings.push_back(message);
self.pendings.insert(peer_id, pendings);
}
}
}
External Addresses
Advertise external addresses for NAT traversal (from core/src/network/network.rs:244-248):
for external_address in self.external_addresses.clone().into_iter() {
self.swarm
.add_external_address(external_address, AddressScore::Infinite);
}
Do not mix Memory and TCP transports in the same node. The network processor validates that all listen addresses use the same protocol type.
Configuration Examples
Development (Single Node)
let mut settings = Settings::default();
settings.network.listen_addr = vec![
ListenAddr::IP4 {
addr: Some(std::net::Ipv4Addr::new(127, 0, 0, 1)),
port: Some(40040),
}
];
settings.network.known_nodes = vec![];
Production (With Bootstrap)
let mut settings = Settings::default();
settings.network.listen_addr = vec![
ListenAddr::IP4 {
addr: Some(std::net::Ipv4Addr::new(0, 0, 0, 0)),
port: Some(40040),
}
];
settings.network.known_nodes = vec![
"/ip4/35.181.1.20/tcp/40040/p2p/12D3KooWLXexpg81PjdjnrhmHUxN7U5EtfXJgr9cahei1SJ9Ub3B"
.to_string(),
];
settings.network.external_address = vec![
"/ip4/203.0.113.42/tcp/40040".to_string(),
];
Testing (Memory Transport)
let mut settings = Settings::default();
settings.network.listen_addr = vec![
ListenAddr::Memory { port: Some(12345) }
];
Best Practices
Port Configuration
- Use ports above 1024 to avoid requiring root privileges
- Default port is 40040 - change if running multiple nodes on same host
- Ensure firewall rules allow incoming connections
Bootstrap Nodes
- Use at least 2-3 bootstrap nodes for redundancy
- Bootstrap nodes should have static IPs or DNS names
- Include your own bootstrap nodes for private networks
External Addresses
- Configure external addresses if behind NAT
- Use DNS names for dynamic IPs
- Test connectivity from outside your network
Security
- All connections use Noise protocol encryption
- Ed25519 keys for node identity
- Authenticated transport prevents impersonation