Skip to content

Commit 418cb3b

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 7b4590d commit 418cb3b

4 files changed

Lines changed: 174 additions & 74 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 135 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,
@@ -5880,20 +5880,62 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
58805880
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
58815881
}
58825882

5883+
#[cfg(splicing)]
5884+
fn check_splice_contribution_sufficient(
5885+
channel_balance: Amount, contribution: &SpliceContribution, is_initiator: bool,
5886+
funding_feerate: FeeRate,
5887+
) -> Result<Amount, ChannelError> {
5888+
let contribution_amount = contribution.value();
5889+
if contribution_amount < SignedAmount::ZERO {
5890+
let estimated_fee = Amount::from_sat(estimate_v2_funding_transaction_fee(
5891+
contribution.inputs(),
5892+
contribution.outputs(),
5893+
is_initiator,
5894+
true, // is_splice
5895+
funding_feerate.to_sat_per_kwu() as u32,
5896+
));
5897+
5898+
if channel_balance > contribution_amount.unsigned_abs() + estimated_fee {
5899+
Ok(estimated_fee)
5900+
} else {
5901+
Err(ChannelError::Warn(format!(
5902+
"Available channel balance {} is lower than needed for splicing out {}, considering fees of {}",
5903+
channel_balance, contribution_amount.unsigned_abs(), estimated_fee,
5904+
)))
5905+
}
5906+
} else {
5907+
check_v2_funding_inputs_sufficient(
5908+
contribution_amount.to_sat(),
5909+
contribution.inputs(),
5910+
is_initiator,
5911+
true,
5912+
funding_feerate.to_sat_per_kwu() as u32,
5913+
)
5914+
.map(Amount::from_sat)
5915+
}
5916+
}
5917+
58835918
/// Estimate our part of the fee of the new funding transaction.
58845919
/// input_count: Number of contributed inputs.
58855920
/// witness_weight: The witness weight for contributed inputs.
58865921
#[allow(dead_code)] // TODO(dual_funding): TODO(splicing): Remove allow once used.
58875922
#[rustfmt::skip]
58885923
fn estimate_v2_funding_transaction_fee(
5889-
funding_inputs: &[FundingTxInput], is_initiator: bool, is_splice: bool,
5924+
funding_inputs: &[FundingTxInput], outputs: &[TxOut], is_initiator: bool, is_splice: bool,
58905925
funding_feerate_sat_per_1000_weight: u32,
58915926
) -> u64 {
5892-
let mut weight: u64 = funding_inputs
5927+
let input_weight: u64 = funding_inputs
58935928
.iter()
58945929
.map(|input| BASE_INPUT_WEIGHT.saturating_add(input.utxo.satisfaction_weight))
58955930
.fold(0, |total_weight, input_weight| total_weight.saturating_add(input_weight));
58965931

5932+
let output_weight: u64 = outputs
5933+
.iter()
5934+
.map(|txout| txout.weight().to_wu())
5935+
.fold(0, |total_weight, output_weight| total_weight.saturating_add(output_weight));
5936+
5937+
let mut weight = input_weight.saturating_add(output_weight);
5938+
58975939
// The initiator pays for all common fields and the shared output in the funding transaction.
58985940
if is_initiator {
58995941
weight = weight
@@ -5930,7 +5972,7 @@ fn check_v2_funding_inputs_sufficient(
59305972
is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
59315973
) -> Result<u64, ChannelError> {
59325974
let estimated_fee = estimate_v2_funding_transaction_fee(
5933-
funding_inputs, is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
5975+
funding_inputs, &[], is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
59345976
);
59355977

59365978
let mut total_input_sats = 0u64;
@@ -5978,6 +6020,9 @@ pub(super) struct FundingNegotiationContext {
59786020
/// The funding inputs we will be contributing to the channel.
59796021
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
59806022
pub our_funding_inputs: Vec<FundingTxInput>,
6023+
/// The funding outputs we will be contributing to the channel.
6024+
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
6025+
pub our_funding_outputs: Vec<TxOut>,
59816026
/// The change output script. This will be used if needed or -- if not set -- generated using
59826027
/// `SignerProvider::get_destination_script`.
59836028
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
@@ -6007,45 +6052,46 @@ impl FundingNegotiationContext {
60076052
debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
60086053
}
60096054

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

60156058
let shared_funding_output = TxOut {
60166059
value: Amount::from_sat(funding.get_value_satoshis()),
60176060
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
60186061
};
60196062

60206063
// Optionally add change output
6021-
if self.our_funding_contribution > SignedAmount::ZERO {
6022-
let change_value_opt = calculate_change_output_value(
6064+
let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6065+
calculate_change_output_value(
60236066
&self,
60246067
self.shared_funding_input.is_some(),
60256068
&shared_funding_output.script_pubkey,
6026-
&funding_outputs,
60276069
context.holder_dust_limit_satoshis,
6028-
)?;
6029-
if let Some(change_value) = change_value_opt {
6030-
let change_script = if let Some(script) = self.change_script {
6031-
script
6032-
} else {
6033-
signer_provider
6034-
.get_destination_script(context.channel_keys_id)
6035-
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6036-
};
6037-
let mut change_output =
6038-
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
6039-
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6040-
let change_output_fee =
6041-
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6042-
let change_value_decreased_with_fee =
6043-
change_value.saturating_sub(change_output_fee);
6044-
// Check dust limit again
6045-
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6046-
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6047-
funding_outputs.push(change_output);
6048-
}
6070+
)?
6071+
} else {
6072+
None
6073+
};
6074+
6075+
let mut funding_outputs = self.our_funding_outputs;
6076+
6077+
if let Some(change_value) = change_value_opt {
6078+
let change_script = if let Some(script) = self.change_script {
6079+
script
6080+
} else {
6081+
signer_provider
6082+
.get_destination_script(context.channel_keys_id)
6083+
.map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6084+
};
6085+
let mut change_output =
6086+
TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
6087+
let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6088+
let change_output_fee =
6089+
fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6090+
let change_value_decreased_with_fee = change_value.saturating_sub(change_output_fee);
6091+
// Check dust limit again
6092+
if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6093+
change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6094+
funding_outputs.push(change_output);
60496095
}
60506096
}
60516097

@@ -10636,44 +10682,66 @@ where
1063610682
if our_funding_contribution > SignedAmount::MAX_MONEY {
1063710683
return Err(APIError::APIMisuseError {
1063810684
err: format!(
10639-
"Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10685+
"Channel {} cannot be spliced in; contribution exceeds total bitcoin supply: {}",
1064010686
self.context.channel_id(),
1064110687
our_funding_contribution,
1064210688
),
1064310689
});
1064410690
}
1064510691

10646-
if our_funding_contribution < SignedAmount::ZERO {
10692+
if our_funding_contribution < -SignedAmount::MAX_MONEY {
1064710693
return Err(APIError::APIMisuseError {
1064810694
err: format!(
10649-
"TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10650-
self.context.channel_id(), our_funding_contribution,
10651-
),
10695+
"Channel {} cannot be spliced out; contribution exhausts total bitcoin supply: {}",
10696+
self.context.channel_id(),
10697+
our_funding_contribution,
10698+
),
1065210699
});
1065310700
}
1065410701

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

10661-
// Check that inputs are sufficient to cover our contribution.
10662-
let _fee = check_v2_funding_inputs_sufficient(
10663-
our_funding_contribution.to_sat(),
10664-
contribution.inputs(),
10665-
true,
10666-
true,
10667-
funding_feerate_per_kw,
10705+
let channel_balance = Amount::from_sat(self.funding.get_value_to_self_msat() / 1000);
10706+
let fees = check_splice_contribution_sufficient(
10707+
channel_balance,
10708+
&contribution,
10709+
true, // is_initiator
10710+
FeeRate::from_sat_per_kwu(funding_feerate_per_kw as u64),
1066810711
)
10669-
.map_err(|err| APIError::APIMisuseError {
10670-
err: format!(
10671-
"Insufficient inputs for splicing; channel ID {}, err {}",
10672-
self.context.channel_id(),
10673-
err,
10674-
),
10712+
.map_err(|e| {
10713+
let splice_type = if our_funding_contribution < SignedAmount::ZERO {
10714+
"spliced out"
10715+
} else {
10716+
"spliced in"
10717+
};
10718+
APIError::APIMisuseError {
10719+
err: format!(
10720+
"Channel {} cannot be {}; {}",
10721+
self.context.channel_id(),
10722+
splice_type,
10723+
e,
10724+
),
10725+
}
1067510726
})?;
1067610727

10728+
// Fees for splice-out are paid from the channel balance whereas fees for splice-in are paid
10729+
// by the funding inputs.
10730+
let adjusted_funding_contribution = if our_funding_contribution < SignedAmount::ZERO {
10731+
let adjusted_funding_contribution = our_funding_contribution
10732+
- fees.to_signed().expect("fees should never exceed splice-out value");
10733+
10734+
// TODO(splicing): Check that channel balance does not go below the channel reserve
10735+
let _post_channel_balance = AddSigned::checked_add_signed(
10736+
channel_balance.to_sat(),
10737+
adjusted_funding_contribution.to_sat(),
10738+
);
10739+
10740+
adjusted_funding_contribution
10741+
} else {
10742+
our_funding_contribution
10743+
};
10744+
1067710745
for FundingTxInput { utxo, prevtx, .. } in contribution.inputs().iter() {
1067810746
const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
1067910747
channel_id: ChannelId([0; 32]),
@@ -10696,14 +10764,15 @@ where
1069610764
}
1069710765

1069810766
let prev_funding_input = self.funding.to_splice_funding_input();
10699-
let (our_funding_inputs, change_script) = contribution.into_tx_parts();
10767+
let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();
1070010768
let funding_negotiation_context = FundingNegotiationContext {
1070110769
is_initiator: true,
10702-
our_funding_contribution,
10770+
our_funding_contribution: adjusted_funding_contribution,
1070310771
funding_tx_locktime: LockTime::from_consensus(locktime),
1070410772
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
1070510773
shared_funding_input: Some(prev_funding_input),
1070610774
our_funding_inputs,
10775+
our_funding_outputs,
1070710776
change_script,
1070810777
};
1070910778

@@ -10719,7 +10788,7 @@ where
1071910788

1072010789
Ok(msgs::SpliceInit {
1072110790
channel_id: self.context.channel_id,
10722-
funding_contribution_satoshis: our_funding_contribution.to_sat(),
10791+
funding_contribution_satoshis: adjusted_funding_contribution.to_sat(),
1072310792
funding_feerate_per_kw,
1072410793
locktime,
1072510794
funding_pubkey,
@@ -10828,6 +10897,7 @@ where
1082810897
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
1082910898
shared_funding_input: Some(prev_funding_input),
1083010899
our_funding_inputs: Vec::new(),
10900+
our_funding_outputs: Vec::new(),
1083110901
change_script: None,
1083210902
};
1083310903

@@ -12526,6 +12596,7 @@ where
1252612596
funding_feerate_sat_per_1000_weight,
1252712597
shared_funding_input: None,
1252812598
our_funding_inputs: funding_inputs,
12599+
our_funding_outputs: Vec::new(),
1252912600
change_script: None,
1253012601
};
1253112602
let chan = Self {
@@ -12680,6 +12751,7 @@ where
1268012751
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
1268112752
shared_funding_input: None,
1268212753
our_funding_inputs: our_funding_inputs.clone(),
12754+
our_funding_outputs: Vec::new(),
1268312755
change_script: None,
1268412756
};
1268512757
let shared_funding_output = TxOut {
@@ -12705,7 +12777,7 @@ where
1270512777
inputs_to_contribute,
1270612778
shared_funding_input: None,
1270712779
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats),
12708-
outputs_to_contribute: Vec::new(),
12780+
outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone(),
1270912781
}
1271012782
).map_err(|err| {
1271112783
let reason = ClosureReason::ProcessingError { err: err.to_string() };
@@ -15876,43 +15948,43 @@ mod tests {
1587615948

1587715949
// 2 inputs, initiator, 2000 sat/kw feerate
1587815950
assert_eq!(
15879-
estimate_v2_funding_transaction_fee(&two_inputs, true, false, 2000),
15951+
estimate_v2_funding_transaction_fee(&two_inputs, &[], true, false, 2000),
1588015952
1520,
1588115953
);
1588215954

1588315955
// higher feerate
1588415956
assert_eq!(
15885-
estimate_v2_funding_transaction_fee(&two_inputs, true, false, 3000),
15957+
estimate_v2_funding_transaction_fee(&two_inputs, &[], true, false, 3000),
1588615958
2280,
1588715959
);
1588815960

1588915961
// only 1 input
1589015962
assert_eq!(
15891-
estimate_v2_funding_transaction_fee(&one_input, true, false, 2000),
15963+
estimate_v2_funding_transaction_fee(&one_input, &[], true, false, 2000),
1589215964
974,
1589315965
);
1589415966

1589515967
// 0 inputs
1589615968
assert_eq!(
15897-
estimate_v2_funding_transaction_fee(&[], true, false, 2000),
15969+
estimate_v2_funding_transaction_fee(&[], &[], true, false, 2000),
1589815970
428,
1589915971
);
1590015972

1590115973
// not initiator
1590215974
assert_eq!(
15903-
estimate_v2_funding_transaction_fee(&[], false, false, 2000),
15975+
estimate_v2_funding_transaction_fee(&[], &[], false, false, 2000),
1590415976
0,
1590515977
);
1590615978

1590715979
// splice initiator
1590815980
assert_eq!(
15909-
estimate_v2_funding_transaction_fee(&one_input, true, true, 2000),
15981+
estimate_v2_funding_transaction_fee(&one_input, &[], true, true, 2000),
1591015982
1746,
1591115983
);
1591215984

1591315985
// splice acceptor
1591415986
assert_eq!(
15915-
estimate_v2_funding_transaction_fee(&one_input, false, true, 2000),
15987+
estimate_v2_funding_transaction_fee(&one_input, &[], false, true, 2000),
1591615988
546,
1591715989
);
1591815990
}

0 commit comments

Comments
 (0)