Skip to content

Commit 3245ebf

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 ae58a4f commit 3245ebf

File tree

4 files changed

+183
-74
lines changed

4 files changed

+183
-74
lines changed

lightning/src/ln/channel.rs

Lines changed: 144 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,18 +5879,60 @@ 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+
contribution.inputs(),
5891+
contribution.outputs(),
5892+
is_initiator,
5893+
true, // is_splice
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
#[allow(dead_code)] // TODO(dual_funding): TODO(splicing): Remove allow once used.
58845919
#[rustfmt::skip]
58855920
fn estimate_v2_funding_transaction_fee(
5886-
funding_inputs: &[FundingTxInput], is_initiator: bool, is_splice: bool,
5921+
funding_inputs: &[FundingTxInput], outputs: &[TxOut], is_initiator: bool, is_splice: bool,
58875922
funding_feerate_sat_per_1000_weight: u32,
58885923
) -> u64 {
5889-
let mut weight: u64 = funding_inputs
5924+
let input_weight: u64 = funding_inputs
58905925
.iter()
58915926
.map(|input| BASE_INPUT_WEIGHT.saturating_add(input.utxo.satisfaction_weight))
58925927
.fold(0, |total_weight, input_weight| total_weight.saturating_add(input_weight));
58935928

5929+
let output_weight: u64 = outputs
5930+
.iter()
5931+
.map(|txout| txout.weight().to_wu())
5932+
.fold(0, |total_weight, output_weight| total_weight.saturating_add(output_weight));
5933+
5934+
let mut weight = input_weight.saturating_add(output_weight);
5935+
58945936
// The initiator pays for all common fields and the shared output in the funding transaction.
58955937
if is_initiator {
58965938
weight = weight
@@ -5927,7 +5969,7 @@ fn check_v2_funding_inputs_sufficient(
59275969
is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
59285970
) -> Result<u64, ChannelError> {
59295971
let estimated_fee = estimate_v2_funding_transaction_fee(
5930-
funding_inputs, is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
5972+
funding_inputs, &[], is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
59315973
);
59325974

59335975
let mut total_input_sats = 0u64;
@@ -5975,6 +6017,9 @@ pub(super) struct FundingNegotiationContext {
59756017
/// The funding inputs we will be contributing to the channel.
59766018
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
59776019
pub our_funding_inputs: Vec<FundingTxInput>,
6020+
/// The funding outputs we will be contributing to the channel.
6021+
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
6022+
pub our_funding_outputs: Vec<TxOut>,
59786023
/// The change output script. This will be used if needed or -- if not set -- generated using
59796024
/// `SignerProvider::get_destination_script`.
59806025
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
@@ -6004,45 +6049,46 @@ impl FundingNegotiationContext {
60046049
debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
60056050
}
60066051

6007-
// Add output for funding tx
60086052
// Note: For the error case when the inputs are insufficient, it will be handled after
60096053
// the `calculate_change_output_value` call below
6010-
let mut funding_outputs = Vec::new();
60116054

60126055
let shared_funding_output = TxOut {
60136056
value: Amount::from_sat(funding.get_value_satoshis()),
60146057
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
60156058
};
60166059

60176060
// Optionally add change output
6018-
if self.our_funding_contribution > SignedAmount::ZERO {
6019-
let change_value_opt = calculate_change_output_value(
6061+
let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6062+
calculate_change_output_value(
60206063
&self,
60216064
self.shared_funding_input.is_some(),
60226065
&shared_funding_output.script_pubkey,
6023-
&funding_outputs,
60246066
context.holder_dust_limit_satoshis,
6025-
)?;
6026-
if let Some(change_value) = change_value_opt {
6027-
let change_script = if let Some(script) = self.change_script {
6028-
script
6029-
} else {
6030-
signer_provider
6031-
.get_destination_script(context.channel_keys_id)
6032-
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6033-
};
6034-
let mut change_output =
6035-
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
6036-
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6037-
let change_output_fee =
6038-
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6039-
let change_value_decreased_with_fee =
6040-
change_value.saturating_sub(change_output_fee);
6041-
// Check dust limit again
6042-
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6043-
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6044-
funding_outputs.push(change_output);
6045-
}
6067+
)?
6068+
} else {
6069+
None
6070+
};
6071+
6072+
let mut funding_outputs = self.our_funding_outputs;
6073+
6074+
if let Some(change_value) = change_value_opt {
6075+
let change_script = if let Some(script) = self.change_script {
6076+
script
6077+
} else {
6078+
signer_provider
6079+
.get_destination_script(context.channel_keys_id)
6080+
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6081+
};
6082+
let mut change_output =
6083+
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
6084+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6085+
let change_output_fee =
6086+
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6087+
let change_value_decreased_with_fee = change_value.saturating_sub(change_output_fee);
6088+
// Check dust limit again
6089+
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6090+
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6091+
funding_outputs.push(change_output);
60466092
}
60476093
}
60486094

@@ -10630,47 +10676,78 @@ where
1063010676
// TODO(splicing): check for quiescence
1063110677

1063210678
let our_funding_contribution = contribution.value();
10679+
if our_funding_contribution == SignedAmount::ZERO {
10680+
return Err(APIError::APIMisuseError {
10681+
err: format!(
10682+
"Channel {} cannot be spliced; contribution cannot be zero",
10683+
self.context.channel_id(),
10684+
),
10685+
});
10686+
}
10687+
1063310688
if our_funding_contribution > SignedAmount::MAX_MONEY {
1063410689
return Err(APIError::APIMisuseError {
1063510690
err: format!(
10636-
"Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10691+
"Channel {} cannot be spliced in; contribution exceeds total bitcoin supply: {}",
1063710692
self.context.channel_id(),
1063810693
our_funding_contribution,
1063910694
),
1064010695
});
1064110696
}
1064210697

10643-
if our_funding_contribution < SignedAmount::ZERO {
10698+
if our_funding_contribution < -SignedAmount::MAX_MONEY {
1064410699
return Err(APIError::APIMisuseError {
1064510700
err: format!(
10646-
"TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10647-
self.context.channel_id(), our_funding_contribution,
10648-
),
10701+
"Channel {} cannot be spliced out; contribution exhausts total bitcoin supply: {}",
10702+
self.context.channel_id(),
10703+
our_funding_contribution,
10704+
),
1064910705
});
1065010706
}
1065110707

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

10658-
// Check that inputs are sufficient to cover our contribution.
10659-
let _fee = check_v2_funding_inputs_sufficient(
10660-
our_funding_contribution.to_sat(),
10661-
contribution.inputs(),
10662-
true,
10663-
true,
10664-
funding_feerate_per_kw,
10711+
let channel_balance = Amount::from_sat(self.funding.get_value_to_self_msat() / 1000);
10712+
let fees = check_splice_contribution_sufficient(
10713+
channel_balance,
10714+
&contribution,
10715+
true, // is_initiator
10716+
FeeRate::from_sat_per_kwu(funding_feerate_per_kw as u64),
1066510717
)
10666-
.map_err(|err| APIError::APIMisuseError {
10667-
err: format!(
10668-
"Insufficient inputs for splicing; channel ID {}, err {}",
10669-
self.context.channel_id(),
10670-
err,
10671-
),
10718+
.map_err(|e| {
10719+
let splice_type = if our_funding_contribution < SignedAmount::ZERO {
10720+
"spliced out"
10721+
} else {
10722+
"spliced in"
10723+
};
10724+
APIError::APIMisuseError {
10725+
err: format!(
10726+
"Channel {} cannot be {}; {}",
10727+
self.context.channel_id(),
10728+
splice_type,
10729+
e,
10730+
),
10731+
}
1067210732
})?;
1067310733

10734+
// Fees for splice-out are paid from the channel balance whereas fees for splice-in are paid
10735+
// by the funding inputs.
10736+
let adjusted_funding_contribution = if our_funding_contribution < SignedAmount::ZERO {
10737+
let adjusted_funding_contribution = our_funding_contribution
10738+
- fees.to_signed().expect("fees should never exceed Amount::MAX_MONEY");
10739+
10740+
// TODO(splicing): Check that channel balance does not go below the channel reserve
10741+
let _post_channel_balance = AddSigned::checked_add_signed(
10742+
channel_balance.to_sat(),
10743+
adjusted_funding_contribution.to_sat(),
10744+
);
10745+
10746+
adjusted_funding_contribution
10747+
} else {
10748+
our_funding_contribution
10749+
};
10750+
1067410751
for FundingTxInput { utxo, prevtx, .. } in contribution.inputs().iter() {
1067510752
const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
1067610753
channel_id: ChannelId([0; 32]),
@@ -10693,14 +10770,15 @@ where
1069310770
}
1069410771

1069510772
let prev_funding_input = self.funding.to_splice_funding_input();
10696-
let (our_funding_inputs, change_script) = contribution.into_tx_parts();
10773+
let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();
1069710774
let funding_negotiation_context = FundingNegotiationContext {
1069810775
is_initiator: true,
10699-
our_funding_contribution,
10776+
our_funding_contribution: adjusted_funding_contribution,
1070010777
funding_tx_locktime: LockTime::from_consensus(locktime),
1070110778
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
1070210779
shared_funding_input: Some(prev_funding_input),
1070310780
our_funding_inputs,
10781+
our_funding_outputs,
1070410782
change_script,
1070510783
};
1070610784

@@ -10716,7 +10794,7 @@ where
1071610794

1071710795
Ok(msgs::SpliceInit {
1071810796
channel_id: self.context.channel_id,
10719-
funding_contribution_satoshis: our_funding_contribution.to_sat(),
10797+
funding_contribution_satoshis: adjusted_funding_contribution.to_sat(),
1072010798
funding_feerate_per_kw,
1072110799
locktime,
1072210800
funding_pubkey,
@@ -10825,6 +10903,7 @@ where
1082510903
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
1082610904
shared_funding_input: Some(prev_funding_input),
1082710905
our_funding_inputs: Vec::new(),
10906+
our_funding_outputs: Vec::new(),
1082810907
change_script: None,
1082910908
};
1083010909

@@ -12523,6 +12602,7 @@ where
1252312602
funding_feerate_sat_per_1000_weight,
1252412603
shared_funding_input: None,
1252512604
our_funding_inputs: funding_inputs,
12605+
our_funding_outputs: Vec::new(),
1252612606
change_script: None,
1252712607
};
1252812608
let chan = Self {
@@ -12677,6 +12757,7 @@ where
1267712757
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
1267812758
shared_funding_input: None,
1267912759
our_funding_inputs: our_funding_inputs.clone(),
12760+
our_funding_outputs: Vec::new(),
1268012761
change_script: None,
1268112762
};
1268212763
let shared_funding_output = TxOut {
@@ -12702,7 +12783,7 @@ where
1270212783
inputs_to_contribute,
1270312784
shared_funding_input: None,
1270412785
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats),
12705-
outputs_to_contribute: Vec::new(),
12786+
outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(),
1270612787
}
1270712788
).map_err(|err| {
1270812789
let reason = ClosureReason::ProcessingError { err: err.to_string() };
@@ -15870,43 +15951,43 @@ mod tests {
1587015951

1587115952
// 2 inputs, initiator, 2000 sat/kw feerate
1587215953
assert_eq!(
15873-
estimate_v2_funding_transaction_fee(&two_inputs, true, false, 2000),
15954+
estimate_v2_funding_transaction_fee(&two_inputs, &[], true, false, 2000),
1587415955
1520,
1587515956
);
1587615957

1587715958
// higher feerate
1587815959
assert_eq!(
15879-
estimate_v2_funding_transaction_fee(&two_inputs, true, false, 3000),
15960+
estimate_v2_funding_transaction_fee(&two_inputs, &[], true, false, 3000),
1588015961
2280,
1588115962
);
1588215963

1588315964
// only 1 input
1588415965
assert_eq!(
15885-
estimate_v2_funding_transaction_fee(&one_input, true, false, 2000),
15966+
estimate_v2_funding_transaction_fee(&one_input, &[], true, false, 2000),
1588615967
974,
1588715968
);
1588815969

1588915970
// 0 inputs
1589015971
assert_eq!(
15891-
estimate_v2_funding_transaction_fee(&[], true, false, 2000),
15972+
estimate_v2_funding_transaction_fee(&[], &[], true, false, 2000),
1589215973
428,
1589315974
);
1589415975

1589515976
// not initiator
1589615977
assert_eq!(
15897-
estimate_v2_funding_transaction_fee(&[], false, false, 2000),
15978+
estimate_v2_funding_transaction_fee(&[], &[], false, false, 2000),
1589815979
0,
1589915980
);
1590015981

1590115982
// splice initiator
1590215983
assert_eq!(
15903-
estimate_v2_funding_transaction_fee(&one_input, true, true, 2000),
15984+
estimate_v2_funding_transaction_fee(&one_input, &[], true, true, 2000),
1590415985
1746,
1590515986
);
1590615987

1590715988
// splice acceptor
1590815989
assert_eq!(
15909-
estimate_v2_funding_transaction_fee(&one_input, false, true, 2000),
15990+
estimate_v2_funding_transaction_fee(&one_input, &[], false, true, 2000),
1591015991
546,
1591115992
);
1591215993
}

0 commit comments

Comments
 (0)