Skip to content

Commit

Permalink
Merge #979
Browse files Browse the repository at this point in the history
979: Fail intercepted HTLC backwards when we should r=luckysori a=luckysori

Fixes #943.
Fixes #944.

---

This is yet another instance where we have to consider the fact that the same event handler code runs for both app and coordinator even though certain parts need to be specific to each role. I've increased the implicit annoyance counter of #885.

Co-authored-by: Lucas Soriano del Pino <[email protected]>
  • Loading branch information
bors[bot] and luckysori authored Jul 25, 2023
2 parents 2beb19d + 5da5e3f commit 0a9c34d
Show file tree
Hide file tree
Showing 13 changed files with 497 additions and 210 deletions.
457 changes: 265 additions & 192 deletions crates/ln-dlc-node/src/ln/event_handler.rs

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions crates/ln-dlc-node/src/node/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,16 @@ where
&self,
hash: &sha256::Hash,
) -> Result<(), tokio::time::error::Elapsed> {
self.wait_for_payment(HTLCStatus::Succeeded, hash).await
self.wait_for_payment(HTLCStatus::Succeeded, hash, None)
.await
}

#[autometrics]
pub async fn wait_for_payment(
&self,
expected_status: HTLCStatus,
hash: &sha256::Hash,
timeout: Option<Duration>,
) -> Result<(), tokio::time::error::Elapsed> {
assert_ne!(
expected_status,
Expand All @@ -237,7 +239,7 @@ where
);
let payment_hash = PaymentHash(hash.into_inner());

tokio::time::timeout(Duration::from_secs(10), async {
tokio::time::timeout(timeout.unwrap_or(Duration::from_secs(10)), async {
loop {
tokio::time::sleep(Duration::from_secs(1)).await;

Expand Down
4 changes: 2 additions & 2 deletions crates/ln-dlc-node/src/tests/dlc/collaborative_settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async fn dlc_collaborative_settlement_test() {
.unwrap();

coordinator
.open_channel(&app, coordinator_ln_balance, app_ln_balance)
.open_private_channel(&app, coordinator_ln_balance, app_ln_balance)
.await
.unwrap();

Expand Down Expand Up @@ -106,7 +106,7 @@ async fn open_dlc_channel_after_closing_dlc_channel() {
.unwrap();

coordinator
.open_channel(&app, coordinator_ln_balance, app_ln_balance)
.open_private_channel(&app, coordinator_ln_balance, app_ln_balance)
.await
.unwrap();

Expand Down
2 changes: 1 addition & 1 deletion crates/ln-dlc-node/src/tests/dlc/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async fn given_lightning_channel_then_can_add_dlc_channel() {
.unwrap();

coordinator
.open_channel(&app, coordinator_ln_balance, app_ln_balance)
.open_private_channel(&app, coordinator_ln_balance, app_ln_balance)
.await
.unwrap();

Expand Down
4 changes: 2 additions & 2 deletions crates/ln-dlc-node/src/tests/dlc/dlc_setup_with_reconnects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async fn reconnecting_during_dlc_channel_setup() {
.unwrap();

coordinator
.open_channel(&app, 50_000, 50_000)
.open_private_channel(&app, 50_000, 50_000)
.await
.unwrap();
let channel_details = app.channel_manager.list_usable_channels();
Expand Down Expand Up @@ -253,7 +253,7 @@ async fn can_lose_connection_before_processing_subchannel_close_finalize() {
.unwrap();

coordinator
.open_channel(&app, coordinator_ln_balance, app_ln_balance)
.open_private_channel(&app, coordinator_ln_balance, app_ln_balance)
.await
.unwrap();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async fn force_close_ln_dlc_channel() {
.unwrap();

let channel_details = coordinator
.open_channel(&app, coordinator_ln_balance, app_ln_balance)
.open_private_channel(&app, coordinator_ln_balance, app_ln_balance)
.await
.unwrap();

Expand Down
5 changes: 3 additions & 2 deletions crates/ln-dlc-node/src/tests/just_in_time_channel/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ async fn open_jit_channel() {
..LnDlcNodeSettings::default()
};
let coordinator =
Node::start_test_coordinator_internal("coordinator", storage.clone(), settings).unwrap();
Node::start_test_coordinator_internal("coordinator", storage.clone(), settings, None)
.unwrap();
let payee = Node::start_test_app("payee").unwrap();

payer.connect(coordinator.info).await.unwrap();
Expand Down Expand Up @@ -137,7 +138,7 @@ async fn fail_to_open_jit_channel_with_fee_rate_over_max() {
// We would like to assert on the payment failing, but this is not guaranteed as the payment can
// still be retried after the first payment path failure. Thus, we check that it doesn't succeed
payee
.wait_for_payment(HTLCStatus::Succeeded, invoice.payment_hash())
.wait_for_payment(HTLCStatus::Succeeded, invoice.payment_hash(), None)
.await
.expect_err("payment should not succeed");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use crate::ln::HTLC_INTERCEPTED_CONNECTION_TIMEOUT;
use crate::node::InMemoryStore;
use crate::node::LnDlcNodeSettings;
use crate::node::Node;
use crate::tests::init_tracing;
use crate::tests::setup_coordinator_payer_channel;
use crate::HTLCStatus;
use bitcoin::Amount;
use lightning::util::events::Event;
use std::sync::Arc;
use std::time::Duration;

#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn fail_intercepted_htlc_if_coordinator_cannot_reconnect_to_payee() {
init_tracing();

// Arrange

let payer = Node::start_test_app("payer").unwrap();
let coordinator = Node::start_test_coordinator("coordinator").unwrap();
let payee = Node::start_test_app("payee").unwrap();

payer.connect(coordinator.info).await.unwrap();
payee.connect(coordinator.info).await.unwrap();

let invoice_amount = 10_000;
setup_coordinator_payer_channel(invoice_amount, &coordinator, &payer).await;

let interceptable_route_hint_hop = coordinator.prepare_jit_channel(payee.info.pubkey);

let invoice = payee
.create_interceptable_invoice(
Some(invoice_amount),
0,
"interceptable-invoice".to_string(),
interceptable_route_hint_hop,
)
.unwrap();

// Act

// We wait a second for payee and coordinator to be disconnected
payee.disconnect(coordinator.info);
tokio::time::sleep(Duration::from_secs(1)).await;

payer.send_payment(&invoice).unwrap();

// Assert

payer
.wait_for_payment(
HTLCStatus::Failed,
invoice.payment_hash(),
// We wait a bit longer than what the coordinator should wait for the payee to
// reconnect
Some(Duration::from_secs(HTLC_INTERCEPTED_CONNECTION_TIMEOUT + 5)),
)
.await
.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn fail_intercepted_htlc_if_connection_lost_after_funding_tx_generated() {
init_tracing();

// Arrange

let payer = Node::start_test_app("payer").unwrap();

let (coordinator, mut ldk_node_event_receiver_coordinator) = {
let (sender, receiver) = tokio::sync::watch::channel(None);
let node = Node::start_test_coordinator_internal(
"coordinator",
Arc::new(InMemoryStore::default()),
LnDlcNodeSettings::default(),
Some(sender),
)
.unwrap();

(node, receiver)
};

let payee = Node::start_test_app("payee").unwrap();

payer.connect(coordinator.info).await.unwrap();
payee.connect(coordinator.info).await.unwrap();

let invoice_amount = 10_000;
setup_coordinator_payer_channel(invoice_amount, &coordinator, &payer).await;

let interceptable_route_hint_hop = coordinator.prepare_jit_channel(payee.info.pubkey);

let invoice = payee
.create_interceptable_invoice(
Some(invoice_amount),
0,
"interceptable-invoice".to_string(),
interceptable_route_hint_hop,
)
.unwrap();

// Act

payer.send_payment(&invoice).unwrap();

tokio::time::timeout(Duration::from_secs(30), async {
loop {
ldk_node_event_receiver_coordinator.changed().await.unwrap();
let event = ldk_node_event_receiver_coordinator.borrow().clone();

if let Some(Event::FundingGenerationReady { .. }) = event {
// We wait a second for payee and coordinator to be disconnected
payee.disconnect(coordinator.info);
tokio::time::sleep(Duration::from_secs(1)).await;

break;
}
}
})
.await
.unwrap();

// Assert

payer
.wait_for_payment(HTLCStatus::Failed, invoice.payment_hash(), None)
.await
.unwrap();
}

#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn fail_intercepted_htlc_if_coordinator_cannot_pay_to_open_jit_channel() {
init_tracing();

// Arrange

let payer = Node::start_test_app("payer").unwrap();
let coordinator = Node::start_test_coordinator("coordinator").unwrap();
let payee = Node::start_test_app("payee").unwrap();

payer.connect(coordinator.info).await.unwrap();
payee.connect(coordinator.info).await.unwrap();

let payer_outbound_liquidity = 200_000;

payer.fund(Amount::ONE_BTC).await.unwrap();
payer
.open_public_channel(&coordinator, payer_outbound_liquidity, 0)
.await
.unwrap();

// Act

// The coordinator should not be able to open any JIT channel because we have not funded their
// on-chain wallet
let invoice_amount = 10_000;

let interceptable_route_hint_hop = coordinator.prepare_jit_channel(payee.info.pubkey);
let invoice = payee
.create_interceptable_invoice(
Some(invoice_amount),
0,
"interceptable-invoice".to_string(),
interceptable_route_hint_hop,
)
.unwrap();

payer.send_payment(&invoice).unwrap();

// Assert

payer
.wait_for_payment(HTLCStatus::Failed, invoice.payment_hash(), None)
.await
.unwrap();
}
1 change: 1 addition & 0 deletions crates/ln-dlc-node/src/tests/just_in_time_channel/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod channel_close;
mod create;
mod fail_intercepted_htlc;
mod multiple_payments;
1 change: 1 addition & 0 deletions crates/ln-dlc-node/src/tests/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ async fn single_app_many_positions_load() {
},
Arc::new(InMemoryStore::default()),
LnDlcNodeSettings::default(),
None,
)
.unwrap(),
);
Expand Down
Loading

0 comments on commit 0a9c34d

Please sign in to comment.