Skip to content

Commit 3278ece

Browse files
authored
Merge pull request #4516 from wpaulino/funding-contribution-builder
Introduce FundingContributionBuilder API
2 parents 5079a0b + 9f9fe58 commit 3278ece

6 files changed

Lines changed: 1779 additions & 1190 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,7 +1512,6 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
15121512
counterparty_node_id: &PublicKey,
15131513
channel_id: &ChannelId,
15141514
wallet: &TestWalletSource,
1515-
logger: Arc<dyn Logger + MaybeSend + MaybeSync>,
15161515
funding_feerate_sat_per_kw: FeeRate| {
15171516
// We conditionally splice out `MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS` only when the node
15181517
// has double the balance required to send a payment upon a `0xff` byte. We do this to
@@ -1532,12 +1531,7 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
15321531
value: Amount::from_sat(MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS),
15331532
script_pubkey: wallet.get_change_script().unwrap(),
15341533
}];
1535-
funding_template.splice_out_sync(
1536-
outputs,
1537-
feerate,
1538-
FeeRate::MAX,
1539-
&WalletSync::new(wallet, logger.clone()),
1540-
)
1534+
funding_template.splice_out(outputs, feerate, FeeRate::MAX)
15411535
});
15421536
};
15431537

@@ -2479,39 +2473,35 @@ pub fn do_test<Out: Output + MaybeSend + MaybeSync>(data: &[u8], out: Out) {
24792473
}
24802474
let cp_node_id = nodes[1].get_our_node_id();
24812475
let wallet = &wallets[0];
2482-
let logger = Arc::clone(&loggers[0]);
24832476
let feerate_sat_per_kw = fee_estimators[0].feerate_sat_per_kw();
2484-
splice_out(&nodes[0], &cp_node_id, &chan_a_id, wallet, logger, feerate_sat_per_kw);
2477+
splice_out(&nodes[0], &cp_node_id, &chan_a_id, wallet, feerate_sat_per_kw);
24852478
},
24862479
0xa5 => {
24872480
if !cfg!(splicing) {
24882481
test_return!();
24892482
}
24902483
let cp_node_id = nodes[0].get_our_node_id();
24912484
let wallet = &wallets[1];
2492-
let logger = Arc::clone(&loggers[1]);
24932485
let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw();
2494-
splice_out(&nodes[1], &cp_node_id, &chan_a_id, wallet, logger, feerate_sat_per_kw);
2486+
splice_out(&nodes[1], &cp_node_id, &chan_a_id, wallet, feerate_sat_per_kw);
24952487
},
24962488
0xa6 => {
24972489
if !cfg!(splicing) {
24982490
test_return!();
24992491
}
25002492
let cp_node_id = nodes[2].get_our_node_id();
25012493
let wallet = &wallets[1];
2502-
let logger = Arc::clone(&loggers[1]);
25032494
let feerate_sat_per_kw = fee_estimators[1].feerate_sat_per_kw();
2504-
splice_out(&nodes[1], &cp_node_id, &chan_b_id, wallet, logger, feerate_sat_per_kw);
2495+
splice_out(&nodes[1], &cp_node_id, &chan_b_id, wallet, feerate_sat_per_kw);
25052496
},
25062497
0xa7 => {
25072498
if !cfg!(splicing) {
25082499
test_return!();
25092500
}
25102501
let cp_node_id = nodes[1].get_our_node_id();
25112502
let wallet = &wallets[2];
2512-
let logger = Arc::clone(&loggers[2]);
25132503
let feerate_sat_per_kw = fee_estimators[2].feerate_sat_per_kw();
2514-
splice_out(&nodes[2], &cp_node_id, &chan_b_id, wallet, logger, feerate_sat_per_kw);
2504+
splice_out(&nodes[2], &cp_node_id, &chan_b_id, wallet, feerate_sat_per_kw);
25152505
},
25162506

25172507
// Sync node by 1 block to cover confirmation of a transaction.

fuzz/src/full_stack.rs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,13 +1083,9 @@ pub fn do_test(mut data: &[u8], logger: &Arc<dyn Logger + MaybeSend + MaybeSync>
10831083
value: Amount::from_sat(splice_out_sats),
10841084
script_pubkey: wallet.get_change_script().unwrap(),
10851085
}];
1086-
let wallet_sync = WalletSync::new(&wallet, Arc::clone(&logger));
1087-
if let Ok(contribution) = funding_template.splice_out_sync(
1088-
outputs,
1089-
feerate,
1090-
FeeRate::MAX,
1091-
&wallet_sync,
1092-
) {
1086+
if let Ok(contribution) =
1087+
funding_template.splice_out(outputs, feerate, FeeRate::MAX)
1088+
{
10931089
let _ = channelmanager.funding_contributed(
10941090
&chan_id,
10951091
&counterparty,
@@ -1890,8 +1886,8 @@ fn splice_seed() -> Vec<u8> {
18901886
// CommitmentSigned message with proper signature (r=f7, s=01...) and funding_txid TLV
18911887
// signature r encodes sighash first byte f7, s follows the pattern from funding_created
18921888
// TLV type 1 (odd/optional) for funding_txid as per impl_writeable_msg!(CommitmentSigned, ...)
1893-
// Note: txid is encoded in reverse byte order (Bitcoin standard), so to get display 0000...0033, encode 3300...0000
1894-
ext_from_hex("0084 c000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000f7 0100000000000000000000000000000000000000000000000000000000000000 0000 01 20 3300000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);
1889+
// Note: txid is encoded in reverse byte order (Bitcoin standard), so to get display 0000...0031, encode 3100...0000
1890+
ext_from_hex("0084 c000000000000000000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000f7 0100000000000000000000000000000000000000000000000000000000000000 0000 01 20 3100000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);
18951891

18961892
// After commitment_signed exchange, we need to exchange tx_signatures.
18971893
// Message type IDs: TxSignatures = 71 (0x0047)
@@ -1904,19 +1900,19 @@ fn splice_seed() -> Vec<u8> {
19041900
// inbound read from peer id 0 of len 150 (134 message + 16 MAC)
19051901
ext_from_hex("030096", &mut test);
19061902
// TxSignatures message with shared_input_signature TLV (type 0)
1907-
// txid must match the splice funding txid (0x33 in reverse byte order)
1903+
// txid must match the splice funding txid (0x31 in reverse byte order)
19081904
// shared_input_signature: 64-byte fuzz signature for the shared input
1909-
ext_from_hex("0047 c000000000000000000000000000000000000000000000000000000000000000 3300000000000000000000000000000000000000000000000000000000000000 0000 00 40 00000000000000000000000000000000000000000000000000000000000000dc 0100000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);
1905+
ext_from_hex("0047 c000000000000000000000000000000000000000000000000000000000000000 3100000000000000000000000000000000000000000000000000000000000000 0000 00 40 00000000000000000000000000000000000000000000000000000000000000dc 0100000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);
19101906

19111907
// Connect a block with the splice funding transaction to confirm it
19121908
// The splice funding tx: version(4) + input_count(1) + txid(32) + vout(4) + script_len(1) + sequence(4)
19131909
// + output_count(1) + value(8) + script_len(1) + script(34) + locktime(4) = 94 bytes = 0x5e
19141910
// Transaction structure from FundingTransactionReadyForSigning:
19151911
// - Input: spending c000...00:0 with sequence 0xfffffffd
1916-
// - Output: 115536 sats to OP_0 PUSH32 6e00...00
1912+
// - Output: 115538 sats to OP_0 PUSH32 6e00...00
19171913
// - Locktime: 13
19181914
ext_from_hex("0c005e", &mut test);
1919-
ext_from_hex("02000000 01 c000000000000000000000000000000000000000000000000000000000000000 00000000 00 fdffffff 01 50c3010000000000 22 00206e00000000000000000000000000000000000000000000000000000000000000 0d000000", &mut test);
1915+
ext_from_hex("02000000 01 c000000000000000000000000000000000000000000000000000000000000000 00000000 00 fdffffff 01 52c3010000000000 22 00206e00000000000000000000000000000000000000000000000000000000000000 0d000000", &mut test);
19201916

19211917
// Connect additional blocks to reach minimum_depth confirmations
19221918
for _ in 0..5 {
@@ -1933,8 +1929,8 @@ fn splice_seed() -> Vec<u8> {
19331929
// inbound read from peer id 0 of len 82 (66 message + 16 MAC)
19341930
ext_from_hex("030052", &mut test);
19351931
// SpliceLocked message (type 77 = 0x004d): channel_id + splice_txid + mac
1936-
// splice_txid must match the splice funding txid (0x33 in reverse byte order)
1937-
ext_from_hex("004d c000000000000000000000000000000000000000000000000000000000000000 3300000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);
1932+
// splice_txid must match the splice funding txid (0x31 in reverse byte order)
1933+
ext_from_hex("004d c000000000000000000000000000000000000000000000000000000000000000 3100000000000000000000000000000000000000000000000000000000000000 03000000000000000000000000000000", &mut test);
19381934

19391935
test
19401936
}
@@ -2064,6 +2060,6 @@ mod tests {
20642060

20652061
// Splice locked
20662062
assert_eq!(log_entries.get(&("lightning::ln::peer_handler".to_string(), "Handling SendSpliceLocked event in peer_handler for node 030000000000000000000000000000000000000000000000000000000000000002 for channel c000000000000000000000000000000000000000000000000000000000000000".to_string())), Some(&1));
2067-
assert_eq!(log_entries.get(&("lightning::ln::channel".to_string(), "Promoting splice funding txid 0000000000000000000000000000000000000000000000000000000000000033".to_string())), Some(&1));
2063+
assert_eq!(log_entries.get(&("lightning::ln::channel".to_string(), "Promoting splice funding txid 0000000000000000000000000000000000000000000000000000000000000031".to_string())), Some(&1));
20682064
}
20692065
}

lightning/src/ln/channel.rs

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12360,7 +12360,25 @@ where
1236012360
);
1236112361
let min_rbf_feerate = prev_feerate.map(min_rbf_feerate);
1236212362
let prior = if pending_splice.last_funding_feerate_sat_per_1000_weight.is_some() {
12363-
self.build_prior_contribution()
12363+
if let Some(prior) = self
12364+
.pending_splice
12365+
.as_ref()
12366+
.and_then(|pending_splice| pending_splice.contributions.last())
12367+
{
12368+
let holder_balance = self
12369+
.get_holder_counterparty_balances_floor_incl_fee(&self.funding)
12370+
.map(|(h, _)| h)
12371+
.map_err(|e| APIError::ChannelUnavailable {
12372+
err: format!(
12373+
"Channel {} cannot be spliced at this time: {}",
12374+
self.context.channel_id(),
12375+
e
12376+
),
12377+
})?;
12378+
Some(PriorContribution::new(prior.clone(), holder_balance))
12379+
} else {
12380+
None
12381+
}
1236412382
} else {
1236512383
None
1236612384
};
@@ -12382,21 +12400,6 @@ where
1238212400
Ok(FundingTemplate::new(Some(shared_input), min_rbf_feerate, prior_contribution))
1238312401
}
1238412402

12385-
/// Clones the prior contribution and fetches the holder balance for deferred feerate
12386-
/// adjustment.
12387-
fn build_prior_contribution(&self) -> Option<PriorContribution> {
12388-
debug_assert!(
12389-
self.pending_splice.is_some(),
12390-
"build_prior_contribution requires pending_splice"
12391-
);
12392-
let prior = self.pending_splice.as_ref()?.contributions.last()?;
12393-
let holder_balance = self
12394-
.get_holder_counterparty_balances_floor_incl_fee(&self.funding)
12395-
.map(|(h, _)| h)
12396-
.ok();
12397-
Some(PriorContribution::new(prior.clone(), holder_balance))
12398-
}
12399-
1240012403
/// Returns whether this channel can ever RBF, independent of splice state.
1240112404
fn is_rbf_compatible(&self) -> Result<(), String> {
1240212405
if self.context.minimum_depth(&self.funding) == Some(0) {
@@ -12568,14 +12571,12 @@ where
1256812571
};
1256912572
}
1257012573

12571-
if let Err(e) = contribution.validate().and_then(|()| {
12572-
// For splice-out, our_funding_contribution is adjusted to cover fees if there
12573-
// aren't any inputs.
12574-
let our_funding_contribution = contribution.net_value();
12574+
let our_funding_contribution = contribution.net_value();
12575+
12576+
if let Err(e) =
1257512577
self.validate_splice_contributions(our_funding_contribution, SignedAmount::ZERO)
12576-
}) {
12578+
{
1257712579
log_error!(logger, "Channel {} cannot be funded: {}", self.context.channel_id(), e);
12578-
1257912580
return Err(QuiescentError::FailSplice(self.splice_funding_failed_for(contribution)));
1258012581
}
1258112582

@@ -14138,13 +14139,11 @@ where
1413814139
// funding_contributed and quiescence, reducing the holder's
1413914140
// balance. If invalid, disconnect and return the contribution so
1414014141
// the user can reclaim their inputs.
14141-
if let Err(e) = contribution.validate().and_then(|()| {
14142-
let our_funding_contribution = contribution.net_value();
14143-
self.validate_splice_contributions(
14144-
our_funding_contribution,
14145-
SignedAmount::ZERO,
14146-
)
14147-
}) {
14142+
let our_funding_contribution = contribution.net_value();
14143+
if let Err(e) = self.validate_splice_contributions(
14144+
our_funding_contribution,
14145+
SignedAmount::ZERO,
14146+
) {
1414814147
let failed = self.splice_funding_failed_for(contribution);
1414914148
return Err((
1415014149
ChannelError::WarnAndDisconnect(format!(

lightning/src/ln/channelmanager.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6733,20 +6733,32 @@ impl<
67336733
/// The splice initiator is responsible for paying fees for common fields, shared inputs, and
67346734
/// shared outputs along with any contributed inputs and outputs. When building a
67356735
/// [`FundingContribution`], fees are estimated at `min_feerate` assuming initiator
6736-
/// responsibility and must be covered by the supplied inputs for splice-in or the channel
6737-
/// balance for splice-out. If the counterparty also initiates a splice and wins the
6738-
/// tie-break, they become the initiator and choose the feerate. The fee is then
6739-
/// re-estimated at the counterparty's feerate for only our contributed inputs and outputs,
6740-
/// which may be higher or lower than the original estimate. The contribution is dropped and
6741-
/// the splice proceeds without it when:
6736+
/// responsibility. Contributions fall into two cases:
6737+
/// - **input-backed contributions**: when wallet inputs are selected, those inputs pay for both
6738+
/// the requested value added to the channel and any explicit withdrawal outputs. For
6739+
/// example, a 60,000 sat input might add 50,000 sat to the channel, pay a 2,000 sat fee,
6740+
/// and return 8,000 sat as change. A later RBF first tries to preserve that 50,000 sat
6741+
/// value added and cover any higher fee or newly requested withdrawal from the original
6742+
/// 10,000 sat fee buffer (2,000 sat fee + 8,000 sat change). If that buffer is not enough,
6743+
/// the prior contribution cannot be reused without selecting new wallet inputs.
6744+
/// - **input-less contributions**: when no wallet inputs are selected, fees and explicit
6745+
/// withdrawal outputs are paid from the channel balance. For example, a pure splice-out that
6746+
/// withdraws 20,000 sat from a 100,000 sat holder balance leaves up to 80,000 sat available
6747+
/// for fees. A later RBF keeps the 20,000 sat withdrawal only while that remaining balance
6748+
/// can still cover the re-estimated fee.
6749+
///
6750+
/// If the counterparty also initiates a splice and wins the tie-break, they become the
6751+
/// initiator and choose the feerate. The fee is then re-estimated at the counterparty's
6752+
/// feerate for only our contributed inputs and outputs, which may be higher or lower than the
6753+
/// original estimate. The contribution is dropped and the splice proceeds without it when:
67426754
/// - the counterparty's feerate is below `min_feerate`
67436755
/// - the counterparty's feerate is above `max_feerate` and the re-estimated fee exceeds the
67446756
/// original fee estimate
67456757
/// - the re-estimated fee exceeds the *fee buffer* regardless of `max_feerate`
67466758
///
67476759
/// The fee buffer is the maximum fee that can be accommodated:
6748-
/// - **splice-in**: the selected inputs' value minus the contributed amount
6749-
/// - **splice-out**: the channel balance minus the withdrawal outputs
6760+
/// - **input-backed contributions**: the original fee plus any change output value
6761+
/// - **input-less contributions**: the channel balance minus the withdrawal outputs
67506762
///
67516763
/// # Events
67526764
///

0 commit comments

Comments
 (0)