Skip to content

Commit b79be98

Browse files
committed
Add splice-out support
Update SpliceContribution with a variant used to support splice-out (i.e., removing funds from a channel). The TxOut values must not exceed the users channel balance after accounting for fees and the reserve requirement.
1 parent 25dc13a commit b79be98

File tree

4 files changed

+165
-74
lines changed

4 files changed

+165
-74
lines changed

lightning/src/ln/channel.rs

Lines changed: 126 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ use bitcoin::hashes::Hash;
2424
use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
2525
use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1};
2626
use bitcoin::secp256k1::{PublicKey, SecretKey};
27-
#[cfg(splicing)]
28-
use bitcoin::Sequence;
2927
use bitcoin::{secp256k1, sighash, TxIn};
28+
#[cfg(splicing)]
29+
use bitcoin::{FeeRate, Sequence};
3030

3131
use crate::chain::chaininterface::{
3232
fee_for_weight, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator,
@@ -5879,20 +5879,53 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
58795879
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
58805880
}
58815881

5882+
#[cfg(splicing)]
5883+
fn check_splice_contribution_sufficient(
5884+
channel_balance: Amount, contribution: &SpliceContribution, is_initiator: bool,
5885+
funding_feerate: FeeRate,
5886+
) -> Result<Amount, ChannelError> {
5887+
let contribution_amount = contribution.value();
5888+
if contribution_amount < SignedAmount::ZERO {
5889+
let estimated_fee = Amount::from_sat(estimate_v2_funding_transaction_fee(
5890+
is_initiator,
5891+
1, // spends the previous funding output
5892+
Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT),
5893+
contribution.outputs(),
5894+
funding_feerate.to_sat_per_kwu() as u32,
5895+
));
5896+
5897+
if channel_balance > contribution_amount.unsigned_abs() + estimated_fee {
5898+
Ok(estimated_fee)
5899+
} else {
5900+
Err(ChannelError::Warn(format!(
5901+
"Available channel balance {} is lower than needed for splicing out {}, considering fees of {}",
5902+
channel_balance, contribution_amount.unsigned_abs(), estimated_fee,
5903+
)))
5904+
}
5905+
} else {
5906+
check_v2_funding_inputs_sufficient(
5907+
contribution_amount.to_sat(),
5908+
contribution.inputs(),
5909+
is_initiator,
5910+
true,
5911+
funding_feerate.to_sat_per_kwu() as u32,
5912+
)
5913+
.map(Amount::from_sat)
5914+
}
5915+
}
5916+
58825917
/// Estimate our part of the fee of the new funding transaction.
58835918
/// input_count: Number of contributed inputs.
58845919
/// witness_weight: The witness weight for contributed inputs.
58855920
#[allow(dead_code)] // TODO(dual_funding): TODO(splicing): Remove allow once used.
58865921
#[rustfmt::skip]
58875922
fn estimate_v2_funding_transaction_fee(
5888-
is_initiator: bool, input_count: usize, witness_weight: Weight,
5923+
is_initiator: bool, input_count: usize, witness_weight: Weight, outputs: &[TxOut],
58895924
funding_feerate_sat_per_1000_weight: u32,
58905925
) -> u64 {
5891-
// Inputs
58925926
let mut weight = (input_count as u64) * BASE_INPUT_WEIGHT;
5893-
5894-
// Witnesses
58955927
weight = weight.saturating_add(witness_weight.to_wu());
5928+
weight = weight.saturating_add(outputs.iter().map(|txout| txout.weight().to_wu()).sum());
58965929

58975930
// If we are the initiator, we must pay for weight of all common fields in the funding transaction.
58985931
if is_initiator {
@@ -5930,7 +5963,7 @@ fn check_v2_funding_inputs_sufficient(
59305963
funding_inputs_len += 1;
59315964
total_input_satisfaction_weight += Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT);
59325965
}
5933-
let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs_len, total_input_satisfaction_weight, funding_feerate_sat_per_1000_weight);
5966+
let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs_len, total_input_satisfaction_weight, &[], funding_feerate_sat_per_1000_weight);
59345967

59355968
let mut total_input_sats = 0u64;
59365969
for FundingTxInput { utxo, .. } in funding_inputs.iter() {
@@ -5977,6 +6010,9 @@ pub(super) struct FundingNegotiationContext {
59776010
/// The funding inputs we will be contributing to the channel.
59786011
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
59796012
pub our_funding_inputs: Vec<FundingTxInput>,
6013+
/// The funding outputs we will be contributing to the channel.
6014+
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
6015+
pub our_funding_outputs: Vec<TxOut>,
59806016
/// The change output script. This will be used if needed or -- if not set -- generated using
59816017
/// `SignerProvider::get_destination_script`.
59826018
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
@@ -6006,45 +6042,46 @@ impl FundingNegotiationContext {
60066042
debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
60076043
}
60086044

6009-
// Add output for funding tx
60106045
// Note: For the error case when the inputs are insufficient, it will be handled after
60116046
// the `calculate_change_output_value` call below
6012-
let mut funding_outputs = Vec::new();
60136047

60146048
let shared_funding_output = TxOut {
60156049
value: Amount::from_sat(funding.get_value_satoshis()),
60166050
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
60176051
};
60186052

60196053
// Optionally add change output
6020-
if self.our_funding_contribution > SignedAmount::ZERO {
6021-
let change_value_opt = calculate_change_output_value(
6054+
let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6055+
calculate_change_output_value(
60226056
&self,
60236057
self.shared_funding_input.is_some(),
60246058
&shared_funding_output.script_pubkey,
6025-
&funding_outputs,
60266059
context.holder_dust_limit_satoshis,
6027-
)?;
6028-
if let Some(change_value) = change_value_opt {
6029-
let change_script = if let Some(script) = self.change_script {
6030-
script
6031-
} else {
6032-
signer_provider
6033-
.get_destination_script(context.channel_keys_id)
6034-
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6035-
};
6036-
let mut change_output =
6037-
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
6038-
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6039-
let change_output_fee =
6040-
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6041-
let change_value_decreased_with_fee =
6042-
change_value.saturating_sub(change_output_fee);
6043-
// Check dust limit again
6044-
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6045-
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6046-
funding_outputs.push(change_output);
6047-
}
6060+
)?
6061+
} else {
6062+
None
6063+
};
6064+
6065+
let mut funding_outputs = self.our_funding_outputs;
6066+
6067+
if let Some(change_value) = change_value_opt {
6068+
let change_script = if let Some(script) = self.change_script {
6069+
script
6070+
} else {
6071+
signer_provider
6072+
.get_destination_script(context.channel_keys_id)
6073+
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6074+
};
6075+
let mut change_output =
6076+
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
6077+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6078+
let change_output_fee =
6079+
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6080+
let change_value_decreased_with_fee = change_value.saturating_sub(change_output_fee);
6081+
// Check dust limit again
6082+
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6083+
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6084+
funding_outputs.push(change_output);
60486085
}
60496086
}
60506087

@@ -10635,44 +10672,66 @@ where
1063510672
if our_funding_contribution > SignedAmount::MAX_MONEY {
1063610673
return Err(APIError::APIMisuseError {
1063710674
err: format!(
10638-
"Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10675+
"Channel {} cannot be spliced in; contribution exceeds total bitcoin supply: {}",
1063910676
self.context.channel_id(),
1064010677
our_funding_contribution,
1064110678
),
1064210679
});
1064310680
}
1064410681

10645-
if our_funding_contribution < SignedAmount::ZERO {
10682+
if our_funding_contribution < -SignedAmount::MAX_MONEY {
1064610683
return Err(APIError::APIMisuseError {
1064710684
err: format!(
10648-
"TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10649-
self.context.channel_id(), our_funding_contribution,
10650-
),
10685+
"Channel {} cannot be spliced out; contribution exhausts total bitcoin supply: {}",
10686+
self.context.channel_id(),
10687+
our_funding_contribution,
10688+
),
1065110689
});
1065210690
}
1065310691

10654-
// TODO(splicing): Once splice-out is supported, check that channel balance does not go below 0
10655-
// (or below channel reserve)
10656-
1065710692
// Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
1065810693
// (Cannot test for miminum required post-splice channel value)
1065910694

10660-
// Check that inputs are sufficient to cover our contribution.
10661-
let _fee = check_v2_funding_inputs_sufficient(
10662-
our_funding_contribution.to_sat(),
10663-
contribution.inputs(),
10664-
true,
10665-
true,
10666-
funding_feerate_per_kw,
10695+
let channel_balance = Amount::from_sat(self.funding.get_value_to_self_msat() / 1000);
10696+
let fees = check_splice_contribution_sufficient(
10697+
channel_balance,
10698+
&contribution,
10699+
true, // is_initiator
10700+
FeeRate::from_sat_per_kwu(funding_feerate_per_kw as u64),
1066710701
)
10668-
.map_err(|err| APIError::APIMisuseError {
10669-
err: format!(
10670-
"Insufficient inputs for splicing; channel ID {}, err {}",
10671-
self.context.channel_id(),
10672-
err,
10673-
),
10702+
.map_err(|e| {
10703+
let splice_type = if our_funding_contribution < SignedAmount::ZERO {
10704+
"spliced out"
10705+
} else {
10706+
"spliced in"
10707+
};
10708+
APIError::APIMisuseError {
10709+
err: format!(
10710+
"Channel {} cannot be {}; {}",
10711+
self.context.channel_id(),
10712+
splice_type,
10713+
e,
10714+
),
10715+
}
1067410716
})?;
1067510717

10718+
// Fees for splice-out are paid from the channel balance whereas fees for splice-in are paid
10719+
// by the funding inputs.
10720+
let adjusted_funding_contribution = if our_funding_contribution < SignedAmount::ZERO {
10721+
let adjusted_funding_contribution = our_funding_contribution
10722+
- fees.to_signed().expect("fees should never exceed splice-out value");
10723+
10724+
// TODO(splicing): Check that channel balance does not go below the channel reserve
10725+
let _post_channel_balance = AddSigned::checked_add_signed(
10726+
channel_balance.to_sat(),
10727+
adjusted_funding_contribution.to_sat(),
10728+
);
10729+
10730+
adjusted_funding_contribution
10731+
} else {
10732+
our_funding_contribution
10733+
};
10734+
1067610735
for FundingTxInput { utxo, prevtx, .. } in contribution.inputs().iter() {
1067710736
const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
1067810737
channel_id: ChannelId([0; 32]),
@@ -10694,14 +10753,15 @@ where
1069410753
}
1069510754

1069610755
let prev_funding_input = self.funding.to_splice_funding_input();
10697-
let (our_funding_inputs, change_script) = contribution.into_tx_parts();
10756+
let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();
1069810757
let funding_negotiation_context = FundingNegotiationContext {
1069910758
is_initiator: true,
10700-
our_funding_contribution,
10759+
our_funding_contribution: adjusted_funding_contribution,
1070110760
funding_tx_locktime: LockTime::from_consensus(locktime),
1070210761
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
1070310762
shared_funding_input: Some(prev_funding_input),
1070410763
our_funding_inputs,
10764+
our_funding_outputs,
1070510765
change_script,
1070610766
};
1070710767

@@ -10717,7 +10777,7 @@ where
1071710777

1071810778
Ok(msgs::SpliceInit {
1071910779
channel_id: self.context.channel_id,
10720-
funding_contribution_satoshis: our_funding_contribution.to_sat(),
10780+
funding_contribution_satoshis: adjusted_funding_contribution.to_sat(),
1072110781
funding_feerate_per_kw,
1072210782
locktime,
1072310783
funding_pubkey,
@@ -10826,6 +10886,7 @@ where
1082610886
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
1082710887
shared_funding_input: Some(prev_funding_input),
1082810888
our_funding_inputs: Vec::new(),
10889+
our_funding_outputs: Vec::new(),
1082910890
change_script: None,
1083010891
};
1083110892

@@ -12524,6 +12585,7 @@ where
1252412585
funding_feerate_sat_per_1000_weight,
1252512586
shared_funding_input: None,
1252612587
our_funding_inputs: funding_inputs,
12588+
our_funding_outputs: Vec::new(),
1252712589
change_script: None,
1252812590
};
1252912591
let chan = Self {
@@ -12678,6 +12740,7 @@ where
1267812740
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
1267912741
shared_funding_input: None,
1268012742
our_funding_inputs: our_funding_inputs.clone(),
12743+
our_funding_outputs: Vec::new(),
1268112744
change_script: None,
1268212745
};
1268312746
let shared_funding_output = TxOut {
@@ -12703,7 +12766,7 @@ where
1270312766
inputs_to_contribute,
1270412767
shared_funding_input: None,
1270512768
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats),
12706-
outputs_to_contribute: Vec::new(),
12769+
outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(),
1270712770
}
1270812771
).map_err(|err| {
1270912772
let reason = ClosureReason::ProcessingError { err: err.to_string() };
@@ -15872,31 +15935,31 @@ mod tests {
1587215935

1587315936
// 2 inputs with weight 300, initiator, 2000 sat/kw feerate
1587415937
assert_eq!(
15875-
estimate_v2_funding_transaction_fee(true, 2, Weight::from_wu(300), 2000),
15938+
estimate_v2_funding_transaction_fee(true, 2, Weight::from_wu(300), &[], 2000),
1587615939
1668
1587715940
);
1587815941

1587915942
// higher feerate
1588015943
assert_eq!(
15881-
estimate_v2_funding_transaction_fee(true, 2, Weight::from_wu(300), 3000),
15944+
estimate_v2_funding_transaction_fee(true, 2, Weight::from_wu(300), &[], 3000),
1588215945
2502
1588315946
);
1588415947

1588515948
// only 1 input
1588615949
assert_eq!(
15887-
estimate_v2_funding_transaction_fee(true, 1, Weight::from_wu(300), 2000),
15950+
estimate_v2_funding_transaction_fee(true, 1, Weight::from_wu(300), &[], 2000),
1588815951
1348
1588915952
);
1589015953

1589115954
// 0 input weight
1589215955
assert_eq!(
15893-
estimate_v2_funding_transaction_fee(true, 1, Weight::from_wu(0), 2000),
15956+
estimate_v2_funding_transaction_fee(true, 1, Weight::from_wu(0), &[], 2000),
1589415957
748
1589515958
);
1589615959

1589715960
// not initiator
1589815961
assert_eq!(
15899-
estimate_v2_funding_transaction_fee(false, 1, Weight::from_wu(0), 2000),
15962+
estimate_v2_funding_transaction_fee(false, 1, Weight::from_wu(0), &[], 2000),
1590015963
320
1590115964
);
1590215965
}

0 commit comments

Comments
 (0)