Skip to content

Commit 2d1278d

Browse files
committed
Add manual testing for accepting dual-funded channels
1 parent 1a29af1 commit 2d1278d

File tree

5 files changed

+296
-12
lines changed

5 files changed

+296
-12
lines changed

lightning/src/ln/channel.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4005,6 +4005,20 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40054005
_ => todo!(),
40064006
}
40074007
}
4008+
4009+
#[cfg(test)]
4010+
pub fn get_initial_counterparty_commitment_signature_for_test<L: Deref>(
4011+
&mut self, logger: &L, channel_transaction_parameters: ChannelTransactionParameters,
4012+
counterparty_cur_commitment_point_override: PublicKey,
4013+
) -> Result<Signature, ChannelError>
4014+
where
4015+
SP::Target: SignerProvider,
4016+
L::Target: Logger
4017+
{
4018+
self.counterparty_cur_commitment_point = Some(counterparty_cur_commitment_point_override);
4019+
self.channel_transaction_parameters = channel_transaction_parameters;
4020+
self.get_initial_counterparty_commitment_signature(logger)
4021+
}
40084022
}
40094023

40104024
// Internal utility functions for channels
@@ -8863,7 +8877,7 @@ impl<SP: Deref> InboundV2Channel<SP> where SP::Target: SignerProvider {
88638877
is_initiator: false,
88648878
inputs_to_contribute: funding_inputs,
88658879
outputs_to_contribute: Vec::new(),
8866-
expected_remote_shared_funding_output: Some((context.get_funding_redeemscript(), context.channel_value_satoshis)),
8880+
expected_remote_shared_funding_output: Some((context.get_funding_redeemscript().to_p2wsh(), context.channel_value_satoshis)),
88678881
}
88688882
).map_err(|_| ChannelError::Close((
88698883
"V2 channel rejected due to sender error".into(),

lightning/src/ln/channelmanager.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2577,8 +2577,14 @@ where
25772577
/// [`ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee`] estimate.
25782578
last_days_feerates: Mutex<VecDeque<(u32, u32)>>,
25792579

2580+
#[cfg(test)]
2581+
pub(super) entropy_source: ES,
2582+
#[cfg(not(test))]
25802583
entropy_source: ES,
25812584
node_signer: NS,
2585+
#[cfg(test)]
2586+
pub(super) signer_provider: SP,
2587+
#[cfg(not(test))]
25822588
signer_provider: SP,
25832589

25842590
logger: L,
@@ -3406,6 +3412,11 @@ where
34063412
&self.default_configuration
34073413
}
34083414

3415+
#[cfg(test)]
3416+
pub fn create_and_insert_outbound_scid_alias_for_test(&self) -> u64 {
3417+
self.create_and_insert_outbound_scid_alias()
3418+
}
3419+
34093420
fn create_and_insert_outbound_scid_alias(&self) -> u64 {
34103421
let height = self.best_block.read().unwrap().height;
34113422
let mut outbound_scid_alias = 0;
@@ -15157,9 +15168,6 @@ mod tests {
1515715168

1515815169
expect_pending_htlcs_forwardable!(nodes[0]);
1515915170
}
15160-
15161-
// Dual-funding: V2 Channel Establishment Tests
15162-
// TODO(dual_funding): Complete these.
1516315171
}
1516415172

1516515173
#[cfg(ldk_bench)]
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Tests that test the creation of dual-funded channels in ChannelManager.
11+
12+
use crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator};
13+
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider};
14+
use crate::ln::chan_utils::{
15+
make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters,
16+
CounterpartyChannelTransactionParameters,
17+
};
18+
use crate::ln::channel::{
19+
calculate_our_funding_satoshis, OutboundV2Channel, MIN_CHAN_DUST_LIMIT_SATOSHIS,
20+
};
21+
use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint};
22+
use crate::ln::functional_test_utils::*;
23+
use crate::ln::msgs::ChannelMessageHandler;
24+
use crate::ln::msgs::{CommitmentSigned, TxAddInput, TxAddOutput, TxComplete};
25+
use crate::ln::types::ChannelId;
26+
use crate::prelude::*;
27+
use crate::sign::ChannelSigner as _;
28+
use crate::util::ser::TransactionU16LenLimited;
29+
use crate::util::test_utils;
30+
31+
// Dual-funding: V2 Channel Establishment Tests
32+
struct V2ChannelEstablishmentTestSession {
33+
initiator_input_value_satoshis: u64,
34+
}
35+
36+
// TODO(dual_funding): Use real node and API for creating V2 channels as initiator when available,
37+
// instead of manually constructing messages.
38+
fn do_test_v2_channel_establishment(session: V2ChannelEstablishmentTestSession) {
39+
let chanmon_cfgs = create_chanmon_cfgs(2);
40+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
41+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
42+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
43+
let logger_a = test_utils::TestLogger::with_id("node a".to_owned());
44+
45+
// Create a funding input for the new channel along with its previous transaction.
46+
let initiator_funding_inputs: Vec<_> = create_dual_funding_utxos_with_prev_txs(
47+
&nodes[0],
48+
&[session.initiator_input_value_satoshis],
49+
)
50+
.into_iter()
51+
.map(|(txin, tx)| (txin, TransactionU16LenLimited::new(tx).unwrap()))
52+
.collect();
53+
54+
// Alice creates a dual-funded channel as initiator.
55+
let funding_feerate = node_cfgs[0]
56+
.fee_estimator
57+
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
58+
let funding_satoshis = calculate_our_funding_satoshis(
59+
true,
60+
&initiator_funding_inputs[..],
61+
funding_feerate,
62+
MIN_CHAN_DUST_LIMIT_SATOSHIS,
63+
)
64+
.unwrap();
65+
let mut channel = OutboundV2Channel::new(
66+
&LowerBoundedFeeEstimator(node_cfgs[0].fee_estimator),
67+
&nodes[0].node.entropy_source,
68+
&nodes[0].node.signer_provider,
69+
nodes[1].node.get_our_node_id(),
70+
&nodes[1].node.init_features(),
71+
funding_satoshis,
72+
initiator_funding_inputs.clone(),
73+
42, /* user_channel_id */
74+
&nodes[0].node.get_current_default_configuration(),
75+
nodes[0].best_block_info().1,
76+
nodes[0].node.create_and_insert_outbound_scid_alias_for_test(),
77+
ConfirmationTarget::NonAnchorChannelFee,
78+
&logger_a,
79+
)
80+
.unwrap();
81+
let open_channel_v2_msg = channel.get_open_channel_v2(nodes[0].chain_source.chain_hash);
82+
83+
nodes[1].node.handle_open_channel_v2(nodes[0].node.get_our_node_id(), &open_channel_v2_msg);
84+
85+
let accept_channel_v2_msg = get_event_msg!(
86+
nodes[1],
87+
MessageSendEvent::SendAcceptChannelV2,
88+
nodes[0].node.get_our_node_id()
89+
);
90+
let channel_id = ChannelId::v2_from_revocation_basepoints(
91+
&RevocationBasepoint::from(accept_channel_v2_msg.common_fields.revocation_basepoint),
92+
&RevocationBasepoint::from(open_channel_v2_msg.common_fields.revocation_basepoint),
93+
);
94+
95+
let tx_add_input_msg = TxAddInput {
96+
channel_id,
97+
serial_id: 2, // Even serial_id from initiator.
98+
prevtx: initiator_funding_inputs[0].1.clone(),
99+
prevtx_out: 0,
100+
sequence: initiator_funding_inputs[0].0.sequence.0,
101+
shared_input_txid: None,
102+
};
103+
let input_value =
104+
tx_add_input_msg.prevtx.as_transaction().output[tx_add_input_msg.prevtx_out as usize].value;
105+
assert_eq!(input_value.to_sat(), session.initiator_input_value_satoshis);
106+
107+
nodes[1].node.handle_tx_add_input(nodes[0].node.get_our_node_id(), &tx_add_input_msg);
108+
109+
let _tx_complete_msg =
110+
get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id());
111+
112+
let tx_add_output_msg = TxAddOutput {
113+
channel_id,
114+
serial_id: 4,
115+
sats: funding_satoshis,
116+
script: make_funding_redeemscript(
117+
&open_channel_v2_msg.common_fields.funding_pubkey,
118+
&accept_channel_v2_msg.common_fields.funding_pubkey,
119+
)
120+
.to_p2wsh(),
121+
};
122+
nodes[1].node.handle_tx_add_output(nodes[0].node.get_our_node_id(), &tx_add_output_msg);
123+
124+
let _tx_complete_msg =
125+
get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id());
126+
127+
let tx_complete_msg = TxComplete { channel_id };
128+
129+
nodes[1].node.handle_tx_complete(nodes[0].node.get_our_node_id(), &tx_complete_msg);
130+
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
131+
assert_eq!(msg_events.len(), 1);
132+
let _msg_commitment_signed_from_1 = match msg_events[0] {
133+
MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => {
134+
assert_eq!(*node_id, nodes[0].node.get_our_node_id());
135+
updates.commitment_signed.clone()
136+
},
137+
_ => panic!("Unexpected event"),
138+
};
139+
140+
let (funding_outpoint, channel_type_features) = {
141+
let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
142+
let peer_state =
143+
per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
144+
let channel_context =
145+
peer_state.channel_by_id.get(&tx_complete_msg.channel_id).unwrap().context();
146+
(channel_context.get_funding_txo(), channel_context.get_channel_type().clone())
147+
};
148+
149+
let channel_transaction_parameters = ChannelTransactionParameters {
150+
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
151+
pubkeys: ChannelPublicKeys {
152+
funding_pubkey: accept_channel_v2_msg.common_fields.funding_pubkey,
153+
revocation_basepoint: RevocationBasepoint(
154+
accept_channel_v2_msg.common_fields.revocation_basepoint,
155+
),
156+
payment_point: accept_channel_v2_msg.common_fields.payment_basepoint,
157+
delayed_payment_basepoint: DelayedPaymentBasepoint(
158+
accept_channel_v2_msg.common_fields.delayed_payment_basepoint,
159+
),
160+
htlc_basepoint: HtlcBasepoint(accept_channel_v2_msg.common_fields.htlc_basepoint),
161+
},
162+
selected_contest_delay: accept_channel_v2_msg.common_fields.to_self_delay,
163+
}),
164+
holder_pubkeys: ChannelPublicKeys {
165+
funding_pubkey: open_channel_v2_msg.common_fields.funding_pubkey,
166+
revocation_basepoint: RevocationBasepoint(
167+
open_channel_v2_msg.common_fields.revocation_basepoint,
168+
),
169+
payment_point: open_channel_v2_msg.common_fields.payment_basepoint,
170+
delayed_payment_basepoint: DelayedPaymentBasepoint(
171+
open_channel_v2_msg.common_fields.delayed_payment_basepoint,
172+
),
173+
htlc_basepoint: HtlcBasepoint(open_channel_v2_msg.common_fields.htlc_basepoint),
174+
},
175+
holder_selected_contest_delay: open_channel_v2_msg.common_fields.to_self_delay,
176+
is_outbound_from_holder: true,
177+
funding_outpoint,
178+
channel_type_features,
179+
};
180+
181+
channel
182+
.context
183+
.get_mut_signer()
184+
.as_mut_ecdsa()
185+
.unwrap()
186+
.provide_channel_parameters(&channel_transaction_parameters);
187+
188+
let msg_commitment_signed_from_0 = CommitmentSigned {
189+
channel_id,
190+
signature: channel
191+
.context
192+
.get_initial_counterparty_commitment_signature_for_test(
193+
&&logger_a,
194+
channel_transaction_parameters,
195+
accept_channel_v2_msg.common_fields.first_per_commitment_point,
196+
)
197+
.unwrap(),
198+
htlc_signatures: vec![],
199+
batch: None,
200+
#[cfg(taproot)]
201+
partial_signature_with_nonce: None,
202+
};
203+
204+
// Handle the initial commitment_signed exchange. Order is not important here.
205+
nodes[1]
206+
.node
207+
.handle_commitment_signed(nodes[0].node.get_our_node_id(), &msg_commitment_signed_from_0);
208+
check_added_monitors(&nodes[1], 1);
209+
210+
let events = nodes[1].node.get_and_clear_pending_events();
211+
assert_eq!(events.len(), 1);
212+
match events[0] {
213+
Event::ChannelPending { channel_id, .. } => channel_id == channel.context.channel_id(),
214+
_ => panic!("Unexpected event"),
215+
};
216+
}
217+
218+
#[test]
219+
fn test_v2_channel_establishment() {
220+
// Only initiator contributes
221+
do_test_v2_channel_establishment(V2ChannelEstablishmentTestSession {
222+
initiator_input_value_satoshis: 100_000,
223+
});
224+
}

lightning/src/ln/functional_test_utils.rs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,20 @@ use crate::util::test_utils;
3838
use crate::util::test_utils::{TestChainMonitor, TestScorer, TestKeysInterface};
3939
use crate::util::ser::{ReadableArgs, Writeable};
4040

41+
use bitcoin::WPubkeyHash;
4142
use bitcoin::amount::Amount;
42-
use bitcoin::block::{Block, Header, Version};
43-
use bitcoin::locktime::absolute::LockTime;
44-
use bitcoin::transaction::{Transaction, TxIn, TxOut};
43+
use bitcoin::block::{Block, Header, Version as BlockVersion};
44+
use bitcoin::locktime::absolute::{LockTime, LOCK_TIME_THRESHOLD};
45+
use bitcoin::transaction::{Sequence, Transaction, TxIn, TxOut};
4546
use bitcoin::hash_types::{BlockHash, TxMerkleNode};
4647
use bitcoin::hashes::sha256::Hash as Sha256;
4748
use bitcoin::hashes::Hash as _;
4849
use bitcoin::network::Network;
4950
use bitcoin::pow::CompactTarget;
51+
use bitcoin::script::ScriptBuf;
5052
use bitcoin::secp256k1::{PublicKey, SecretKey};
51-
use bitcoin::transaction;
53+
use bitcoin::transaction::{self, Version as TxVersion};
54+
use bitcoin::witness::Witness;
5255

5356
use alloc::rc::Rc;
5457
use core::cell::RefCell;
@@ -90,7 +93,7 @@ pub fn mine_transaction_without_consistency_checks<'a, 'b, 'c, 'd>(node: &'a Nod
9093
let height = node.best_block_info().1 + 1;
9194
let mut block = Block {
9295
header: Header {
93-
version: Version::NO_SOFT_FORK_SIGNALLING,
96+
version: BlockVersion::NO_SOFT_FORK_SIGNALLING,
9497
prev_blockhash: node.best_block_hash(),
9598
merkle_root: TxMerkleNode::all_zeros(),
9699
time: height,
@@ -217,7 +220,7 @@ impl ConnectStyle {
217220

218221
pub fn create_dummy_header(prev_blockhash: BlockHash, time: u32) -> Header {
219222
Header {
220-
version: Version::NO_SOFT_FORK_SIGNALLING,
223+
version: BlockVersion::NO_SOFT_FORK_SIGNALLING,
221224
prev_blockhash,
222225
merkle_root: TxMerkleNode::all_zeros(),
223226
time,
@@ -1221,6 +1224,37 @@ fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
12211224
}
12221225
}
12231226

1227+
pub fn create_dual_funding_utxos_with_prev_txs(
1228+
node: &Node<'_, '_, '_>, utxo_values_in_satoshis: &[u64],
1229+
) -> Vec<(TxIn, Transaction)> {
1230+
// Ensure we have unique transactions per node by using the locktime.
1231+
let tx = Transaction {
1232+
version: TxVersion::TWO,
1233+
lock_time: LockTime::from_height(
1234+
u32::from_be_bytes(node.keys_manager.get_secure_random_bytes()[0..4].try_into().unwrap()) % LOCK_TIME_THRESHOLD
1235+
).unwrap(),
1236+
input: vec![],
1237+
output: utxo_values_in_satoshis.iter().map(|value_satoshis| TxOut {
1238+
value: Amount::from_sat(*value_satoshis), script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()),
1239+
}).collect()
1240+
};
1241+
1242+
let mut result = vec![];
1243+
for i in 0..utxo_values_in_satoshis.len() {
1244+
result.push(
1245+
(TxIn {
1246+
previous_output: OutPoint {
1247+
txid: tx.compute_txid(),
1248+
index: i as u16,
1249+
}.into_bitcoin_outpoint(),
1250+
script_sig: ScriptBuf::new(),
1251+
sequence: Sequence::ZERO,
1252+
witness: Witness::new(),
1253+
}, tx.clone()));
1254+
}
1255+
result
1256+
}
1257+
12241258
pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: ChannelId) -> Transaction {
12251259
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, &node_b.node.get_our_node_id(), channel_value, 42);
12261260
assert_eq!(temporary_channel_id, expected_temporary_channel_id);

lightning/src/ln/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub(crate) mod onion_utils;
4444
mod outbound_payment;
4545
pub mod wire;
4646

47+
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
48+
pub(crate) mod interactivetxs;
49+
4750
pub use onion_utils::create_payment_onion;
4851
// Older rustc (which we support) refuses to let us call the get_payment_preimage_hash!() macro
4952
// without the node parameter being mut. This is incorrect, and thus newer rustcs will complain
@@ -88,7 +91,8 @@ mod async_signer_tests;
8891
#[cfg(test)]
8992
#[allow(unused_mut)]
9093
mod offers_tests;
91-
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
92-
pub(crate) mod interactivetxs;
94+
#[cfg(test)]
95+
#[allow(unused_mut)]
96+
mod dual_funding_tests;
9397

9498
pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN;

0 commit comments

Comments
 (0)