Skip to content

Commit a93ea54

Browse files
committed
Allow passing funding inputs when manually accepting a dual-funded channel
We introduce a `ChannelManager::accept_inbound_channel_with_contribution` method allowing contributing to the overall channel capacity of an inbound dual-funded channel by contributing inputs.
1 parent 2772bfd commit a93ea54

File tree

3 files changed

+134
-33
lines changed

3 files changed

+134
-33
lines changed

lightning/src/ln/channel.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use bitcoin::constants::ChainHash;
1414
use bitcoin::script::{Builder, Script, ScriptBuf, WScriptHash};
1515
use bitcoin::sighash::EcdsaSighashType;
1616
use bitcoin::transaction::{Transaction, TxOut};
17-
use bitcoin::Witness;
17+
use bitcoin::{Weight, Witness};
1818

1919
use bitcoin::hash_types::{BlockHash, Txid};
2020
use bitcoin::hashes::sha256::Hash as Sha256;
@@ -6353,7 +6353,11 @@ impl FundingNegotiationContext {
63536353
.our_funding_inputs
63546354
.into_iter()
63556355
.map(|FundingTxInput { utxo, sequence, prevtx }| {
6356-
(TxIn { previous_output: utxo.outpoint, sequence, ..Default::default() }, prevtx)
6356+
(
6357+
TxIn { previous_output: utxo.outpoint, sequence, ..Default::default() },
6358+
prevtx,
6359+
Weight::from_wu(utxo.satisfaction_weight),
6360+
)
63576361
})
63586362
.collect();
63596363

@@ -13052,22 +13056,22 @@ where
1305213056

1305313057
/// Creates a new dual-funded channel from a remote side's request for one.
1305413058
/// Assumes chain_hash has already been checked and corresponds with what we expect!
13055-
/// TODO(dual_funding): Allow contributions, pass intended amount and inputs
1305613059
#[allow(dead_code)] // TODO(dual_funding): Remove once V2 channels is enabled.
1305713060
#[rustfmt::skip]
1305813061
pub fn new_inbound<ES: Deref, F: Deref, L: Deref>(
1305913062
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
1306013063
holder_node_id: PublicKey, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures,
13061-
their_features: &InitFeatures, msg: &msgs::OpenChannelV2,
13062-
user_id: u128, config: &UserConfig, current_chain_height: u32, logger: &L,
13064+
their_features: &InitFeatures, msg: &msgs::OpenChannelV2, user_id: u128, config: &UserConfig,
13065+
current_chain_height: u32, logger: &L, our_funding_contribution_sats: u64,
13066+
our_funding_inputs: Vec<FundingTxInput>,
1306313067
) -> Result<Self, ChannelError>
1306413068
where ES::Target: EntropySource,
1306513069
F::Target: FeeEstimator,
1306613070
L::Target: Logger,
1306713071
{
13068-
// TODO(dual_funding): Take these as input once supported
13069-
let (our_funding_contribution, our_funding_contribution_sats) = (SignedAmount::ZERO, 0u64);
13070-
let our_funding_inputs = Vec::new();
13072+
// This u64 -> i64 cast is safe as `our_funding_contribution_sats` is bounded above by the total
13073+
// bitcoin supply, which is far less than i64::MAX.
13074+
let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_sats as i64);
1307113075

1307213076
let channel_value_satoshis =
1307313077
our_funding_contribution_sats.saturating_add(msg.common_fields.funding_satoshis);
@@ -13127,7 +13131,7 @@ where
1312713131
let inputs_to_contribute = our_funding_inputs
1312813132
.into_iter()
1312913133
.map(|FundingTxInput { utxo, sequence, prevtx }| {
13130-
(TxIn { previous_output: utxo.outpoint, sequence, ..Default::default() }, prevtx)
13134+
(TxIn { previous_output: utxo.outpoint, sequence, ..Default::default() }, prevtx, Weight::from_wu(utxo.satisfaction_weight))
1313113135
})
1313213136
.collect();
1313313137

lightning/src/ln/channelmanager.rs

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,10 @@ use crate::ln::channel::QuiescentAction;
6161
use crate::ln::channel::{
6262
self, hold_time_since, Channel, ChannelError, ChannelUpdateStatus, FundedChannel,
6363
InboundV1Channel, OutboundV1Channel, PendingV2Channel, ReconnectionMsg, ShutdownResult,
64-
UpdateFulfillCommitFetch, WithChannelContext,
64+
UpdateFulfillCommitFetch, WithChannelContext, TOTAL_BITCOIN_SUPPLY_SATOSHIS,
6565
};
6666
use crate::ln::channel_state::ChannelDetails;
67+
use crate::ln::funding::FundingTxInput;
6768
#[cfg(splicing)]
6869
use crate::ln::funding::SpliceContribution;
6970
use crate::ln::inbound_payment;
@@ -9154,6 +9155,8 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
91549155
false,
91559156
user_channel_id,
91569157
config_overrides,
9158+
0,
9159+
vec![],
91579160
)
91589161
}
91599162

@@ -9185,15 +9188,71 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
91859188
true,
91869189
user_channel_id,
91879190
config_overrides,
9191+
0,
9192+
vec![],
9193+
)
9194+
}
9195+
9196+
/// Accepts a request to open a dual-funded channel with a contribution provided by us after an
9197+
/// [`Event::OpenChannelRequest`].
9198+
///
9199+
/// The [`Event::OpenChannelRequest::channel_negotiation_type`] field will indicate the open channel
9200+
/// request is for a dual-funded channel when the variant is `InboundChannelFunds::DualFunded`.
9201+
///
9202+
/// The `temporary_channel_id` parameter indicates which inbound channel should be accepted,
9203+
/// and the `counterparty_node_id` parameter is the id of the peer which has requested to open
9204+
/// the channel.
9205+
///
9206+
/// The `user_channel_id` parameter will be provided back in
9207+
/// [`Event::ChannelClosed::user_channel_id`] to allow tracking of which events correspond
9208+
/// with which `accept_inbound_channel_*` call.
9209+
///
9210+
/// The `funding_inputs` parameter provides the `txin`s along with their previous transactions, and
9211+
/// a corresponding witness weight for each input that will be used to contribute towards our
9212+
/// portion of the channel value. Our contribution will be calculated as the total value of these
9213+
/// inputs minus the fees we need to cover for the interactive funding transaction. The witness
9214+
/// weights must correspond to the witnesses you will provide through [`ChannelManager::funding_transaction_signed`]
9215+
/// after receiving [`Event::FundingTransactionReadyForSigning`].
9216+
///
9217+
/// Note that this method will return an error and reject the channel if it requires support for
9218+
/// zero confirmations.
9219+
// TODO(dual_funding): Discussion on complications with 0conf dual-funded channels where "locking"
9220+
// of UTXOs used for funding would be required and other issues.
9221+
// See https://diyhpl.us/~bryan/irc/bitcoin/bitcoin-dev/linuxfoundation-pipermail/lightning-dev/2023-May/003922.txt
9222+
///
9223+
/// [`Event::OpenChannelRequest`]: events::Event::OpenChannelRequest
9224+
/// [`Event::OpenChannelRequest::channel_negotiation_type`]: events::Event::OpenChannelRequest::channel_negotiation_type
9225+
/// [`Event::ChannelClosed::user_channel_id`]: events::Event::ChannelClosed::user_channel_id
9226+
/// [`Event::FundingTransactionReadyForSigning`]: events::Event::FundingTransactionReadyForSigning
9227+
/// [`ChannelManager::funding_transaction_signed`]: ChannelManager::funding_transaction_signed
9228+
pub fn accept_inbound_channel_with_contribution(
9229+
&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey,
9230+
user_channel_id: u128, config_overrides: Option<ChannelConfigOverrides>,
9231+
our_funding_satoshis: u64, funding_inputs: Vec<FundingTxInput>,
9232+
) -> Result<(), APIError> {
9233+
self.do_accept_inbound_channel(
9234+
temporary_channel_id,
9235+
counterparty_node_id,
9236+
false,
9237+
user_channel_id,
9238+
config_overrides,
9239+
our_funding_satoshis,
9240+
funding_inputs,
91889241
)
91899242
}
91909243

91919244
/// TODO(dual_funding): Allow contributions, pass intended amount and inputs
91929245
#[rustfmt::skip]
91939246
fn do_accept_inbound_channel(
91949247
&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, accept_0conf: bool,
9195-
user_channel_id: u128, config_overrides: Option<ChannelConfigOverrides>
9248+
user_channel_id: u128, config_overrides: Option<ChannelConfigOverrides>, our_funding_satoshis: u64,
9249+
funding_inputs: Vec<FundingTxInput>
91969250
) -> Result<(), APIError> {
9251+
// We do this check early as we will cast this to i64 for the NegotiationContext, and this is the
9252+
// actual upper bound which is less than i64::MAX.
9253+
if our_funding_satoshis >= TOTAL_BITCOIN_SUPPLY_SATOSHIS {
9254+
return Err(APIError::APIMisuseError{err: format!("the funding contribution must be smaller than the total bitcoin supply, it was {} satoshis", our_funding_satoshis)});
9255+
}
91979256

91989257
let mut config = self.default_configuration.clone();
91999258

@@ -9251,7 +9310,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
92519310
&self.channel_type_features(), &peer_state.latest_features,
92529311
&open_channel_msg,
92539312
user_channel_id, &config, best_block_height,
9254-
&self.logger,
9313+
&self.logger, our_funding_satoshis, funding_inputs,
92559314
).map_err(|e| {
92569315
let channel_id = open_channel_msg.common_fields.temporary_channel_id;
92579316
MsgHandleErrInternal::from_chan_no_close(e, channel_id)
@@ -9534,7 +9593,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
95349593
&self.fee_estimator, &self.entropy_source, &self.signer_provider,
95359594
self.get_our_node_id(), *counterparty_node_id, &self.channel_type_features(),
95369595
&peer_state.latest_features, msg, user_channel_id,
9537-
&self.default_configuration, best_block_height, &self.logger,
9596+
&self.default_configuration, best_block_height, &self.logger, 0, vec![],
95389597
).map_err(|e| MsgHandleErrInternal::from_chan_no_close(e, msg.common_fields.temporary_channel_id))?;
95399598
let message_send_event = MessageSendEvent::SendAcceptChannelV2 {
95409599
node_id: *counterparty_node_id,

lightning/src/ln/interactivetxs.rs

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,12 @@ pub(crate) fn estimate_input_weight(prev_output: &TxOut) -> Weight {
832832
})
833833
}
834834

835+
pub(crate) fn get_input_weight(witness_weight: Weight) -> Weight {
836+
Weight::from_wu(
837+
(BASE_INPUT_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT).saturating_add(witness_weight.to_wu()),
838+
)
839+
}
840+
835841
pub(crate) fn get_output_weight(script_pubkey: &ScriptBuf) -> Weight {
836842
Weight::from_wu(
837843
(8 /* value */ + script_pubkey.consensus_encode(&mut sink()).unwrap() as u64)
@@ -1949,7 +1955,7 @@ where
19491955
pub feerate_sat_per_kw: u32,
19501956
pub is_initiator: bool,
19511957
pub funding_tx_locktime: AbsoluteLockTime,
1952-
pub inputs_to_contribute: Vec<(TxIn, Transaction)>,
1958+
pub inputs_to_contribute: Vec<(TxIn, Transaction, Weight)>,
19531959
pub shared_funding_input: Option<SharedOwnedInput>,
19541960
pub shared_funding_output: SharedOwnedOutput,
19551961
pub outputs_to_contribute: Vec<TxOut>,
@@ -1989,15 +1995,15 @@ impl InteractiveTxConstructor {
19891995
);
19901996

19911997
// Check for the existence of prevouts'
1992-
for (txin, tx) in inputs_to_contribute.iter() {
1998+
for (txin, tx, _) in inputs_to_contribute.iter() {
19931999
let vout = txin.previous_output.vout as usize;
19942000
if tx.output.get(vout).is_none() {
19952001
return Err(AbortReason::PrevTxOutInvalid);
19962002
}
19972003
}
19982004
let mut inputs_to_contribute: Vec<(SerialId, InputOwned)> = inputs_to_contribute
19992005
.into_iter()
2000-
.map(|(txin, tx)| {
2006+
.map(|(txin, tx, _)| {
20012007
let serial_id = generate_holder_serial_id(entropy_source, is_initiator);
20022008
let vout = txin.previous_output.vout as usize;
20032009
let prev_output = tx.output.get(vout).unwrap().clone(); // checked above
@@ -2165,6 +2171,11 @@ impl InteractiveTxConstructor {
21652171
/// given inputs and outputs, and intended contribution. Takes into account the fees and the dust
21662172
/// limit.
21672173
///
2174+
/// Note that since the type of change output cannot be determined at this point, this calculation
2175+
/// does not account for the weight contributed by the change output itself. The fees for the
2176+
/// weight of this change output should be subtracted from the result of this function call to get
2177+
/// the final amount for the change output (if above dust).
2178+
///
21682179
/// Three outcomes are possible:
21692180
/// - Inputs are sufficient for intended contribution, fees, and a larger-than-dust change:
21702181
/// `Ok(Some(change_amount))`
@@ -2247,13 +2258,14 @@ mod tests {
22472258
use crate::util::atomic_counter::AtomicCounter;
22482259
use bitcoin::absolute::LockTime as AbsoluteLockTime;
22492260
use bitcoin::amount::Amount;
2261+
use bitcoin::ecdsa::Signature;
22502262
use bitcoin::hashes::Hash;
22512263
use bitcoin::hex::FromHex;
22522264
use bitcoin::key::{TweakedPublicKey, UntweakedPublicKey};
22532265
use bitcoin::script::Builder;
22542266
use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey};
22552267
use bitcoin::transaction::Version;
2256-
use bitcoin::{opcodes, WScriptHash, Weight, XOnlyPublicKey};
2268+
use bitcoin::{opcodes, WScriptHash, Weight, Witness, XOnlyPublicKey};
22572269
use bitcoin::{
22582270
OutPoint, PubkeyHash, ScriptBuf, Sequence, SignedAmount, Transaction, TxIn, TxOut,
22592271
WPubkeyHash,
@@ -2310,12 +2322,12 @@ mod tests {
23102322

23112323
struct TestSession {
23122324
description: &'static str,
2313-
inputs_a: Vec<(TxIn, Transaction)>,
2325+
inputs_a: Vec<(TxIn, Transaction, Weight)>,
23142326
a_shared_input: Option<(OutPoint, TxOut, u64)>,
23152327
/// The funding output, with the value contributed
23162328
shared_output_a: (TxOut, u64),
23172329
outputs_a: Vec<TxOut>,
2318-
inputs_b: Vec<(TxIn, Transaction)>,
2330+
inputs_b: Vec<(TxIn, Transaction, Weight)>,
23192331
b_shared_input: Option<(OutPoint, TxOut, u64)>,
23202332
/// The funding output, with the value contributed
23212333
shared_output_b: (TxOut, u64),
@@ -2585,7 +2597,7 @@ mod tests {
25852597
}
25862598
}
25872599

2588-
fn generate_inputs(outputs: &[TestOutput]) -> Vec<(TxIn, Transaction)> {
2600+
fn generate_inputs(outputs: &[TestOutput]) -> Vec<(TxIn, Transaction, Weight)> {
25892601
let tx = generate_tx(outputs);
25902602
let txid = tx.compute_txid();
25912603
tx.output
@@ -2596,9 +2608,16 @@ mod tests {
25962608
previous_output: OutPoint { txid, vout: idx as u32 },
25972609
script_sig: Default::default(),
25982610
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
2599-
witness: Default::default(),
2611+
witness: Witness::p2wpkh(
2612+
&Signature::sighash_all(
2613+
bitcoin::secp256k1::ecdsa::Signature::from_der(&<Vec<u8>>::from_hex("3044022008f4f37e2d8f74e18c1b8fde2374d5f28402fb8ab7fd1cc5b786aa40851a70cb022032b1374d1a0f125eae4f69d1bc0b7f896c964cfdba329f38a952426cf427484c").unwrap()[..]).unwrap()
2614+
)
2615+
.into(),
2616+
&PublicKey::from_slice(&[2; 33]).unwrap(),
2617+
),
26002618
};
2601-
(txin, tx.clone())
2619+
let witness_weight = Weight::from_wu_usize(txin.witness.size());
2620+
(txin, tx.clone(), witness_weight)
26022621
})
26032622
.collect()
26042623
}
@@ -2646,12 +2665,12 @@ mod tests {
26462665
(generate_txout(&TestOutput::P2WSH(value)), local_value)
26472666
}
26482667

2649-
fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, Transaction)> {
2668+
fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, Transaction, Weight)> {
26502669
// Generate transactions with a total `count` number of outputs such that no transaction has a
26512670
// serialized length greater than u16::MAX.
26522671
let max_outputs_per_prevtx = 1_500;
26532672
let mut remaining = count;
2654-
let mut inputs: Vec<(TxIn, Transaction)> = Vec::with_capacity(count as usize);
2673+
let mut inputs: Vec<(TxIn, Transaction, Weight)> = Vec::with_capacity(count as usize);
26552674

26562675
while remaining > 0 {
26572676
let tx_output_count = remaining.min(max_outputs_per_prevtx);
@@ -2664,7 +2683,7 @@ mod tests {
26642683
);
26652684
let txid = tx.compute_txid();
26662685

2667-
let mut temp: Vec<(TxIn, Transaction)> = tx
2686+
let mut temp: Vec<(TxIn, Transaction, Weight)> = tx
26682687
.output
26692688
.iter()
26702689
.enumerate()
@@ -2673,9 +2692,16 @@ mod tests {
26732692
previous_output: OutPoint { txid, vout: idx as u32 },
26742693
script_sig: Default::default(),
26752694
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
2676-
witness: Default::default(),
2695+
witness: Witness::p2wpkh(
2696+
&Signature::sighash_all(
2697+
bitcoin::secp256k1::ecdsa::Signature::from_der(&<Vec<u8>>::from_hex("3044022008f4f37e2d8f74e18c1b8fde2374d5f28402fb8ab7fd1cc5b786aa40851a70cb022032b1374d1a0f125eae4f69d1bc0b7f896c964cfdba329f38a952426cf427484c").unwrap()[..]).unwrap()
2698+
)
2699+
.into(),
2700+
&PublicKey::from_slice(&[2; 33]).unwrap(),
2701+
),
26772702
};
2678-
(input, tx.clone())
2703+
let witness_weight = Weight::from_wu_usize(input.witness.size());
2704+
(input, tx.clone(), witness_weight)
26792705
})
26802706
.collect();
26812707

@@ -2891,9 +2917,15 @@ mod tests {
28912917
previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
28922918
..Default::default()
28932919
};
2920+
let invalid_sequence_input_witness_weight =
2921+
Weight::from_wu_usize(invalid_sequence_input.witness.size());
28942922
do_test_interactive_tx_constructor(TestSession {
28952923
description: "Invalid input sequence from initiator",
2896-
inputs_a: vec![(invalid_sequence_input, tx.clone())],
2924+
inputs_a: vec![(
2925+
invalid_sequence_input,
2926+
tx.clone(),
2927+
invalid_sequence_input_witness_weight,
2928+
)],
28972929
a_shared_input: None,
28982930
shared_output_a: generate_funding_txout(1_000_000, 1_000_000),
28992931
outputs_a: vec![],
@@ -2908,9 +2940,13 @@ mod tests {
29082940
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
29092941
..Default::default()
29102942
};
2943+
let duplicate_input_witness_weight = Weight::from_wu_usize(duplicate_input.witness.size());
29112944
do_test_interactive_tx_constructor(TestSession {
29122945
description: "Duplicate prevout from initiator",
2913-
inputs_a: vec![(duplicate_input.clone(), tx.clone()), (duplicate_input, tx.clone())],
2946+
inputs_a: vec![
2947+
(duplicate_input.clone(), tx.clone(), duplicate_input_witness_weight),
2948+
(duplicate_input, tx.clone(), duplicate_input_witness_weight),
2949+
],
29142950
a_shared_input: None,
29152951
shared_output_a: generate_funding_txout(1_000_000, 1_000_000),
29162952
outputs_a: vec![],
@@ -2926,13 +2962,14 @@ mod tests {
29262962
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
29272963
..Default::default()
29282964
};
2965+
let duplicate_input_witness_weight = Weight::from_wu_usize(duplicate_input.witness.size());
29292966
do_test_interactive_tx_constructor(TestSession {
29302967
description: "Non-initiator uses same prevout as initiator",
2931-
inputs_a: vec![(duplicate_input.clone(), tx.clone())],
2968+
inputs_a: vec![(duplicate_input.clone(), tx.clone(), duplicate_input_witness_weight)],
29322969
a_shared_input: None,
29332970
shared_output_a: generate_funding_txout(1_000_000, 905_000),
29342971
outputs_a: vec![],
2935-
inputs_b: vec![(duplicate_input.clone(), tx.clone())],
2972+
inputs_b: vec![(duplicate_input.clone(), tx.clone(), duplicate_input_witness_weight)],
29362973
b_shared_input: None,
29372974
shared_output_b: generate_funding_txout(1_000_000, 95_000),
29382975
outputs_b: vec![],
@@ -2943,13 +2980,14 @@ mod tests {
29432980
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
29442981
..Default::default()
29452982
};
2983+
let duplicate_input_witness_weight = Weight::from_wu_usize(duplicate_input.witness.size());
29462984
do_test_interactive_tx_constructor(TestSession {
29472985
description: "Non-initiator uses same prevout as initiator",
2948-
inputs_a: vec![(duplicate_input.clone(), tx.clone())],
2986+
inputs_a: vec![(duplicate_input.clone(), tx.clone(), duplicate_input_witness_weight)],
29492987
a_shared_input: None,
29502988
shared_output_a: generate_funding_txout(1_000_000, 1_000_000),
29512989
outputs_a: vec![],
2952-
inputs_b: vec![(duplicate_input.clone(), tx.clone())],
2990+
inputs_b: vec![(duplicate_input.clone(), tx.clone(), duplicate_input_witness_weight)],
29532991
b_shared_input: None,
29542992
shared_output_b: generate_funding_txout(1_000_000, 0),
29552993
outputs_b: vec![],

0 commit comments

Comments
 (0)