diff --git a/CHANGELOG.md b/CHANGELOG.md index cfdc5701a2..4d7c631bb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,13 @@ disclosed. The method is stateless: the payment id, preimage, and invoice are taken from `Event::PaymentSuccessful` and nothing is persisted. Payments settled via a static invoice, i.e., async payments, don't support payer proofs. (#1045) +- `Node::splice_in`, `Node::splice_in_with_all`, and `Node::splice_out` now take an optional + `min_fee_rate`. If set, it is used as the minimum feerate for our contribution to the splice + transaction instead of a feerate estimate retrieved from the configured chain source. For + `splice_in_with_all`, the amount spliced in is determined at `min_fee_rate`, or at the estimate + if unset. + `Node::bump_channel_funding_fee` takes the same optional `min_fee_rate` for the replacement + transaction. ## Bug Fixes and Improvements - Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 259ae41d55..326de194da 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -90,13 +90,13 @@ interface Node { [Throws=NodeError] UserChannelId open_0reserve_channel_with_all(PublicKey node_id, SocketAddress address, u64? push_to_counterparty_msat, FeeRate? fee_rate, ChannelConfig? channel_config); [Throws=NodeError] - void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats); + void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats, FeeRate? min_fee_rate); [Throws=NodeError] - void splice_in_with_all([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id); + void splice_in_with_all([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, FeeRate? min_fee_rate); [Throws=NodeError] - void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats); + void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats, FeeRate? min_fee_rate); [Throws=NodeError] - void bump_channel_funding_fee([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id); + void bump_channel_funding_fee([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, FeeRate? min_fee_rate); [Throws=NodeError] void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id); [Throws=NodeError] diff --git a/src/builder.rs b/src/builder.rs index 7f28996695..54e79f4b3a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -2819,9 +2819,9 @@ mod tests { node.update_channel_config(&user_channel_id, node_id, Default::default()), Err(Error::NotRunning) ); - assert_eq!(node.splice_in(&user_channel_id, node_id, 1_000), Err(Error::NotRunning)); + assert_eq!(node.splice_in(&user_channel_id, node_id, 1_000, None), Err(Error::NotRunning)); assert_eq!( - node.bump_channel_funding_fee(&user_channel_id, node_id), + node.bump_channel_funding_fee(&user_channel_id, node_id, None), Err(Error::NotRunning) ); assert_eq!(node.bolt11_payment().fail_for_id(payment_id), Err(Error::NotRunning)); diff --git a/src/lib.rs b/src/lib.rs index 7262e9ba02..a5f2a72e18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1805,15 +1805,16 @@ impl Node { fn splice_in_inner( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, - splice_amount_sats: FundingAmount, + splice_amount_sats: FundingAmount, min_fee_rate: Option, ) -> Result<(), Error> { let open_channels = self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); if let Some(channel_details) = open_channels.iter().find(|c| c.user_channel_id == user_channel_id.0) { - let min_feerate = - self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding); + let min_feerate = min_fee_rate.unwrap_or_else(|| { + self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding) + }); let max_feerate = max_funding_feerate(min_feerate); let splice_amount_sats = match splice_amount_sats { @@ -1939,13 +1940,19 @@ impl Node { /// it. Once negotiation with the counterparty is complete, the channel remains operational /// while waiting for a new funding transaction to confirm. /// + /// If `min_fee_rate` is set, it will be used as the minimum feerate for our contribution to the + /// splice transaction instead of a feerate estimate retrieved from the configured chain source. + /// As with the estimate, we may pay up to 1.5 times this feerate: when the channel already has a + /// pending splice that requires a higher feerate to be replaced, or when the counterparty + /// initiates a splice concurrently and our contribution is added to theirs. + /// /// # Experimental API /// /// This API is experimental. Currently, a splice-in will be marked as an outbound payment, but /// this classification may change in the future. pub fn splice_in( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, - splice_amount_sats: u64, + splice_amount_sats: u64, min_fee_rate: Option, ) -> Result<(), Error> { if !*self.is_running.read().expect("lock") { return Err(Error::NotRunning); @@ -1955,6 +1962,7 @@ impl Node { user_channel_id, counterparty_node_id, FundingAmount::Exact { amount_sats: splice_amount_sats }, + maybe_map_fee_rate_opt(min_fee_rate), ) } @@ -1967,17 +1975,31 @@ impl Node { /// it. Once negotiation with the counterparty is complete, the channel remains operational /// while waiting for a new funding transaction to confirm. /// + /// If `min_fee_rate` is set, it will be used as the minimum feerate for our contribution to the + /// splice transaction instead of a feerate estimate retrieved from the configured chain source. + /// As with the estimate, we may pay up to 1.5 times this feerate: when the channel already has a + /// pending splice that requires a higher feerate to be replaced, or when the counterparty + /// initiates a splice concurrently and our contribution is added to theirs. + /// + /// The amount spliced in is determined at `min_fee_rate`, or at the estimate if unset. + /// /// # Experimental API /// /// This API is experimental. Currently, a splice-in will be marked as an outbound payment, but /// this classification may change in the future. pub fn splice_in_with_all( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, + min_fee_rate: Option, ) -> Result<(), Error> { if !*self.is_running.read().expect("lock") { return Err(Error::NotRunning); } - self.splice_in_inner(user_channel_id, counterparty_node_id, FundingAmount::Max) + self.splice_in_inner( + user_channel_id, + counterparty_node_id, + FundingAmount::Max, + maybe_map_fee_rate_opt(min_fee_rate), + ) } /// Remove funds from an existing channel, sending them to an on-chain address. @@ -1986,6 +2008,12 @@ impl Node { /// it. Once negotiation with the counterparty is complete, the channel remains operational /// while waiting for a new funding transaction to confirm. /// + /// If `min_fee_rate` is set, it will be used as the minimum feerate for our contribution to the + /// splice transaction instead of a feerate estimate retrieved from the configured chain source. + /// As with the estimate, we may pay up to 1.5 times this feerate: when the channel already has a + /// pending splice that requires a higher feerate to be replaced, or when the counterparty + /// initiates a splice concurrently and our contribution is added to theirs. + /// /// # Experimental API /// /// This API is experimental. Currently, a splice-out will be marked as an inbound payment if @@ -1993,12 +2021,14 @@ impl Node { /// in the future. pub fn splice_out( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, address: &Address, - splice_amount_sats: u64, + splice_amount_sats: u64, min_fee_rate: Option, ) -> Result<(), Error> { if !*self.is_running.read().expect("lock") { return Err(Error::NotRunning); } + let min_fee_rate = maybe_map_fee_rate_opt(min_fee_rate); + let open_channels = self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); if let Some(channel_details) = @@ -2012,8 +2042,9 @@ impl Node { self.wallet.parse_and_validate_address(address)?; - let min_feerate = - self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding); + let min_feerate = min_fee_rate.unwrap_or_else(|| { + self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding) + }); let max_feerate = max_funding_feerate(min_feerate); let funding_template = self @@ -2077,20 +2108,29 @@ impl Node { /// Fee-bumps the pending splice on a channel by replacing its in-flight funding transaction /// (RBF). The splice's amount and destination are preserved; only the fee rate is raised. /// Errors if the channel has no pending splice to bump. + /// + /// If `min_fee_rate` is set, it will be used as the minimum feerate for the replacement instead + /// of a feerate estimate retrieved from the configured chain source. As with the estimate, we + /// pay the feerate required to replace the pending splice if it is higher, but no more than 1.5 + /// times this feerate: if replacing it requires more, the bump fails. pub fn bump_channel_funding_fee( &self, user_channel_id: &UserChannelId, counterparty_node_id: PublicKey, + min_fee_rate: Option, ) -> Result<(), Error> { if !*self.is_running.read().expect("lock") { return Err(Error::NotRunning); } + let min_fee_rate = maybe_map_fee_rate_opt(min_fee_rate); + let open_channels = self.channel_manager.list_channels_with_counterparty(&counterparty_node_id); if let Some(channel_details) = open_channels.iter().find(|c| c.user_channel_id == user_channel_id.0) { - let min_feerate = - self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding); + let min_feerate = min_fee_rate.unwrap_or_else(|| { + self.fee_estimator.estimate_fee_rate(ConfirmationTarget::ChannelFunding) + }); let funding_template = self .channel_manager diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 3f7e6c1517..127fac2439 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1236,7 +1236,7 @@ pub async fn open_channel_with_all( pub async fn splice_in_with_all( node_a: &TestNode, node_b: &TestNode, user_channel_id: &UserChannelId, electrsd: &ElectrsD, ) { - node_a.splice_in_with_all(user_channel_id, node_b.node_id()).unwrap(); + node_a.splice_in_with_all(user_channel_id, node_b.node_id(), None).unwrap(); let splice_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); wait_for_tx(&electrsd.client, splice_txo.txid).await; @@ -1681,7 +1681,7 @@ pub(crate) async fn do_channel_full_cycle( let available_splice_out_sat = node_b.list_channels()[0].outbound_capacity_msat / 1000; let splice_out_sat = available_splice_out_sat / 2; assert!(splice_out_sat > 500_000); - node_b.splice_out(&user_channel_id_b, node_a.node_id(), &addr_a, splice_out_sat).unwrap(); + node_b.splice_out(&user_channel_id_b, node_a.node_id(), &addr_a, splice_out_sat, None).unwrap(); let splice_out_txo = expect_splice_negotiated_event!(node_b, node_a.node_id()); wait_for_tx(electrsd, splice_out_txo.txid).await; @@ -1703,7 +1703,7 @@ pub(crate) async fn do_channel_full_cycle( println!("\nA splices in the splice-out payment from B"); let splice_in_sat = splice_out_sat; - node_a.splice_in(&user_channel_id_a, node_b.node_id(), splice_in_sat).unwrap(); + node_a.splice_in(&user_channel_id_a, node_b.node_id(), splice_in_sat, None).unwrap(); let splice_in_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); wait_for_tx(electrsd, splice_in_txo.txid).await; diff --git a/tests/common/scenarios/mod.rs b/tests/common/scenarios/mod.rs index 3a1066ca0d..addc34681d 100644 --- a/tests/common/scenarios/mod.rs +++ b/tests/common/scenarios/mod.rs @@ -239,7 +239,7 @@ pub(crate) async fn splice_in_scenario( ) .await; let ext_node_id = peer.get_node_id().await.unwrap(); - node.splice_in(&user_ch, ext_node_id, 500_000).unwrap(); + node.splice_in(&user_ch, ext_node_id, 500_000, None).unwrap(); let splice_txo = expect_splice_negotiated_event!(node, ext_node_id); wait_for_tx(electrs, splice_txo.txid).await; generate_blocks_and_wait(bitcoind, electrs, 6).await; diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 404b846415..080fb8c359 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -19,7 +19,7 @@ use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::sha256::Hash as Sha256Hash; use bitcoin::hashes::Hash; use bitcoin::hex::DisplayHex; -use bitcoin::{Address, Amount, ScriptBuf, Transaction, Txid}; +use bitcoin::{Address, Amount, FeeRate, ScriptBuf, Transaction, Txid}; use common::logging::{ init_log_logger, validate_log_entry, CollectingLogWriter, MultiNodeLogger, TestLogWriter, }; @@ -2851,7 +2851,8 @@ async fn splice_channel() { &user_channel_id_a, node_b.node_id(), &address, - excessive_splice_out_sats + excessive_splice_out_sats, + None ), Err(NodeError::ChannelSplicingFailed), ); @@ -2861,11 +2862,11 @@ async fn splice_channel() { let amount_msat = 400_000_000; assert_eq!( - node_b.splice_in(&user_channel_id_b, node_b.node_id(), 5_000_000), + node_b.splice_in(&user_channel_id_b, node_b.node_id(), 5_000_000, None), Err(NodeError::ChannelSplicingFailed), ); assert_eq!( - node_b.splice_out(&user_channel_id_b, node_b.node_id(), &address, amount_msat / 1000), + node_b.splice_out(&user_channel_id_b, node_b.node_id(), &address, amount_msat / 1000, None), Err(NodeError::ChannelSplicingFailed), ); assert_eq!( @@ -2874,7 +2875,7 @@ async fn splice_channel() { ); // Splice-in funds for Node B so that it has outbound liquidity to make a payment - node_b.splice_in(&user_channel_id_b, node_a.node_id(), 4_000_000).unwrap(); + node_b.splice_in(&user_channel_id_b, node_a.node_id(), 4_000_000, None).unwrap(); let txo = expect_splice_negotiated_event!(node_b, node_a.node_id()); @@ -2932,7 +2933,9 @@ async fn splice_channel() { // Splice-out funds for Node A from the payment sent by Node B let address = node_a.onchain_payment().new_address().unwrap(); - node_a.splice_out(&user_channel_id_a, node_b.node_id(), &address, amount_msat / 1000).unwrap(); + node_a + .splice_out(&user_channel_id_a, node_b.node_id(), &address, amount_msat / 1000, None) + .unwrap(); let txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); @@ -2976,6 +2979,136 @@ async fn splice_channel() { ); } +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn splice_channel_with_fee_rate() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = random_chain_source(&bitcoind, &electrsd); + + let (node_a, node_b) = setup_two_nodes(&chain_source, false, false); + + let address_a = node_a.onchain_payment().new_address().unwrap(); + let address_b = node_b.onchain_payment().new_address().unwrap(); + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![address_a, address_b], + Amount::from_sat(5_000_000), + ) + .await; + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + open_channel(&node_a, &node_b, 4_000_000, false, &electrsd).await; + + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + // Ten times the feerate our chain source estimates on regtest, so a splice that ignored the + // requested feerate would be easy to tell apart. + let fee_rate = FeeRate::from_sat_per_kwu(2_500); + + let address = node_a.onchain_payment().new_address().unwrap(); + node_a + .splice_out(&user_channel_id_a, node_b.node_id(), &address, 100_000, Some(fee_rate.into())) + .unwrap(); + + let txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); + wait_for_classified_funding_payment(&node_a, txo.txid).await; + wait_for_tx(&electrsd.client, txo.txid).await; + + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + assert_splice_pays_fee_rate(&node_a, &electrsd.client, txo.txid, fee_rate); + + // A pending splice is fee-bumped to a requested feerate. Replacing it takes a feerate well above + // our estimate, so the bump only succeeds at the requested one. Staying within 1.5 times the + // pending splice's feerate lets the replacement reuse its inputs, paying the higher fee from + // their change. + node_a.splice_in(&user_channel_id_a, node_b.node_id(), 100_000, Some(fee_rate.into())).unwrap(); + + let original_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); + wait_for_classified_funding_payment(&node_a, original_txo.txid).await; + wait_for_tx(&electrsd.client, original_txo.txid).await; + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + let bump_fee_rate = FeeRate::from_sat_per_kwu(3_000); + node_a + .bump_channel_funding_fee(&user_channel_id_a, node_b.node_id(), Some(bump_fee_rate.into())) + .unwrap(); + + let txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); + assert_ne!(original_txo, txo, "RBF should produce a different funding txo"); + wait_for_classified_funding_payment(&node_a, txo.txid).await; + wait_for_tx(&electrsd.client, txo.txid).await; + + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + assert_splice_pays_fee_rate(&node_a, &electrsd.client, txo.txid, bump_fee_rate); + + // The amount spliced in is determined at the requested feerate, so the splice would fail to + // fund its fee if it were determined at our estimate instead. + node_a.splice_in_with_all(&user_channel_id_a, node_b.node_id(), Some(fee_rate.into())).unwrap(); + + let txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); + wait_for_classified_funding_payment(&node_a, txo.txid).await; + wait_for_tx(&electrsd.client, txo.txid).await; + + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6).await; + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + assert_splice_pays_fee_rate(&node_a, &electrsd.client, txo.txid, fee_rate); +} + +/// Asserts that the splice transaction `txid` we funded pays `fee_rate`. Our fee is determined from +/// an estimate of the transaction's weight, which may differ slightly from its actual weight. +/// +/// The payment is looked up by transaction rather than by id, as a fee-bumped splice keeps the id +/// of its first candidate. +fn assert_splice_pays_fee_rate( + node: &Node, electrs: &E, txid: Txid, fee_rate: FeeRate, +) { + let payment = node + .list_all_payments() + .into_iter() + .find(|p| matches!(p.kind, PaymentKind::Onchain { txid: t, .. } if t == txid)) + .unwrap(); + let fee_paid_sat = payment.fee_paid_msat.unwrap() / 1_000; + let weight_wu = electrs.transaction_get(&txid).unwrap().weight().to_wu(); + + let paid_sat_per_kwu = fee_paid_sat * 1_000 / weight_wu; + let requested_sat_per_kwu = fee_rate.to_sat_per_kwu(); + assert!( + paid_sat_per_kwu.abs_diff(requested_sat_per_kwu) <= requested_sat_per_kwu / 100, + "paid {} sat/kwu, requested {} sat/kwu", + paid_sat_per_kwu, + requested_sat_per_kwu + ); +} + #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn rbf_splice_channel() { run_rbf_splice_channel_test(false).await; @@ -3039,12 +3172,12 @@ async fn run_rbf_splice_channel_test(confirm_original: bool) { // bump_channel_funding_fee should fail when there's no pending splice assert_eq!( - node_b.bump_channel_funding_fee(&user_channel_id_b, node_a.node_id()), + node_b.bump_channel_funding_fee(&user_channel_id_b, node_a.node_id(), None), Err(NodeError::ChannelSplicingFailed), ); // Initiate a splice-in to create a pending splice - node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000).unwrap(); + node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000, None).unwrap(); let original_txo = expect_splice_negotiated_event!(node_b, node_a.node_id()); @@ -3075,12 +3208,12 @@ async fn run_rbf_splice_channel_test(confirm_original: bool) { // Re-splicing the pending splice we already contributed to is rejected; the RBF guard points at // bump_channel_funding_fee instead. assert_eq!( - node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000), + node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000, None), Err(NodeError::ChannelSplicingFailed), ); // bump_channel_funding_fee should succeed when there's a pending splice - node_b.bump_channel_funding_fee(&user_channel_id_b, node_a.node_id()).unwrap(); + node_b.bump_channel_funding_fee(&user_channel_id_b, node_a.node_id(), None).unwrap(); let rbf_txo = expect_splice_negotiated_event!(node_b, node_a.node_id()); @@ -3284,7 +3417,7 @@ async fn splice_payment_reorged_to_unconfirmed() { let user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); // node_b splices in, recording a funding payment it contributed to. - node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000).unwrap(); + node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000, None).unwrap(); let splice_txo = expect_splice_negotiated_event!(node_b, node_a.node_id()); wait_for_tx(&electrsd.client, splice_txo.txid).await; // node_b recorded what the splice's transaction is when signing it, so the sync below files it @@ -3362,7 +3495,7 @@ async fn splice_in_rbf_joins_counterparty_splice() { // node_b (which didn't fund the channel open, so holds the on-chain balance) initiates a // splice-in; node_a does not contribute to this first candidate. - node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000).unwrap(); + node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000, None).unwrap(); let counterparty_txo = expect_splice_negotiated_event!(node_b, node_a.node_id()); wait_for_tx(&electrsd.client, counterparty_txo.txid).await; node_a.sync_wallets().unwrap(); @@ -3371,7 +3504,7 @@ async fn splice_in_rbf_joins_counterparty_splice() { // node_a contributes to the pending splice via RBF. Before honoring the funding template's RBF // minimum feerate, this was rejected with FeeRateBelowRbfMinimum because node_a's funding // feerate estimate sat below the minimum required to replace the in-flight transaction. - node_a.splice_in(&user_channel_id_a, node_b.node_id(), 100_000).unwrap(); + node_a.splice_in(&user_channel_id_a, node_b.node_id(), 100_000, None).unwrap(); let rbf_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); expect_splice_negotiated_event!(node_b, node_a.node_id()); assert_ne!(counterparty_txo, rbf_txo, "node_a's RBF should produce a different funding txo"); @@ -3444,7 +3577,7 @@ async fn open_and_splice_from_counterparty( let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id()); let user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); - node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000).unwrap(); + node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000, None).unwrap(); let counterparty_txo = expect_splice_negotiated_event!(node_b, node_a.node_id()); wait_for_tx(&electrsd.client, counterparty_txo.txid).await; node_a.sync_wallets().unwrap(); @@ -3626,7 +3759,7 @@ async fn open_and_join_counterparty_splice( let user_channel_id_a = expect_channel_ready_event!(node_a, node_b.node_id()); let user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); - node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000).unwrap(); + node_b.splice_in(&user_channel_id_b, node_a.node_id(), 1_000_000, None).unwrap(); let first_txo = expect_splice_negotiated_event!(node_b, node_a.node_id()); let first_tx = wait_for_transaction(bitcoind, electrsd, first_txo.txid).await; wait_for_classified_funding_payment(node_b, first_txo.txid).await; @@ -3634,7 +3767,7 @@ async fn open_and_join_counterparty_splice( node_b.sync_wallets().unwrap(); deprioritise_transaction(bitcoind, first_txo.txid); - node_a.splice_in(&user_channel_id_a, node_b.node_id(), 100_000).unwrap(); + node_a.splice_in(&user_channel_id_a, node_b.node_id(), 100_000, None).unwrap(); let rbf_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); expect_splice_negotiated_event!(node_b, node_a.node_id()); assert_ne!(first_txo, rbf_txo, "node A's round should replace node B's"); @@ -3774,7 +3907,7 @@ async fn signed_splice_round_the_monitor_watches_is_kept_at_close() { let broadcast_b = logs_b.count(BROADCAST_FUNDING); let resolved_a = logs_a.count(CLOSED_CHANNEL_ROUNDS_RESOLVED); - node_a.splice_in(&user_channel_id_a, node_b.node_id(), 200_000).unwrap(); + node_a.splice_in(&user_channel_id_a, node_b.node_id(), 200_000, None).unwrap(); // Recording the round writes what the transaction is before the round is signed, so node A does // not sign while those writes are held, and node B's `commitment_signed` is stashed until it // has. @@ -3890,7 +4023,7 @@ async fn broadcast_splice_round_lost_to_a_close_fails_its_payment() { .and_then(|channel| channel.funding_txo) .expect("the channel has a funding"); - node_a.splice_in(&user_channel_id_a, node_b.node_id(), 500_000).unwrap(); + node_a.splice_in(&user_channel_id_a, node_b.node_id(), 500_000, None).unwrap(); let splice_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); wait_for_tx(&electrsd.client, splice_txo.txid).await; wait_for_classified_funding_payment(&node_a, splice_txo.txid).await; @@ -4055,7 +4188,7 @@ async fn signed_splice_round_the_monitor_does_not_watch_is_dropped_at_close() { let reclaimed_a = logs_a.count(RECLAIMED_ADDRESSES); let hold_b = Arc::clone(&store_b.serializer).write_owned().await; - node_a.splice_in(&user_channel_id_a, node_b.node_id(), 200_000).unwrap(); + node_a.splice_in(&user_channel_id_a, node_b.node_id(), 200_000, None).unwrap(); assert!(logs_a.wait_for_count(SIGNED_FUNDING, signed_a + 1).await, "node A never signed"); let rbf_txid = only_signed_round_txid(&logs_a); assert_eq!(logs_b.count(SIGNED_FUNDING), signed_b, "node B signed with its writes held"); @@ -4131,7 +4264,7 @@ async fn superseded_zero_conf_splice_round_keeps_its_payment_at_close() { node_a.sync_wallets().unwrap(); node_b.sync_wallets().unwrap(); - node_a.splice_in(&user_channel_id_a, node_b.node_id(), 1_000_000).unwrap(); + node_a.splice_in(&user_channel_id_a, node_b.node_id(), 1_000_000, None).unwrap(); let first = expect_splice_negotiated_event!(node_a, node_b.node_id()); wait_for_classified_funding_payment(&node_a, first.txid).await; // The zero-conf splice locks without confirmations, re-signaled as `ChannelReady`, and node A @@ -4141,7 +4274,7 @@ async fn superseded_zero_conf_splice_round_keeps_its_payment_at_close() { assert_eq!(logs_a.count(ROUND_LOCKED), 1, "the promotion of the first round was not recorded"); let address = node_a.onchain_payment().new_address().unwrap(); - node_a.splice_out(&user_channel_id_a, node_b.node_id(), &address, 500_000).unwrap(); + node_a.splice_out(&user_channel_id_a, node_b.node_id(), &address, 500_000, None).unwrap(); let second = expect_splice_negotiated_event!(node_a, node_b.node_id()); wait_for_classified_funding_payment(&node_a, second.txid).await; expect_channel_ready_event!(node_a, node_b.node_id()); @@ -4224,7 +4357,7 @@ async fn splice_rounds_discarded_while_the_channel_is_listed_fail_at_close() { let user_channel_id_b = expect_channel_ready_event!(node_b, node_a.node_id()); // Node B contributes nothing to either round, so only node A hears of them. - node_a.splice_in_with_all(&user_channel_id_a, node_b.node_id()).unwrap(); + node_a.splice_in_with_all(&user_channel_id_a, node_b.node_id(), None).unwrap(); let first_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); let first_round = wait_for_transaction(&bitcoind, &electrsd, first_txo.txid).await; wait_for_classified_funding_payment(&node_a, first_txo.txid).await; @@ -4246,7 +4379,7 @@ async fn splice_rounds_discarded_while_the_channel_is_listed_fail_at_close() { // The bump pays little more than the first round, which bitcoind may refuse to replace for it: // have it replaced, so the mempool serves the bump. deprioritise_transaction(&bitcoind, first_txo.txid); - node_a.bump_channel_funding_fee(&user_channel_id_a, node_b.node_id()).unwrap(); + node_a.bump_channel_funding_fee(&user_channel_id_a, node_b.node_id(), None).unwrap(); let bump_txo = expect_splice_negotiated_event!(node_a, node_b.node_id()); assert_ne!(first_txo, bump_txo, "the bump produced the same funding"); wait_for_classified_funding_payment(&node_a, bump_txo.txid).await; @@ -5575,13 +5708,16 @@ async fn live_node_apis_return_not_running_after_stop() { node_a.open_channel(node_b_id, node_b_addr.clone(), 100_000, None, None, None), Err(NodeError::NotRunning) ); - assert_eq!(node_a.splice_in(&user_channel_id, node_b_id, 10_000), Err(NodeError::NotRunning)); assert_eq!( - node_a.splice_out(&user_channel_id, node_b_id, &btc_address, 10_000), + node_a.splice_in(&user_channel_id, node_b_id, 10_000, None), + Err(NodeError::NotRunning) + ); + assert_eq!( + node_a.splice_out(&user_channel_id, node_b_id, &btc_address, 10_000, None), Err(NodeError::NotRunning) ); assert_eq!( - node_a.bump_channel_funding_fee(&user_channel_id, node_b_id), + node_a.bump_channel_funding_fee(&user_channel_id, node_b_id, None), Err(NodeError::NotRunning) ); assert_eq!(node_a.close_channel(&user_channel_id, node_b_id), Err(NodeError::NotRunning));