feat: enhance relay address validation and configuration management
This commit is contained in:
parent
9c3f7250df
commit
f97cde8608
|
|
@ -337,10 +337,14 @@ pub async fn set_relay_address(
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
relay_addr: String,
|
relay_addr: String,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// validate the relay address format
|
// validate relay format and require /p2p/<peer-id> component
|
||||||
let _ = relay_addr
|
let (validated_multiaddr, validated_peer_id) =
|
||||||
.parse::<libp2p::Multiaddr>()
|
crate::node::validate_relay_multiaddr(&relay_addr)?;
|
||||||
.map_err(|_| "invalid relay address format")?;
|
log::info!(
|
||||||
|
"updating relay address to {} (peer {})",
|
||||||
|
validated_multiaddr,
|
||||||
|
validated_peer_id
|
||||||
|
);
|
||||||
|
|
||||||
// stop the current node if running
|
// stop the current node if running
|
||||||
{
|
{
|
||||||
|
|
@ -356,7 +360,7 @@ pub async fn set_relay_address(
|
||||||
|
|
||||||
// update settings with the new relay address
|
// update settings with the new relay address
|
||||||
let mut settings = state.storage.load_settings().unwrap_or_default();
|
let mut settings = state.storage.load_settings().unwrap_or_default();
|
||||||
settings.custom_relay_addr = Some(relay_addr);
|
settings.custom_relay_addr = Some(validated_multiaddr.to_string());
|
||||||
state
|
state
|
||||||
.storage
|
.storage
|
||||||
.save_settings(&settings)
|
.save_settings(&settings)
|
||||||
|
|
|
||||||
|
|
@ -21,28 +21,20 @@ const DEFAULT_RELAY_ADDR: &str =
|
||||||
const RELAY_INITIAL_BACKOFF_SECS: u64 = 2;
|
const RELAY_INITIAL_BACKOFF_SECS: u64 = 2;
|
||||||
const RELAY_MAX_BACKOFF_SECS: u64 = 120;
|
const RELAY_MAX_BACKOFF_SECS: u64 = 120;
|
||||||
const RELAY_BACKOFF_MULTIPLIER: u64 = 2;
|
const RELAY_BACKOFF_MULTIPLIER: u64 = 2;
|
||||||
|
const RENDEZVOUS_TICK_SECS: u64 = 120;
|
||||||
|
const KAD_BOOTSTRAP_TICK_SECS: u64 = 180;
|
||||||
|
const DUSK_BOOTSTRAP_PEERS_ENV: &str = "DUSK_BOOTSTRAP_PEERS";
|
||||||
// max time to hold pending rendezvous registrations before discarding (10 min)
|
// max time to hold pending rendezvous registrations before discarding (10 min)
|
||||||
const PENDING_QUEUE_TTL_SECS: u64 = 600;
|
const PENDING_QUEUE_TTL_SECS: u64 = 600;
|
||||||
// grace period before warning the frontend about relay being down,
|
// grace period before warning the frontend about relay being down,
|
||||||
// prevents banner flashing on transient disconnections
|
// prevents banner flashing on transient disconnections
|
||||||
const RELAY_WARN_GRACE_SECS: u64 = 8;
|
const RELAY_WARN_GRACE_SECS: u64 = 8;
|
||||||
|
|
||||||
// resolve the relay multiaddr from env var, custom setting, or default
|
#[derive(Clone)]
|
||||||
// priority: DUSK_RELAY_ADDR env var > custom setting > DEFAULT_RELAY_ADDR
|
struct RelayConfig {
|
||||||
fn relay_addr(custom_addr: Option<&str>) -> Option<libp2p::Multiaddr> {
|
addr: libp2p::Multiaddr,
|
||||||
let addr_str = std::env::var("DUSK_RELAY_ADDR")
|
peer_id: libp2p::PeerId,
|
||||||
.ok()
|
source: &'static str,
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.or_else(|| custom_addr.map(|s| s.to_string()))
|
|
||||||
.or_else(|| {
|
|
||||||
if DEFAULT_RELAY_ADDR.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(DEFAULT_RELAY_ADDR.to_string())
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
addr_str.parse().ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the peer id from a multiaddr (the /p2p/<peer_id> component)
|
// extract the peer id from a multiaddr (the /p2p/<peer_id> component)
|
||||||
|
|
@ -54,6 +46,119 @@ fn peer_id_from_multiaddr(addr: &libp2p::Multiaddr) -> Option<libp2p::PeerId> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_relay_multiaddr(
|
||||||
|
relay_addr: &str,
|
||||||
|
) -> Result<(libp2p::Multiaddr, libp2p::PeerId), String> {
|
||||||
|
let trimmed = relay_addr.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
return Err("relay address cannot be empty".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let multiaddr = trimmed.parse::<libp2p::Multiaddr>().map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"invalid relay multiaddr '{}': {}",
|
||||||
|
trimmed,
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let peer_id = peer_id_from_multiaddr(&multiaddr).ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"relay multiaddr must include '/p2p/<peer-id>': '{}'",
|
||||||
|
trimmed
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((multiaddr, peer_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve the relay multiaddr from env var, custom setting, or default
|
||||||
|
// priority: DUSK_RELAY_ADDR env var > custom setting > DEFAULT_RELAY_ADDR
|
||||||
|
fn resolve_relay_config(custom_addr: Option<&str>) -> Option<RelayConfig> {
|
||||||
|
let mut candidates: Vec<(&'static str, String)> = Vec::new();
|
||||||
|
|
||||||
|
if let Ok(env_addr) = std::env::var("DUSK_RELAY_ADDR") {
|
||||||
|
let trimmed = env_addr.trim();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
candidates.push(("DUSK_RELAY_ADDR", trimmed.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(custom) = custom_addr.map(str::trim).filter(|s| !s.is_empty()) {
|
||||||
|
candidates.push(("custom_relay_addr", custom.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_trimmed = DEFAULT_RELAY_ADDR.trim();
|
||||||
|
if !default_trimmed.is_empty() {
|
||||||
|
candidates.push(("DEFAULT_RELAY_ADDR", default_trimmed.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (source, candidate) in candidates {
|
||||||
|
match validate_relay_multiaddr(&candidate) {
|
||||||
|
Ok((addr, peer_id)) => {
|
||||||
|
return Some(RelayConfig {
|
||||||
|
addr,
|
||||||
|
peer_id,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"ignoring invalid relay address from {}: {}",
|
||||||
|
source,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bootstrap_peers(relay_config: Option<&RelayConfig>) -> Vec<(libp2p::Multiaddr, libp2p::PeerId)> {
|
||||||
|
let mut peers: Vec<(libp2p::Multiaddr, libp2p::PeerId)> = Vec::new();
|
||||||
|
let mut seen = HashSet::new();
|
||||||
|
|
||||||
|
if let Some(cfg) = relay_config {
|
||||||
|
let key = format!("{}|{}", cfg.addr, cfg.peer_id);
|
||||||
|
if seen.insert(key) {
|
||||||
|
peers.push((cfg.addr.clone(), cfg.peer_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(raw) = std::env::var(DUSK_BOOTSTRAP_PEERS_ENV) {
|
||||||
|
for addr in raw
|
||||||
|
.split(',')
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
{
|
||||||
|
match validate_relay_multiaddr(addr) {
|
||||||
|
Ok((multiaddr, peer_id)) => {
|
||||||
|
let key = format!("{}|{}", multiaddr, peer_id);
|
||||||
|
if seen.insert(key) {
|
||||||
|
peers.push((multiaddr, peer_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"ignoring invalid bootstrap peer in {}: {}",
|
||||||
|
DUSK_BOOTSTRAP_PEERS_ENV,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_namespace_unique(queue: &mut Vec<String>, namespace: String) {
|
||||||
|
if !queue.contains(&namespace) {
|
||||||
|
queue.push(namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handle to the running p2p node, used to stop it
|
// handle to the running p2p node, used to stop it
|
||||||
pub struct NodeHandle {
|
pub struct NodeHandle {
|
||||||
pub task: JoinHandle<()>,
|
pub task: JoinHandle<()>,
|
||||||
|
|
@ -267,24 +372,52 @@ pub async fn start(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// resolve the relay address for WAN connectivity
|
// resolve validated relay and bootstrap peer configuration for WAN connectivity
|
||||||
let relay_multiaddr = relay_addr(custom_relay_addr.as_deref());
|
let relay_config = resolve_relay_config(custom_relay_addr.as_deref());
|
||||||
let relay_peer_id = relay_multiaddr.as_ref().and_then(peer_id_from_multiaddr);
|
if let Some(cfg) = relay_config.as_ref() {
|
||||||
|
log::info!(
|
||||||
|
"using relay {} (peer {}) from {}",
|
||||||
|
cfg.addr,
|
||||||
|
cfg.peer_id,
|
||||||
|
cfg.source
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let relay_multiaddr = relay_config.as_ref().map(|cfg| cfg.addr.clone());
|
||||||
|
let relay_peer_id = relay_config.as_ref().map(|cfg| cfg.peer_id);
|
||||||
|
|
||||||
|
let bootstrap_nodes = bootstrap_peers(relay_config.as_ref());
|
||||||
|
if !bootstrap_nodes.is_empty() {
|
||||||
|
log::info!(
|
||||||
|
"configured {} WAN bootstrap peer(s) (relay + optional {})",
|
||||||
|
bootstrap_nodes.len(),
|
||||||
|
DUSK_BOOTSTRAP_PEERS_ENV
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (addr, peer) in &bootstrap_nodes {
|
||||||
|
swarm_instance
|
||||||
|
.behaviour_mut()
|
||||||
|
.kademlia
|
||||||
|
.add_address(peer, addr.clone());
|
||||||
|
}
|
||||||
|
|
||||||
// if a relay is configured, dial it immediately
|
// if a relay is configured, dial it immediately
|
||||||
// don't emit RelayStatus here -- the store defaults to connected=true so
|
// don't emit RelayStatus here -- the store defaults to connected=true so
|
||||||
// no warning shows during the initial handshake. the warning only appears
|
// no warning shows during the initial handshake. the warning only appears
|
||||||
// if the dial actually fails (OutgoingConnectionError) or the connection drops.
|
// if the dial actually fails (OutgoingConnectionError) or the connection drops.
|
||||||
if let Some(ref addr) = relay_multiaddr {
|
if let Some(ref addr) = relay_multiaddr {
|
||||||
log::info!("dialing relay at {}", addr);
|
log::info!("relay dial start (startup): {}", addr);
|
||||||
if let Err(e) = swarm_instance.dial(addr.clone()) {
|
if let Err(e) = swarm_instance.dial(addr.clone()) {
|
||||||
log::warn!("failed to dial relay: {}", e);
|
log::warn!("relay dial failed (startup): {}", e);
|
||||||
// emit disconnected status immediately if dial fails
|
// emit disconnected status immediately if dial fails
|
||||||
let _ = app_handle.emit("dusk-event", DuskEvent::RelayStatus { connected: false });
|
let _ = app_handle.emit("dusk-event", DuskEvent::RelayStatus { connected: false });
|
||||||
|
} else {
|
||||||
|
log::info!("relay dial initiated (startup)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if relay address is invalid or not configured, emit disconnected status
|
// if relay address is invalid or not configured, emit disconnected status
|
||||||
log::warn!("no valid relay address configured, running in LAN-only mode");
|
log::warn!(
|
||||||
|
"no valid relay address configured from DUSK_RELAY_ADDR/custom/default, running in LAN-only mode"
|
||||||
|
);
|
||||||
let _ = app_handle.emit("dusk-event", DuskEvent::RelayStatus { connected: false });
|
let _ = app_handle.emit("dusk-event", DuskEvent::RelayStatus { connected: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,11 +448,15 @@ pub async fn start(
|
||||||
// timestamp when pending items were first queued (for TTL cleanup)
|
// timestamp when pending items were first queued (for TTL cleanup)
|
||||||
let mut pending_queued_at: Option<std::time::Instant> = None;
|
let mut pending_queued_at: Option<std::time::Instant> = None;
|
||||||
|
|
||||||
// rendezvous registration refresh interval (registrations expire)
|
// rendezvous registration/rediscovery refresh interval
|
||||||
let mut rendezvous_tick = tokio::time::interval(std::time::Duration::from_secs(120));
|
let mut rendezvous_tick =
|
||||||
|
tokio::time::interval(std::time::Duration::from_secs(RENDEZVOUS_TICK_SECS));
|
||||||
|
// periodic Kademlia bootstrap fallback for WAN resilience
|
||||||
|
let mut kad_bootstrap_tick =
|
||||||
|
tokio::time::interval(std::time::Duration::from_secs(KAD_BOOTSTRAP_TICK_SECS));
|
||||||
|
|
||||||
// all community namespaces we're registered under (for refresh)
|
// all namespaces we should keep active for rendezvous register + rediscover
|
||||||
let mut registered_namespaces: HashSet<String> = HashSet::new();
|
let mut active_namespaces: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
// pending gif search replies keyed by request_response request id
|
// pending gif search replies keyed by request_response request id
|
||||||
let mut pending_gif_replies: HashMap<
|
let mut pending_gif_replies: HashMap<
|
||||||
|
|
@ -347,6 +484,54 @@ pub async fn start(
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
event = swarm_instance.select_next_some() => {
|
event = swarm_instance.select_next_some() => {
|
||||||
match event {
|
match event {
|
||||||
|
// --- kademlia fallback discovery lifecycle ---
|
||||||
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Kademlia(
|
||||||
|
libp2p::kad::Event::OutboundQueryProgressed {
|
||||||
|
id,
|
||||||
|
result: libp2p::kad::QueryResult::Bootstrap(Ok(result)),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)) => {
|
||||||
|
log::debug!(
|
||||||
|
"kademlia bootstrap progress (query {:?}): remaining={}",
|
||||||
|
id,
|
||||||
|
result.num_remaining
|
||||||
|
);
|
||||||
|
}
|
||||||
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Kademlia(
|
||||||
|
libp2p::kad::Event::OutboundQueryProgressed {
|
||||||
|
id,
|
||||||
|
result: libp2p::kad::QueryResult::Bootstrap(Err(e)),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)) => {
|
||||||
|
log::warn!("kademlia bootstrap query {:?} failed: {:?}", id, e);
|
||||||
|
}
|
||||||
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Kademlia(
|
||||||
|
libp2p::kad::Event::OutboundQueryProgressed {
|
||||||
|
id,
|
||||||
|
result: libp2p::kad::QueryResult::GetClosestPeers(result),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)) => {
|
||||||
|
match result {
|
||||||
|
Ok(ok) => {
|
||||||
|
log::debug!(
|
||||||
|
"kademlia closest-peers query {:?} returned {} peer(s)",
|
||||||
|
id,
|
||||||
|
ok.peers.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"kademlia closest-peers query {:?} failed: {:?}",
|
||||||
|
id,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- gossipsub messages ---
|
// --- gossipsub messages ---
|
||||||
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Gossipsub(
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Gossipsub(
|
||||||
libp2p::gossipsub::Event::Message { message, .. }
|
libp2p::gossipsub::Event::Message { message, .. }
|
||||||
|
|
@ -792,14 +977,20 @@ pub async fn start(
|
||||||
if let Some(rp) = relay_peer {
|
if let Some(rp) = relay_peer {
|
||||||
match libp2p::rendezvous::Namespace::new(ns.clone()) {
|
match libp2p::rendezvous::Namespace::new(ns.clone()) {
|
||||||
Ok(namespace) => {
|
Ok(namespace) => {
|
||||||
|
log::info!(
|
||||||
|
"rendezvous register start (queued replay) for namespace '{}'",
|
||||||
|
ns
|
||||||
|
);
|
||||||
if let Err(e) = swarm_instance.behaviour_mut().rendezvous.register(
|
if let Err(e) = swarm_instance.behaviour_mut().rendezvous.register(
|
||||||
namespace,
|
namespace,
|
||||||
rp,
|
rp,
|
||||||
None,
|
None,
|
||||||
) {
|
) {
|
||||||
log::warn!("failed to register on rendezvous for {}: {:?}", ns, e);
|
log::warn!(
|
||||||
} else {
|
"rendezvous register failed (queued replay) for '{}': {:?}",
|
||||||
registered_namespaces.insert(ns);
|
ns,
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -812,13 +1003,28 @@ pub async fn start(
|
||||||
let queued = std::mem::take(&mut pending_discoveries);
|
let queued = std::mem::take(&mut pending_discoveries);
|
||||||
for ns in queued {
|
for ns in queued {
|
||||||
if let Some(rp) = relay_peer {
|
if let Some(rp) = relay_peer {
|
||||||
|
match libp2p::rendezvous::Namespace::new(ns.clone()) {
|
||||||
|
Ok(namespace) => {
|
||||||
|
log::info!(
|
||||||
|
"rendezvous discover start (queued replay) for namespace '{}'",
|
||||||
|
ns
|
||||||
|
);
|
||||||
swarm_instance.behaviour_mut().rendezvous.discover(
|
swarm_instance.behaviour_mut().rendezvous.discover(
|
||||||
Some(libp2p::rendezvous::Namespace::new(ns.clone()).unwrap()),
|
Some(namespace),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
rp,
|
rp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"invalid queued rendezvous namespace '{}': {:?}",
|
||||||
|
ns,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// queues drained, reset the TTL tracker
|
// queues drained, reset the TTL tracker
|
||||||
|
|
@ -830,17 +1036,33 @@ pub async fn start(
|
||||||
// peers learn about us once the relay mesh is live
|
// peers learn about us once the relay mesh is live
|
||||||
publish_profile(&mut swarm_instance, &node_keypair, &storage);
|
publish_profile(&mut swarm_instance, &node_keypair, &storage);
|
||||||
}
|
}
|
||||||
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::RelayClient(event)) => {
|
||||||
|
log::debug!("relay client event: {:?}", event);
|
||||||
|
}
|
||||||
|
|
||||||
// --- rendezvous client events ---
|
// --- rendezvous client events ---
|
||||||
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Rendezvous(
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Rendezvous(
|
||||||
libp2p::rendezvous::client::Event::Registered { namespace, .. }
|
libp2p::rendezvous::client::Event::Registered { namespace, .. }
|
||||||
)) => {
|
)) => {
|
||||||
log::info!("registered on rendezvous under namespace '{}'", namespace);
|
log::info!(
|
||||||
registered_namespaces.insert(namespace.to_string());
|
"rendezvous register success for namespace '{}'",
|
||||||
|
namespace
|
||||||
|
);
|
||||||
|
active_namespaces.insert(namespace.to_string());
|
||||||
}
|
}
|
||||||
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Rendezvous(
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Rendezvous(
|
||||||
libp2p::rendezvous::client::Event::Discovered { registrations, .. }
|
libp2p::rendezvous::client::Event::Discovered { registrations, cookie, .. }
|
||||||
)) => {
|
)) => {
|
||||||
|
let namespace_desc = cookie
|
||||||
|
.namespace()
|
||||||
|
.map(|ns| ns.to_string())
|
||||||
|
.unwrap_or_else(|| "<all>".to_string());
|
||||||
|
log::info!(
|
||||||
|
"rendezvous discover success: namespace '{}' returned {} peer record(s)",
|
||||||
|
namespace_desc,
|
||||||
|
registrations.len()
|
||||||
|
);
|
||||||
|
|
||||||
// discovered peers on rendezvous, connect to them through the relay
|
// discovered peers on rendezvous, connect to them through the relay
|
||||||
for registration in registrations {
|
for registration in registrations {
|
||||||
let discovered_peer = registration.record.peer_id();
|
let discovered_peer = registration.record.peer_id();
|
||||||
|
|
@ -897,8 +1119,22 @@ pub async fn start(
|
||||||
.with(libp2p::multiaddr::Protocol::P2pCircuit)
|
.with(libp2p::multiaddr::Protocol::P2pCircuit)
|
||||||
.with(libp2p::multiaddr::Protocol::P2p(discovered_peer));
|
.with(libp2p::multiaddr::Protocol::P2p(discovered_peer));
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"relay-circuit dial start to discovered peer {} via {}",
|
||||||
|
discovered_peer,
|
||||||
|
relay_addr
|
||||||
|
);
|
||||||
if let Err(e) = swarm_instance.dial(circuit_addr) {
|
if let Err(e) = swarm_instance.dial(circuit_addr) {
|
||||||
log::warn!("failed to dial peer {} through relay: {}", discovered_peer, e);
|
log::warn!(
|
||||||
|
"relay-circuit dial failed for peer {}: {}",
|
||||||
|
discovered_peer,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log::info!(
|
||||||
|
"relay-circuit dial initiated for peer {}",
|
||||||
|
discovered_peer
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -908,6 +1144,23 @@ pub async fn start(
|
||||||
)) => {
|
)) => {
|
||||||
log::warn!("rendezvous registration failed for '{}': {:?}", namespace, error);
|
log::warn!("rendezvous registration failed for '{}': {:?}", namespace, error);
|
||||||
}
|
}
|
||||||
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Rendezvous(
|
||||||
|
libp2p::rendezvous::client::Event::DiscoverFailed { namespace, error, .. }
|
||||||
|
)) => {
|
||||||
|
let ns = namespace
|
||||||
|
.map(|ns| ns.to_string())
|
||||||
|
.unwrap_or_else(|| "<all>".to_string());
|
||||||
|
log::warn!(
|
||||||
|
"rendezvous discover failed for namespace '{}': {:?}",
|
||||||
|
ns,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Rendezvous(
|
||||||
|
libp2p::rendezvous::client::Event::Expired { peer }
|
||||||
|
)) => {
|
||||||
|
log::debug!("rendezvous registration expired for peer {}", peer);
|
||||||
|
}
|
||||||
|
|
||||||
// --- identify events ---
|
// --- identify events ---
|
||||||
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Identify(
|
libp2p::swarm::SwarmEvent::Behaviour(behaviour::DuskBehaviourEvent::Identify(
|
||||||
|
|
@ -950,6 +1203,10 @@ pub async fn start(
|
||||||
swarm_instance.behaviour_mut().gossipsub.add_explicit_peer(&peer_id);
|
swarm_instance.behaviour_mut().gossipsub.add_explicit_peer(&peer_id);
|
||||||
connected_peers.insert(peer_id.to_string());
|
connected_peers.insert(peer_id.to_string());
|
||||||
|
|
||||||
|
if Some(peer_id) == relay_peer {
|
||||||
|
log::info!("relay dial success: connected to relay peer {}", peer_id);
|
||||||
|
}
|
||||||
|
|
||||||
let _ = app_handle.emit("dusk-event", DuskEvent::PeerConnected {
|
let _ = app_handle.emit("dusk-event", DuskEvent::PeerConnected {
|
||||||
peer_id: peer_id.to_string(),
|
peer_id: peer_id.to_string(),
|
||||||
});
|
});
|
||||||
|
|
@ -973,9 +1230,15 @@ pub async fn start(
|
||||||
let relay_circuit_addr = addr.clone()
|
let relay_circuit_addr = addr.clone()
|
||||||
.with(libp2p::multiaddr::Protocol::P2pCircuit);
|
.with(libp2p::multiaddr::Protocol::P2pCircuit);
|
||||||
|
|
||||||
log::info!("connected to relay, requesting reservation");
|
log::info!(
|
||||||
|
"relay reservation request start via listen_on {}",
|
||||||
|
relay_circuit_addr
|
||||||
|
);
|
||||||
if let Err(e) = swarm_instance.listen_on(relay_circuit_addr) {
|
if let Err(e) = swarm_instance.listen_on(relay_circuit_addr) {
|
||||||
log::warn!("failed to listen on relay circuit: {}", e);
|
log::warn!(
|
||||||
|
"relay reservation request failed (listen_on error): {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1039,7 +1302,10 @@ pub async fn start(
|
||||||
// and schedule a retry with backoff
|
// and schedule a retry with backoff
|
||||||
if Some(peer_id) == relay_peer {
|
if Some(peer_id) == relay_peer {
|
||||||
relay_reservation_active = false;
|
relay_reservation_active = false;
|
||||||
log::warn!("lost connection to relay, scheduling reconnect in {}s", relay_backoff_secs);
|
log::warn!(
|
||||||
|
"relay reservation closed (relay connection dropped), scheduling reconnect in {}s",
|
||||||
|
relay_backoff_secs
|
||||||
|
);
|
||||||
// defer the warning so quick reconnections don't flash the banner
|
// defer the warning so quick reconnections don't flash the banner
|
||||||
if relay_warn_at.is_none() {
|
if relay_warn_at.is_none() {
|
||||||
relay_warn_at = Some(
|
relay_warn_at = Some(
|
||||||
|
|
@ -1087,13 +1353,54 @@ pub async fn start(
|
||||||
_ = rendezvous_tick.tick() => {
|
_ = rendezvous_tick.tick() => {
|
||||||
if relay_reservation_active {
|
if relay_reservation_active {
|
||||||
if let Some(rp) = relay_peer {
|
if let Some(rp) = relay_peer {
|
||||||
for ns in registered_namespaces.clone() {
|
for ns in active_namespaces.clone() {
|
||||||
|
match libp2p::rendezvous::Namespace::new(ns.clone()) {
|
||||||
|
Ok(namespace) => {
|
||||||
|
log::info!(
|
||||||
|
"rendezvous register refresh start for namespace '{}'",
|
||||||
|
ns
|
||||||
|
);
|
||||||
if let Err(e) = swarm_instance.behaviour_mut().rendezvous.register(
|
if let Err(e) = swarm_instance.behaviour_mut().rendezvous.register(
|
||||||
libp2p::rendezvous::Namespace::new(ns.clone()).unwrap(),
|
namespace,
|
||||||
rp,
|
rp,
|
||||||
None,
|
None,
|
||||||
) {
|
) {
|
||||||
log::warn!("failed to refresh rendezvous registration for {}: {:?}", ns, e);
|
log::warn!(
|
||||||
|
"failed to refresh rendezvous registration for '{}': {:?}",
|
||||||
|
ns,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"invalid active rendezvous namespace '{}' during refresh: {:?}",
|
||||||
|
ns,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match libp2p::rendezvous::Namespace::new(ns.clone()) {
|
||||||
|
Ok(namespace) => {
|
||||||
|
log::info!(
|
||||||
|
"rendezvous rediscovery start for namespace '{}'",
|
||||||
|
ns
|
||||||
|
);
|
||||||
|
swarm_instance.behaviour_mut().rendezvous.discover(
|
||||||
|
Some(namespace),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
rp,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"invalid active rendezvous namespace '{}' during rediscovery: {:?}",
|
||||||
|
ns,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1118,6 +1425,42 @@ pub async fn start(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// periodic kademlia bootstrap/query as WAN fallback when relay+rendezvous are degraded
|
||||||
|
_ = kad_bootstrap_tick.tick() => {
|
||||||
|
for (addr, peer) in &bootstrap_nodes {
|
||||||
|
swarm_instance
|
||||||
|
.behaviour_mut()
|
||||||
|
.kademlia
|
||||||
|
.add_address(peer, addr.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
match swarm_instance.behaviour_mut().kademlia.bootstrap() {
|
||||||
|
Ok(query_id) => {
|
||||||
|
log::info!(
|
||||||
|
"kademlia bootstrap started (query {:?}, peers: {})",
|
||||||
|
query_id,
|
||||||
|
bootstrap_nodes.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"kademlia bootstrap start failed: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_peer_id = *swarm_instance.local_peer_id();
|
||||||
|
let query_id = swarm_instance
|
||||||
|
.behaviour_mut()
|
||||||
|
.kademlia
|
||||||
|
.get_closest_peers(local_peer_id);
|
||||||
|
log::debug!(
|
||||||
|
"kademlia get_closest_peers started (query {:?})",
|
||||||
|
query_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// relay reconnection with exponential backoff
|
// relay reconnection with exponential backoff
|
||||||
_ = tokio::time::sleep_until(
|
_ = tokio::time::sleep_until(
|
||||||
relay_retry_at.unwrap_or_else(|| tokio::time::Instant::now() + std::time::Duration::from_secs(86400))
|
relay_retry_at.unwrap_or_else(|| tokio::time::Instant::now() + std::time::Duration::from_secs(86400))
|
||||||
|
|
@ -1125,15 +1468,17 @@ pub async fn start(
|
||||||
relay_retry_at = None;
|
relay_retry_at = None;
|
||||||
if !relay_reservation_active {
|
if !relay_reservation_active {
|
||||||
if let Some(ref addr) = relay_multiaddr {
|
if let Some(ref addr) = relay_multiaddr {
|
||||||
log::info!("attempting relay reconnect to {}", addr);
|
log::info!("relay dial start (reconnect): {}", addr);
|
||||||
if let Err(e) = swarm_instance.dial(addr.clone()) {
|
if let Err(e) = swarm_instance.dial(addr.clone()) {
|
||||||
log::warn!("failed to dial relay: {}", e);
|
log::warn!("relay dial failed (reconnect): {}", e);
|
||||||
// schedule another retry
|
// schedule another retry
|
||||||
relay_retry_at = Some(
|
relay_retry_at = Some(
|
||||||
tokio::time::Instant::now() + std::time::Duration::from_secs(relay_backoff_secs),
|
tokio::time::Instant::now() + std::time::Duration::from_secs(relay_backoff_secs),
|
||||||
);
|
);
|
||||||
relay_backoff_secs = (relay_backoff_secs * RELAY_BACKOFF_MULTIPLIER)
|
relay_backoff_secs = (relay_backoff_secs * RELAY_BACKOFF_MULTIPLIER)
|
||||||
.min(RELAY_MAX_BACKOFF_SECS);
|
.min(RELAY_MAX_BACKOFF_SECS);
|
||||||
|
} else {
|
||||||
|
log::info!("relay dial initiated (reconnect)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1174,8 +1519,11 @@ pub async fn start(
|
||||||
let _ = reply.send(addrs);
|
let _ = reply.send(addrs);
|
||||||
}
|
}
|
||||||
Some(NodeCommand::Dial { addr }) => {
|
Some(NodeCommand::Dial { addr }) => {
|
||||||
|
log::info!("manual dial start: {}", addr);
|
||||||
if let Err(e) = swarm_instance.dial(addr.clone()) {
|
if let Err(e) = swarm_instance.dial(addr.clone()) {
|
||||||
log::warn!("failed to dial {}: {}", addr, e);
|
log::warn!("failed to dial {}: {}", addr, e);
|
||||||
|
} else {
|
||||||
|
log::info!("manual dial initiated: {}", addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(NodeCommand::BroadcastPresence { status }) => {
|
Some(NodeCommand::BroadcastPresence { status }) => {
|
||||||
|
|
@ -1209,14 +1557,17 @@ pub async fn start(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(NodeCommand::RegisterRendezvous { namespace }) => {
|
Some(NodeCommand::RegisterRendezvous { namespace }) => {
|
||||||
|
active_namespaces.insert(namespace.clone());
|
||||||
if relay_reservation_active {
|
if relay_reservation_active {
|
||||||
if let Some(rp) = relay_peer {
|
if let Some(rp) = relay_peer {
|
||||||
match libp2p::rendezvous::Namespace::new(namespace.clone()) {
|
match libp2p::rendezvous::Namespace::new(namespace.clone()) {
|
||||||
Ok(ns) => {
|
Ok(ns) => {
|
||||||
|
log::info!(
|
||||||
|
"rendezvous register start for namespace '{}'",
|
||||||
|
namespace
|
||||||
|
);
|
||||||
if let Err(e) = swarm_instance.behaviour_mut().rendezvous.register(ns, rp, None) {
|
if let Err(e) = swarm_instance.behaviour_mut().rendezvous.register(ns, rp, None) {
|
||||||
log::warn!("failed to register on rendezvous: {:?}", e);
|
log::warn!("failed to register on rendezvous: {:?}", e);
|
||||||
} else {
|
|
||||||
registered_namespaces.insert(namespace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => log::warn!("invalid rendezvous namespace '{}': {:?}", namespace, e),
|
Err(e) => log::warn!("invalid rendezvous namespace '{}': {:?}", namespace, e),
|
||||||
|
|
@ -1227,14 +1578,19 @@ pub async fn start(
|
||||||
if pending_queued_at.is_none() {
|
if pending_queued_at.is_none() {
|
||||||
pending_queued_at = Some(std::time::Instant::now());
|
pending_queued_at = Some(std::time::Instant::now());
|
||||||
}
|
}
|
||||||
pending_registrations.push(namespace);
|
queue_namespace_unique(&mut pending_registrations, namespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(NodeCommand::DiscoverRendezvous { namespace }) => {
|
Some(NodeCommand::DiscoverRendezvous { namespace }) => {
|
||||||
|
active_namespaces.insert(namespace.clone());
|
||||||
if relay_reservation_active {
|
if relay_reservation_active {
|
||||||
if let Some(rp) = relay_peer {
|
if let Some(rp) = relay_peer {
|
||||||
match libp2p::rendezvous::Namespace::new(namespace.clone()) {
|
match libp2p::rendezvous::Namespace::new(namespace.clone()) {
|
||||||
Ok(ns) => {
|
Ok(ns) => {
|
||||||
|
log::info!(
|
||||||
|
"rendezvous discover start for namespace '{}'",
|
||||||
|
namespace
|
||||||
|
);
|
||||||
swarm_instance.behaviour_mut().rendezvous.discover(
|
swarm_instance.behaviour_mut().rendezvous.discover(
|
||||||
Some(ns),
|
Some(ns),
|
||||||
None,
|
None,
|
||||||
|
|
@ -1250,7 +1606,7 @@ pub async fn start(
|
||||||
if pending_queued_at.is_none() {
|
if pending_queued_at.is_none() {
|
||||||
pending_queued_at = Some(std::time::Instant::now());
|
pending_queued_at = Some(std::time::Instant::now());
|
||||||
}
|
}
|
||||||
pending_discoveries.push(namespace);
|
queue_namespace_unique(&mut pending_discoveries, namespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(NodeCommand::UnregisterRendezvous { namespace }) => {
|
Some(NodeCommand::UnregisterRendezvous { namespace }) => {
|
||||||
|
|
@ -1259,7 +1615,7 @@ pub async fn start(
|
||||||
if pending_registrations.is_empty() && pending_discoveries.is_empty() {
|
if pending_registrations.is_empty() && pending_discoveries.is_empty() {
|
||||||
pending_queued_at = None;
|
pending_queued_at = None;
|
||||||
}
|
}
|
||||||
registered_namespaces.remove(&namespace);
|
active_namespaces.remove(&namespace);
|
||||||
|
|
||||||
if relay_reservation_active {
|
if relay_reservation_active {
|
||||||
if let Some(rp) = relay_peer {
|
if let Some(rp) = relay_peer {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue