Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ base64 = { version = "0.22.1", default-features = false, features = ["std"] }
getrandom = { version = "0.3", default-features = false }
chrono = { version = "0.4", default-features = false, features = ["clock"] }
tokio = { version = "1.39", default-features = false, features = [ "rt-multi-thread", "time", "sync", "macros", "net" ] }
tokio-util = { version = "0.7", default-features = false, features = ["rt"] }
esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"] }
electrum-client = { version = "0.25", default-features = false, features = ["proxy", "use-rustls-ring"] }
libc = "0.2"
Expand Down
87 changes: 73 additions & 14 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,44 @@ use crate::logger::{log_debug, log_error, log_info, LdkLogger};
use crate::types::{KeysManager, PeerManager};
use crate::Error;

type PendingConnections =
Mutex<HashMap<PublicKey, Vec<tokio::sync::oneshot::Sender<Result<(), Error>>>>>;

struct PendingConnectionGuard<'a> {
pending_connections: &'a PendingConnections,
node_id: PublicKey,
active: bool,
}

impl<'a> PendingConnectionGuard<'a> {
fn new(pending_connections: &'a PendingConnections, node_id: PublicKey) -> Self {
Self { pending_connections, node_id, active: true }
}

fn disarm(&mut self) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is this really needed ? On drop, we'd briefly lock the pending connections, see that we have no subscribers for the node_id, and do nothing.

self.active = false;
}
}

impl Drop for PendingConnectionGuard<'_> {
fn drop(&mut self) {
if !self.active {
return;
}
let mut pending_connections = self.pending_connections.lock().expect("lock");
if let Some(subscribers) = pending_connections.remove(&self.node_id) {
for subscriber in subscribers {
let _ = subscriber.send(Err(Error::ConnectionFailed));
}
}
}
}

pub(crate) struct ConnectionManager<L: Deref + Clone + Sync + Send>
where
L::Target: LdkLogger,
{
pending_connections:
Mutex<HashMap<PublicKey, Vec<tokio::sync::oneshot::Sender<Result<(), Error>>>>>,
pending_connections: PendingConnections,
peer_manager: Arc<PeerManager>,
tor_proxy_config: Option<TorConfig>,
keys_manager: Arc<KeysManager>,
Expand Down Expand Up @@ -60,26 +92,29 @@ where
pub(crate) async fn do_connect_peer(
&self, node_id: PublicKey, addr: SocketAddress,
) -> Result<(), Error> {
// If another task is already connecting, subscribe to its result instead of starting a
// duplicate attempt.
if let Some(pending_connection_ready_receiver) =
self.register_or_subscribe_pending_connection(&node_id)
{
return pending_connection_ready_receiver.await.map_err(|e| {
debug_assert!(false, "Failed to receive connection result: {:?}", e);
log_error!(self.logger, "Failed to receive connection result: {:?}", e);
Error::ConnectionFailed
})?;
}

let mut pending_connection =
PendingConnectionGuard::new(&self.pending_connections, node_id);
let res = self.do_connect_peer_internal(node_id, addr).await;
self.propagate_result_to_subscribers(&node_id, res);
pending_connection.disarm();
res
}

async fn do_connect_peer_internal(
&self, node_id: PublicKey, addr: SocketAddress,
) -> Result<(), Error> {
// First, we check if there is already an outbound connection in flight, if so, we just
// await on the corresponding watch channel. The task driving the connection future will
// send us the result..
let pending_ready_receiver_opt = self.register_or_subscribe_pending_connection(&node_id);
if let Some(pending_connection_ready_receiver) = pending_ready_receiver_opt {
return pending_connection_ready_receiver.await.map_err(|e| {
debug_assert!(false, "Failed to receive connection result: {:?}", e);
log_error!(self.logger, "Failed to receive connection result: {:?}", e);
Error::ConnectionFailed
})?;
}

log_info!(self.logger, "Connecting to peer: {}@{}", node_id, addr);

match addr {
Expand Down Expand Up @@ -246,6 +281,7 @@ where
match pending_connections_lock.entry(*node_id) {
hash_map::Entry::Occupied(mut entry) => {
let (tx, rx) = tokio::sync::oneshot::channel();
entry.get_mut().retain(|subscriber| !subscriber.is_closed());
entry.get_mut().push(tx);
Some(rx)
},
Expand Down Expand Up @@ -277,3 +313,26 @@ where
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn pending_connection_guard_notifies_subscribers_when_abandoned() {
let node_id: PublicKey =
"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".parse().unwrap();
let pending_connections = Mutex::new(HashMap::new());
let (sender, mut receiver) = tokio::sync::oneshot::channel();
pending_connections.lock().expect("lock").insert(node_id, vec![sender]);

let connection_guard = PendingConnectionGuard::new(&pending_connections, node_id);
drop(connection_guard);

assert!(
pending_connections.lock().expect("lock").is_empty(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should we also make sure that key-values for a different node_id are not dropped ?

"abandoned connection attempt should remove pending state"
);
assert_eq!(receiver.try_recv(), Ok(Err(Error::ConnectionFailed)));
}
}
55 changes: 35 additions & 20 deletions src/liquidity/client/lsps1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use tokio::sync::oneshot;

use crate::connection::ConnectionManager;
use crate::liquidity::{
select_lsps_for_protocol, LspConfig, LspNode, LIQUIDITY_REQUEST_TIMEOUT_SECS,
LSPS_DISCOVERY_WAIT_TIMEOUT_SECS,
select_lsps_for_protocol, LspConfig, LspNode, PendingRequest, PendingRequestGuard,
LIQUIDITY_REQUEST_TIMEOUT_SECS, LSPS_DISCOVERY_WAIT_TIMEOUT_SECS,
};
use crate::logger::{log_error, log_info, LdkLogger, Logger};
use crate::runtime::Runtime;
Expand All @@ -35,11 +35,11 @@ where
{
pub(crate) lsp_nodes: Arc<RwLock<Vec<LspNode>>>,
pub(crate) pending_opening_params_requests:
Mutex<HashMap<LSPSRequestId, oneshot::Sender<LSPS1OpeningParamsResponse>>>,
Mutex<HashMap<LSPSRequestId, PendingRequest<LSPS1OpeningParamsResponse>>>,
pub(crate) pending_create_order_requests:
Mutex<HashMap<LSPSRequestId, oneshot::Sender<LSPS1OrderStatus>>>,
Mutex<HashMap<LSPSRequestId, PendingRequest<LSPS1OrderStatus>>>,
pub(crate) pending_check_order_status_requests:
Mutex<HashMap<LSPSRequestId, oneshot::Sender<LSPS1OrderStatus>>>,
Mutex<HashMap<LSPSRequestId, PendingRequest<LSPS1OrderStatus>>>,
pub(crate) discovery_done_rx: tokio::sync::watch::Receiver<bool>,
pub(crate) liquidity_manager: Arc<LiquidityManager>,
pub(crate) logger: L,
Expand All @@ -61,12 +61,17 @@ where
})?;

let (request_sender, request_receiver) = oneshot::channel();
{
let _pending_request = {
let mut pending_opening_params_requests_lock =
self.pending_opening_params_requests.lock().expect("lock");
let request_id = client_handler.request_supported_options(lsps1_node.node_id);
pending_opening_params_requests_lock.insert(request_id, request_sender);
}
PendingRequestGuard::insert(
&self.pending_opening_params_requests,
&mut pending_opening_params_requests_lock,
request_id,
request_sender,
)
};

tokio::time::timeout(Duration::from_secs(LIQUIDITY_REQUEST_TIMEOUT_SECS), request_receiver)
.await
Expand Down Expand Up @@ -146,16 +151,21 @@ where

let (request_sender, request_receiver) = oneshot::channel();
let request_id;
{
let _pending_request = {
let mut pending_create_order_requests_lock =
self.pending_create_order_requests.lock().expect("lock");
request_id = client_handler.create_order(
&lsps1_node.node_id,
order_params.clone(),
Some(refund_address),
);
pending_create_order_requests_lock.insert(request_id.clone(), request_sender);
}
PendingRequestGuard::insert(
&self.pending_create_order_requests,
&mut pending_create_order_requests_lock,
request_id.clone(),
request_sender,
)
};

let response = tokio::time::timeout(
Duration::from_secs(LIQUIDITY_REQUEST_TIMEOUT_SECS),
Expand Down Expand Up @@ -191,12 +201,17 @@ where
})?;

let (request_sender, request_receiver) = oneshot::channel();
{
let _pending_request = {
let mut pending_check_order_status_requests_lock =
self.pending_check_order_status_requests.lock().expect("lock");
let request_id = client_handler.check_order_status(&lsp_node_id, order_id);
pending_check_order_status_requests_lock.insert(request_id, request_sender);
}
PendingRequestGuard::insert(
&self.pending_check_order_status_requests,
&mut pending_check_order_status_requests_lock,
request_id,
request_sender,
)
};

let response = tokio::time::timeout(
Duration::from_secs(LIQUIDITY_REQUEST_TIMEOUT_SECS),
Expand Down Expand Up @@ -229,15 +244,15 @@ where
.iter()
.any(|n| n.node_id == counterparty_node_id)
{
if let Some(sender) = self
if let Some(request) = self
.pending_opening_params_requests
.lock()
.expect("lock")
.remove(&request_id)
{
let response = LSPS1OpeningParamsResponse { supported_options };

match sender.send(response) {
match request.sender.send(response) {
Ok(()) => (),
Err(_) => {
log_error!(
Expand Down Expand Up @@ -279,7 +294,7 @@ where
.iter()
.any(|n| n.node_id == counterparty_node_id)
{
if let Some(sender) =
if let Some(request) =
self.pending_create_order_requests.lock().expect("lock").remove(&request_id)
{
let response = LSPS1OrderStatus {
Expand All @@ -290,7 +305,7 @@ where
counterparty_node_id,
};

match sender.send(response) {
match request.sender.send(response) {
Ok(()) => (),
Err(_) => {
log_error!(
Expand Down Expand Up @@ -329,7 +344,7 @@ where
.iter()
.any(|n| n.node_id == counterparty_node_id)
{
if let Some(sender) = self
if let Some(request) = self
.pending_check_order_status_requests
.lock()
.expect("lock")
Expand All @@ -343,7 +358,7 @@ where
counterparty_node_id,
};

match sender.send(response) {
match request.sender.send(response) {
Ok(()) => (),
Err(_) => {
log_error!(
Expand Down
38 changes: 24 additions & 14 deletions src/liquidity/client/lsps2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use tokio::task::JoinSet;

use crate::connection::ConnectionManager;
use crate::liquidity::{
select_all_lsps_for_protocol, select_lsps_for_protocol, LspConfig, LspNode,
LIQUIDITY_REQUEST_TIMEOUT_SECS, LSPS_DISCOVERY_WAIT_TIMEOUT_SECS,
select_all_lsps_for_protocol, select_lsps_for_protocol, LspConfig, LspNode, PendingRequest,
PendingRequestGuard, LIQUIDITY_REQUEST_TIMEOUT_SECS, LSPS_DISCOVERY_WAIT_TIMEOUT_SECS,
};
use crate::logger::{log_debug, log_error, log_info, LdkLogger};
use crate::payment::store::LSPS2Parameters;
Expand All @@ -41,9 +41,9 @@ where
{
pub(crate) lsp_nodes: Arc<RwLock<Vec<LspNode>>>,
pub(crate) pending_lsps2_fee_requests:
Mutex<HashMap<LSPSRequestId, oneshot::Sender<LSPS2FeeResponse>>>,
Mutex<HashMap<LSPSRequestId, PendingRequest<LSPS2FeeResponse>>>,
pub(crate) pending_buy_requests:
Mutex<HashMap<LSPSRequestId, oneshot::Sender<LSPS2BuyResponse>>>,
Mutex<HashMap<LSPSRequestId, PendingRequest<LSPS2BuyResponse>>>,
pub(crate) channel_manager: Arc<ChannelManager>,
pub(crate) keys_manager: Arc<KeysManager>,
pub(crate) discovery_done_rx: tokio::sync::watch::Receiver<bool>,
Expand Down Expand Up @@ -262,13 +262,18 @@ where
})?;

let (fee_request_sender, fee_request_receiver) = oneshot::channel();
{
let _pending_request = {
let mut pending_fee_requests_lock =
self.pending_lsps2_fee_requests.lock().expect("lock");
let request_id =
client_handler.request_opening_params(lsps2_node.node_id, lsps2_node.token.clone());
pending_fee_requests_lock.insert(request_id, fee_request_sender);
}
PendingRequestGuard::insert(
&self.pending_lsps2_fee_requests,
&mut pending_fee_requests_lock,
request_id,
fee_request_sender,
)
};

tokio::time::timeout(
Duration::from_secs(LIQUIDITY_REQUEST_TIMEOUT_SECS),
Expand Down Expand Up @@ -298,7 +303,7 @@ where
})?;

let (buy_request_sender, buy_request_receiver) = oneshot::channel();
{
let _pending_request = {
let mut pending_buy_requests_lock = self.pending_buy_requests.lock().expect("lock");
let request_id = client_handler
.select_opening_params(lsps2_node.node_id, amount_msat, opening_fee_params)
Expand All @@ -310,8 +315,13 @@ where
);
Error::LiquidityRequestFailed
})?;
pending_buy_requests_lock.insert(request_id, buy_request_sender);
}
PendingRequestGuard::insert(
&self.pending_buy_requests,
&mut pending_buy_requests_lock,
request_id,
buy_request_sender,
)
};

let buy_response = tokio::time::timeout(
Duration::from_secs(LIQUIDITY_REQUEST_TIMEOUT_SECS),
Expand Down Expand Up @@ -428,12 +438,12 @@ where
.iter()
.any(|n| n.node_id == counterparty_node_id)
{
if let Some(sender) =
if let Some(request) =
self.pending_lsps2_fee_requests.lock().expect("lock").remove(&request_id)
{
let response = LSPS2FeeResponse { opening_fee_params_menu };

match sender.send(response) {
match request.sender.send(response) {
Ok(()) => (),
Err(_) => {
log_error!(
Expand Down Expand Up @@ -474,12 +484,12 @@ where
.iter()
.any(|n| n.node_id == counterparty_node_id)
{
if let Some(sender) =
if let Some(request) =
self.pending_buy_requests.lock().expect("lock").remove(&request_id)
{
let response = LSPS2BuyResponse { intercept_scid, cltv_expiry_delta };

match sender.send(response) {
match request.sender.send(response) {
Ok(()) => (),
Err(_) => {
log_error!(
Expand Down
Loading