Skip to content

Commit 2cc5267

Browse files
fixup: client_trusts_lsp is now a dynamic flag. also channel_needs_manual_broadcast logs error if it fails
1 parent 53a8174 commit 2cc5267

File tree

4 files changed

+168
-29
lines changed

4 files changed

+168
-29
lines changed

src/event.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -514,16 +514,20 @@ where
514514
locktime,
515515
) {
516516
Ok(final_tx) => {
517-
let needs_manual_broadcast = self
518-
.liquidity_source
519-
.as_ref()
520-
.map(|ls| {
517+
let needs_manual_broadcast =
518+
match self.liquidity_source.as_ref().map(|ls| {
521519
ls.as_ref().lsps2_channel_needs_manual_broadcast(
522520
counterparty_node_id,
523521
user_channel_id,
524522
)
525-
})
526-
.unwrap_or(false);
523+
}) {
524+
Some(Ok(v)) => v,
525+
Some(Err(e)) => {
526+
log_error!(self.logger, "Failed to determine if channel needs manual broadcast: {:?}", e);
527+
false
528+
},
529+
None => false,
530+
};
527531

528532
let result = if needs_manual_broadcast {
529533
self.liquidity_source.as_ref().map(|ls| {

src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,14 @@ impl Node {
10101010
self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect()
10111011
}
10121012

1013+
/// Dynamically set whether the LSPS2 service operates in `client_trusts_lsp` mode.
1014+
pub fn set_lsps2_client_trusts_lsp(&self, trust: bool) -> Result<(), Error> {
1015+
let liquidity_source =
1016+
self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?;
1017+
liquidity_source.set_client_trusts_lsp(trust);
1018+
Ok(())
1019+
}
1020+
10131021
/// Connect to a node on the peer-to-peer network.
10141022
///
10151023
/// If `persist` is set to `true`, we'll remember the peer and reconnect to it on restart.

src/liquidity.rs

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use lightning::ln::msgs::SocketAddress;
2020
use lightning::ln::types::ChannelId;
2121
use lightning::routing::router::{RouteHint, RouteHintHop};
2222

23+
use lightning::util::errors::APIError;
2324
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, InvoiceBuilder, RoutingFees};
2425

2526
use lightning_liquidity::events::LiquidityEvent;
@@ -92,9 +93,12 @@ pub(crate) struct LSPS2ClientConfig {
9293
pub token: Option<String>,
9394
}
9495

96+
use std::sync::atomic::{AtomicBool, Ordering};
97+
9598
struct LSPS2Service {
9699
service_config: LSPS2ServiceConfig,
97100
ldk_service_config: LdkLSPS2ServiceConfig,
101+
client_trusts_lsp: AtomicBool,
98102
}
99103

100104
/// Represents the configuration of the LSPS2 service.
@@ -133,8 +137,6 @@ pub struct LSPS2ServiceConfig {
133137
pub min_payment_size_msat: u64,
134138
/// The maximum payment size that we will accept when opening a channel.
135139
pub max_payment_size_msat: u64,
136-
/// Use the client trusts lsp model
137-
pub client_trusts_lsp: bool,
138140
}
139141

140142
pub(crate) struct LiquiditySourceBuilder<L: Deref>
@@ -217,7 +219,11 @@ where
217219
&mut self, promise_secret: [u8; 32], service_config: LSPS2ServiceConfig,
218220
) -> &mut Self {
219221
let ldk_service_config = LdkLSPS2ServiceConfig { promise_secret };
220-
self.lsps2_service = Some(LSPS2Service { service_config, ldk_service_config });
222+
self.lsps2_service = Some(LSPS2Service {
223+
client_trusts_lsp: AtomicBool::new(false),
224+
service_config,
225+
ldk_service_config,
226+
});
221227
self
222228
}
223229

@@ -295,23 +301,21 @@ where
295301

296302
pub(crate) fn lsps2_channel_needs_manual_broadcast(
297303
&self, counterparty_node_id: PublicKey, user_channel_id: u128,
298-
) -> bool {
304+
) -> Result<bool, APIError> {
299305
// if we are not in a client_trusts_lsp model, we don't check and just return false
300306
if !self.is_client_trusts_lsp() {
301307
log_debug!(self.logger, "Skipping funding transaction broadcast as client trusts LSP.");
302-
return false;
308+
return Ok(false);
303309
}
304310

305311
// if we are in a client_trusts_lsp model, then we check if the LSP has an LSPS2 operation in progress
306-
self.lsps2_service.as_ref().map_or(false, |_| {
312+
self.lsps2_service.as_ref().map_or(Ok(false), |_| {
307313
let lsps2_service_handler = self.liquidity_manager.lsps2_service_handler();
308314
if let Some(handler) = lsps2_service_handler {
309-
handler
310-
.channel_needs_manual_broadcast(user_channel_id, &counterparty_node_id)
311-
.unwrap_or(false)
315+
handler.channel_needs_manual_broadcast(user_channel_id, &counterparty_node_id)
312316
} else {
313317
log_error!(self.logger, "LSPS2 service handler is not available.");
314-
false
318+
Ok(false)
315319
}
316320
})
317321
}
@@ -360,9 +364,21 @@ where
360364
});
361365
}
362366

367+
pub(crate) fn set_client_trusts_lsp(&self, trust: bool) {
368+
if let Some(service) = self.lsps2_service.as_ref() {
369+
service.client_trusts_lsp.store(trust, Ordering::SeqCst);
370+
log_info!(self.logger, "Updated LSPS2 client_trusts_lsp to {}", trust);
371+
} else {
372+
log_error!(
373+
self.logger,
374+
"Attempted to update LSPS2 trust mode but service not configured"
375+
);
376+
}
377+
}
378+
363379
fn is_client_trusts_lsp(&self) -> bool {
364380
if let Some(lsps2_service) = self.lsps2_service.as_ref() {
365-
lsps2_service.service_config.client_trusts_lsp
381+
lsps2_service.client_trusts_lsp.load(Ordering::SeqCst)
366382
} else {
367383
false
368384
}
@@ -547,10 +563,8 @@ where
547563
if let Some(lsps2_service_handler) =
548564
self.liquidity_manager.lsps2_service_handler().as_ref()
549565
{
550-
let service_config = if let Some(service_config) =
551-
self.lsps2_service.as_ref().map(|s| s.service_config.clone())
552-
{
553-
service_config
566+
let service_config = if let Some(service) = self.lsps2_service.as_ref() {
567+
service.service_config.clone()
554568
} else {
555569
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as LSPS2 liquidity service was not configured.",);
556570
return;
@@ -616,10 +630,13 @@ where
616630
if let Some(lsps2_service_handler) =
617631
self.liquidity_manager.lsps2_service_handler().as_ref()
618632
{
619-
let service_config = if let Some(service_config) =
620-
self.lsps2_service.as_ref().map(|s| s.service_config.clone())
633+
let (service_config, trusts_lsp) = if let Some(service) =
634+
self.lsps2_service.as_ref()
621635
{
622-
service_config
636+
(
637+
service.service_config.clone(),
638+
service.client_trusts_lsp.load(Ordering::SeqCst),
639+
)
623640
} else {
624641
log_error!(self.logger, "Failed to handle LSPS2ServiceEvent as LSPS2 liquidity service was not configured.",);
625642
return;
@@ -656,7 +673,7 @@ where
656673
request_id,
657674
intercept_scid,
658675
LSPS2_CHANNEL_CLTV_EXPIRY_DELTA,
659-
service_config.client_trusts_lsp,
676+
trusts_lsp,
660677
user_channel_id,
661678
) {
662679
Ok(()) => {},

tests/integration_tests_rust.rs

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,7 +1263,6 @@ fn lsps2_client_service_integration() {
12631263
min_channel_lifetime: 100,
12641264
min_channel_opening_fee_msat: 0,
12651265
max_client_to_self_delay: 1024,
1266-
client_trusts_lsp: false,
12671266
};
12681267

12691268
let service_config = random_config(true);
@@ -1409,7 +1408,6 @@ fn lsps2_client_trusts_lsp() {
14091408
min_channel_lifetime: 100,
14101409
min_channel_opening_fee_msat: 0,
14111410
max_client_to_self_delay: 1024,
1412-
client_trusts_lsp: true,
14131411
};
14141412

14151413
let service_config = random_config(true);
@@ -1418,7 +1416,7 @@ fn lsps2_client_trusts_lsp() {
14181416
service_builder.set_liquidity_provider_lsps2(lsps2_service_config);
14191417
let service_node = service_builder.build().unwrap();
14201418
service_node.start().unwrap();
1421-
1419+
service_node.set_lsps2_client_trusts_lsp(true).unwrap();
14221420
let service_node_id = service_node.node_id();
14231421
let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone();
14241422

@@ -1530,6 +1528,119 @@ fn lsps2_client_trusts_lsp() {
15301528
assert!(funding_tx_found, "Funding transaction should be broadcast after the client claims it");
15311529
}
15321530

1531+
#[test]
1532+
fn lsps2_in_flight_under_attack_switch() {
1533+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
1534+
1535+
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
1536+
1537+
let sync_config = EsploraSyncConfig { background_sync_config: None };
1538+
1539+
// Setup three nodes: service, client, and payer
1540+
let channel_opening_fee_ppm = 10_000;
1541+
let channel_over_provisioning_ppm = 100_000;
1542+
let lsps2_service_config = LSPS2ServiceConfig {
1543+
require_token: None,
1544+
advertise_service: false,
1545+
channel_opening_fee_ppm,
1546+
channel_over_provisioning_ppm,
1547+
max_payment_size_msat: 1_000_000_000,
1548+
min_payment_size_msat: 0,
1549+
min_channel_lifetime: 100,
1550+
min_channel_opening_fee_msat: 0,
1551+
max_client_to_self_delay: 1024,
1552+
};
1553+
1554+
let service_config = random_config(true);
1555+
setup_builder!(service_builder, service_config.node_config);
1556+
service_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
1557+
service_builder.set_liquidity_provider_lsps2(lsps2_service_config);
1558+
let service_node = service_builder.build().unwrap();
1559+
service_node.start().unwrap();
1560+
1561+
let service_node_id = service_node.node_id();
1562+
let service_addr = service_node.listening_addresses().unwrap().first().unwrap().clone();
1563+
1564+
let client_config = random_config(true);
1565+
setup_builder!(client_builder, client_config.node_config);
1566+
client_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
1567+
client_builder.set_liquidity_source_lsps2(service_node_id, service_addr.clone(), None);
1568+
let client_node = client_builder.build().unwrap();
1569+
client_node.start().unwrap();
1570+
1571+
let payer_config = random_config(true);
1572+
setup_builder!(payer_builder, payer_config.node_config);
1573+
payer_builder.set_chain_source_esplora(esplora_url.clone(), Some(sync_config));
1574+
let payer_node = payer_builder.build().unwrap();
1575+
payer_node.start().unwrap();
1576+
1577+
let service_addr_onchain = service_node.onchain_payment().new_address().unwrap();
1578+
let client_addr_onchain = client_node.onchain_payment().new_address().unwrap();
1579+
let payer_addr_onchain = payer_node.onchain_payment().new_address().unwrap();
1580+
1581+
let premine_amount_sat = 10_000_000;
1582+
1583+
premine_and_distribute_funds(
1584+
&bitcoind.client,
1585+
&electrsd.client,
1586+
vec![service_addr_onchain, client_addr_onchain, payer_addr_onchain],
1587+
Amount::from_sat(premine_amount_sat),
1588+
);
1589+
service_node.sync_wallets().unwrap();
1590+
client_node.sync_wallets().unwrap();
1591+
payer_node.sync_wallets().unwrap();
1592+
println!("Premine complete!");
1593+
// Open a channel payer -> service that will allow paying the JIT invoice
1594+
open_channel(&payer_node, &service_node, 5_000_000, false, &electrsd);
1595+
1596+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);
1597+
service_node.sync_wallets().unwrap();
1598+
payer_node.sync_wallets().unwrap();
1599+
expect_channel_ready_event!(payer_node, service_node.node_id());
1600+
expect_channel_ready_event!(service_node, payer_node.node_id());
1601+
1602+
let initial_mempool_size = bitcoind.client.get_raw_mempool().unwrap().0.len();
1603+
1604+
let invoice_description =
1605+
Bolt11InvoiceDescription::Direct(Description::new(String::from("asdf")).unwrap());
1606+
let jit_amount_msat = 100_000_000;
1607+
1608+
println!("Generating JIT invoice!");
1609+
let jit_invoice = client_node
1610+
.bolt11_payment()
1611+
.receive_via_jit_channel(jit_amount_msat, &invoice_description.into(), 1024, None)
1612+
.unwrap();
1613+
1614+
// Have the payer_node pay the invoice, thereby triggering channel open service_node -> client_node.
1615+
println!("Paying JIT invoice!");
1616+
let _payment_id = payer_node.bolt11_payment().send(&jit_invoice, None).unwrap();
1617+
1618+
// Switch trust mode immediately after payment is sent, before events are processed.
1619+
// This simulates the LSP going into "under-attack" mode.
1620+
println!("Switching trust mode on LSP during in-flight JIT channel opening.");
1621+
service_node.set_lsps2_client_trusts_lsp(true).unwrap();
1622+
1623+
expect_channel_pending_event!(service_node, client_node.node_id());
1624+
expect_channel_ready_event!(service_node, client_node.node_id());
1625+
expect_channel_pending_event!(client_node, service_node.node_id());
1626+
expect_channel_ready_event!(client_node, service_node.node_id());
1627+
1628+
println!("Waiting for funding transaction to be broadcast...");
1629+
let mut funding_tx_found = false;
1630+
for _ in 0..500 {
1631+
std::thread::sleep(std::time::Duration::from_millis(100));
1632+
let current_mempool = bitcoind.client.get_raw_mempool().unwrap();
1633+
if current_mempool.0.len() > initial_mempool_size {
1634+
funding_tx_found = true;
1635+
break;
1636+
}
1637+
}
1638+
assert!(
1639+
funding_tx_found,
1640+
"Funding transaction should be broadcast immediately as the JIT process started before the trust switch"
1641+
);
1642+
}
1643+
15331644
#[test]
15341645
fn lsps2_lsp_trusts_client_but_client_does_not_claim() {
15351646
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
@@ -1551,7 +1662,6 @@ fn lsps2_lsp_trusts_client_but_client_does_not_claim() {
15511662
min_channel_lifetime: 100,
15521663
min_channel_opening_fee_msat: 0,
15531664
max_client_to_self_delay: 1024,
1554-
client_trusts_lsp: false,
15551665
};
15561666

15571667
let service_config = random_config(true);

0 commit comments

Comments
 (0)