Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- ASB: Honest peers (those that committed real funds to a recent swap) are now exempt from the incoming connection limit, so an attacker flooding connections from throwaway peer IDs can no longer lock honest peers out and block quotes/swaps. A new `[network]` config option `connection_limit_exemption_freshness_days` controls how recently a peer must have transacted to stay exempt (default: `30`).

## [4.11.3] - 2026-06-24

## [4.11.2] - 2026-06-23
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions swap-asb/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ pub async fn main() -> Result<()> {
config.tor.wormhole_num_intro_points,
config.tor.wormhole_swap_freshness_hours,
db.clone(),
config.network.connection_limit_exemption_freshness_days,
config.network.connection_limit_exemption_max_peers,
metrics_registry.as_mut(),
)?;

Expand Down
15 changes: 15 additions & 0 deletions swap-env/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ pub struct Network {
/// `/metrics`. When unset, the metrics endpoint is disabled.
#[serde(default)]
pub prometheus_port: Option<u16>,
#[serde(default = "default_connection_limit_exemption_freshness_days")]
pub connection_limit_exemption_freshness_days: u64,
#[serde(default = "default_connection_limit_exemption_max_peers")]
pub connection_limit_exemption_max_peers: u64,
}

pub fn default_connection_limit_exemption_freshness_days() -> u64 {
30
}

pub fn default_connection_limit_exemption_max_peers() -> u64 {
500
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
Expand Down Expand Up @@ -439,6 +451,9 @@ pub fn query_user_for_initial_config_with_network(
rendezvous_point: rendezvous_points,
external_addresses: vec![],
prometheus_port: None,
connection_limit_exemption_freshness_days:
default_connection_limit_exemption_freshness_days(),
connection_limit_exemption_max_peers: default_connection_limit_exemption_max_peers(),
},
bitcoin: Bitcoin {
electrum_rpc_urls,
Expand Down
5 changes: 5 additions & 0 deletions swap-orchestrator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::path::PathBuf;
use std::str::FromStr;
use swap_env::config::{
Bitcoin, Config, ConfigNotInitialized, Data, Maker, Monero, Network, TorConf,
default_connection_limit_exemption_freshness_days, default_connection_limit_exemption_max_peers,
default_price_ticker_rest_poll_interval_exolix_secs, default_price_ticker_source_enabled,
default_price_ticker_validity_duration_secs,
};
Expand Down Expand Up @@ -347,6 +348,10 @@ fn main() {
rendezvous_point: rendezvous_points,
external_addresses: vec![],
prometheus_port: None,
connection_limit_exemption_freshness_days:
default_connection_limit_exemption_freshness_days(),
connection_limit_exemption_max_peers:
default_connection_limit_exemption_max_peers(),
},
bitcoin: Bitcoin {
electrum_rpc_urls: match electrum_server_type {
Expand Down
5 changes: 4 additions & 1 deletion swap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ tor-rtcompat = { workspace = true, features = ["tokio"] }

# LibP2P
libp2p = { workspace = true, features = ["tcp", "yamux", "dns", "noise", "request-response", "ping", "rendezvous", "identify", "macros", "cbor", "json", "tokio", "serde", "rsa", "websocket", "metrics"] }
libp2p-core = "0.41.3"
libp2p-identity = "0.2.13"
libp2p-swarm = "0.44.2"
libp2p-tor = { path = "../libp2p-tor", features = ["listen-onion-service"] }

# Error handling
Expand Down Expand Up @@ -81,7 +84,7 @@ swap-p2p = { path = "../swap-p2p" }
swap-proptest = { path = "../swap-proptest" }
swap-serde = { path = "../swap-serde" }
tauri = { version = "2.0", features = ["config-json5"], optional = true, default-features = false }
time = "0.3"
time = { version = "0.3", features = ["formatting", "macros", "parsing"] }
toml = { workspace = true }
toml_edit = { workspace = true }
url = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion swap/src/asb/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ where
}
SwarmEvent::IncomingConnectionError { send_back_addr: address, error, .. } => {
if let libp2p::swarm::ListenError::Denied { cause } = &error {
if let Some(exceeded) = cause.downcast_ref::<libp2p::connection_limits::Exceeded>() {
if let Some(exceeded) = cause.downcast_ref::<crate::network::connection_limits::Exceeded>() {
tracing::warn!(%address, error = %exceeded, "Rejected inbound connection to prevent against denial-of-service");
} else {
tracing::trace!(%address, "Failed to set up connection with peer: {:?}", error);
Expand Down
17 changes: 14 additions & 3 deletions swap/src/asb/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,19 @@ pub mod transport {
pub mod behaviour {
use std::sync::Arc;

use libp2p::{connection_limits, identify, identity, ping, swarm::behaviour::toggle::Toggle};
use libp2p::{identify, identity, ping, swarm::behaviour::toggle::Toggle};
use swap_p2p::protocols::metered::RequestResponseMetrics;
use swap_p2p::{out_event::alice::OutEvent, patches};

use crate::network::connection_limits;
use crate::network::wormhole;
use crate::network::wormhole::PeerTrust;
use crate::network::wormhole::alice::transport::WormholeChannels;

use super::*;

const HONEST_PEERS_REFRESH_INTERVAL: Duration = Duration::from_secs(60);

/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
#[derive(NetworkBehaviour)]
#[behaviour(out_event = "OutEvent", event_process = false)]
Expand Down Expand Up @@ -193,6 +196,8 @@ pub mod behaviour {
rendezvous_nodes: Vec<PeerId>,
connection_limits: connection_limits::ConnectionLimits,
trust_provider: Arc<dyn PeerTrust + Send + Sync>,
connection_limit_exemption_freshness_days: u64,
connection_limit_exemption_max_peers: u64,
wormhole_channels: Option<WormholeChannels>,
wormhole_swap_freshness_hours: u64,
request_response_metrics: Option<RequestResponseMetrics>,
Expand All @@ -209,7 +214,7 @@ pub mod behaviour {
let wormhole = wormhole_channels.map(|channels| {
wormhole::alice::Behaviour::new(
&identity,
trust_provider,
Arc::clone(&trust_provider),
channels.service_tx,
channels.handle_rx,
wormhole::alice::Config {
Expand All @@ -230,7 +235,13 @@ pub mod behaviour {
};

Self {
connection_limits: connection_limits::Behaviour::new(connection_limits),
connection_limits: connection_limits::Behaviour::new(
connection_limits,
trust_provider,
connection_limit_exemption_freshness_days,
connection_limit_exemption_max_peers as usize,
HONEST_PEERS_REFRESH_INTERVAL,
),
rendezvous: Toggle::from(behaviour),
quote: quote::alice(request_response_metrics.clone()),
swap_setup: alice::Behaviour::new(
Expand Down
2 changes: 2 additions & 0 deletions swap/src/database.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use alice::Alice;
pub use bob::Bob;
pub use entered_at::{format_entered_at, parse_entered_at};
pub use sqlite::SqliteDatabase;

use crate::cli::api::tauri_bindings::TauriHandle;
Expand All @@ -14,6 +15,7 @@ use swap_fs::ensure_directory_exists;
pub use swap_db::alice;
pub use swap_db::bob;

mod entered_at;
mod sqlite;

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
Expand Down
Loading
Loading