Skip to content

Commit bc3eb67

Browse files
committed
Add manual testing for accepting dual-funded channels
1 parent 04d7bc0 commit bc3eb67

File tree

5 files changed

+338
-12
lines changed

5 files changed

+338
-12
lines changed

lightning/src/ln/channel.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -4065,6 +4065,20 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40654065
partial_signature_with_nonce: None,
40664066
})
40674067
}
4068+
4069+
#[cfg(test)]
4070+
pub fn get_initial_counterparty_commitment_signature_for_test<L: Deref>(
4071+
&mut self, logger: &L, channel_transaction_parameters: ChannelTransactionParameters,
4072+
counterparty_cur_commitment_point_override: PublicKey,
4073+
) -> Result<Signature, ChannelError>
4074+
where
4075+
SP::Target: SignerProvider,
4076+
L::Target: Logger
4077+
{
4078+
self.counterparty_cur_commitment_point = Some(counterparty_cur_commitment_point_override);
4079+
self.channel_transaction_parameters = channel_transaction_parameters;
4080+
self.get_initial_counterparty_commitment_signature(logger)
4081+
}
40684082
}
40694083

40704084
// Internal utility functions for channels
@@ -8937,7 +8951,7 @@ impl<SP: Deref> InboundV2Channel<SP> where SP::Target: SignerProvider {
89378951
is_initiator: false,
89388952
inputs_to_contribute: funding_inputs,
89398953
outputs_to_contribute: Vec::new(),
8940-
expected_remote_shared_funding_output: Some((context.get_funding_redeemscript(), context.channel_value_satoshis)),
8954+
expected_remote_shared_funding_output: Some((context.get_funding_redeemscript().to_p2wsh(), context.channel_value_satoshis)),
89418955
}
89428956
).map_err(|_| ChannelError::Close((
89438957
"V2 channel rejected due to sender error".into(),

lightning/src/ln/channelmanager.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -2600,8 +2600,14 @@ where
26002600
/// offer they resolve to to the given one.
26012601
pub testing_dnssec_proof_offer_resolution_override: Mutex<HashMap<HumanReadableName, Offer>>,
26022602

2603+
#[cfg(test)]
2604+
pub(super) entropy_source: ES,
2605+
#[cfg(not(test))]
26032606
entropy_source: ES,
26042607
node_signer: NS,
2608+
#[cfg(test)]
2609+
pub(super) signer_provider: SP,
2610+
#[cfg(not(test))]
26052611
signer_provider: SP,
26062612

26072613
logger: L,
@@ -3469,6 +3475,11 @@ where
34693475
&self.default_configuration
34703476
}
34713477

3478+
#[cfg(test)]
3479+
pub fn create_and_insert_outbound_scid_alias_for_test(&self) -> u64 {
3480+
self.create_and_insert_outbound_scid_alias()
3481+
}
3482+
34723483
fn create_and_insert_outbound_scid_alias(&self) -> u64 {
34733484
let height = self.best_block.read().unwrap().height;
34743485
let mut outbound_scid_alias = 0;
@@ -15674,9 +15685,6 @@ mod tests {
1567415685

1567515686
expect_pending_htlcs_forwardable!(nodes[0]);
1567615687
}
15677-
15678-
// Dual-funding: V2 Channel Establishment Tests
15679-
// TODO(dual_funding): Complete these.
1568015688
}
1568115689

1568215690
#[cfg(ldk_bench)]
+266
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
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 bitcoin::Weight;
13+
14+
use crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator};
15+
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider};
16+
use crate::ln::chan_utils::{
17+
make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters,
18+
CounterpartyChannelTransactionParameters,
19+
};
20+
use crate::ln::channel::{
21+
calculate_our_funding_satoshis, OutboundV2Channel, MIN_CHAN_DUST_LIMIT_SATOSHIS,
22+
};
23+
use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint};
24+
use crate::ln::functional_test_utils::*;
25+
use crate::ln::msgs::ChannelMessageHandler;
26+
use crate::ln::msgs::{CommitmentSigned, TxAddInput, TxAddOutput, TxComplete};
27+
use crate::ln::types::ChannelId;
28+
use crate::prelude::*;
29+
use crate::sign::{ChannelSigner as _, P2WPKH_WITNESS_WEIGHT};
30+
use crate::util::ser::TransactionU16LenLimited;
31+
use crate::util::test_utils;
32+
33+
// Dual-funding: V2 Channel Establishment Tests
34+
struct V2ChannelEstablishmentTestSession {
35+
initiator_input_value_satoshis: u64,
36+
}
37+
38+
// TODO(dual_funding): Use real node and API for creating V2 channels as initiator when available,
39+
// instead of manually constructing messages.
40+
fn do_test_v2_channel_establishment(
41+
session: V2ChannelEstablishmentTestSession, test_async_persist: bool,
42+
) {
43+
let chanmon_cfgs = create_chanmon_cfgs(2);
44+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
45+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
46+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
47+
let logger_a = test_utils::TestLogger::with_id("node a".to_owned());
48+
49+
// Create a funding input for the new channel along with its previous transaction.
50+
let initiator_funding_inputs: Vec<_> = create_dual_funding_utxos_with_prev_txs(
51+
&nodes[0],
52+
&[session.initiator_input_value_satoshis],
53+
)
54+
.into_iter()
55+
.map(|(txin, tx)| (txin, TransactionU16LenLimited::new(tx).unwrap()))
56+
.collect();
57+
58+
// Alice creates a dual-funded channel as initiator.
59+
let funding_feerate = node_cfgs[0]
60+
.fee_estimator
61+
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
62+
let funding_satoshis = calculate_our_funding_satoshis(
63+
true,
64+
&initiator_funding_inputs[..],
65+
Weight::from_wu(P2WPKH_WITNESS_WEIGHT),
66+
funding_feerate,
67+
MIN_CHAN_DUST_LIMIT_SATOSHIS,
68+
)
69+
.unwrap();
70+
let mut channel = OutboundV2Channel::new(
71+
&LowerBoundedFeeEstimator(node_cfgs[0].fee_estimator),
72+
&nodes[0].node.entropy_source,
73+
&nodes[0].node.signer_provider,
74+
nodes[1].node.get_our_node_id(),
75+
&nodes[1].node.init_features(),
76+
funding_satoshis,
77+
initiator_funding_inputs.clone(),
78+
42, /* user_channel_id */
79+
nodes[0].node.get_current_default_configuration(),
80+
nodes[0].best_block_info().1,
81+
nodes[0].node.create_and_insert_outbound_scid_alias_for_test(),
82+
ConfirmationTarget::NonAnchorChannelFee,
83+
&logger_a,
84+
)
85+
.unwrap();
86+
let open_channel_v2_msg = channel.get_open_channel_v2(nodes[0].chain_source.chain_hash);
87+
88+
nodes[1].node.handle_open_channel_v2(nodes[0].node.get_our_node_id(), &open_channel_v2_msg);
89+
90+
let accept_channel_v2_msg = get_event_msg!(
91+
nodes[1],
92+
MessageSendEvent::SendAcceptChannelV2,
93+
nodes[0].node.get_our_node_id()
94+
);
95+
let channel_id = ChannelId::v2_from_revocation_basepoints(
96+
&RevocationBasepoint::from(accept_channel_v2_msg.common_fields.revocation_basepoint),
97+
&RevocationBasepoint::from(open_channel_v2_msg.common_fields.revocation_basepoint),
98+
);
99+
100+
let tx_add_input_msg = TxAddInput {
101+
channel_id,
102+
serial_id: 2, // Even serial_id from initiator.
103+
prevtx: initiator_funding_inputs[0].1.clone(),
104+
prevtx_out: 0,
105+
sequence: initiator_funding_inputs[0].0.sequence.0,
106+
shared_input_txid: None,
107+
};
108+
let input_value =
109+
tx_add_input_msg.prevtx.as_transaction().output[tx_add_input_msg.prevtx_out as usize].value;
110+
assert_eq!(input_value.to_sat(), session.initiator_input_value_satoshis);
111+
112+
nodes[1].node.handle_tx_add_input(nodes[0].node.get_our_node_id(), &tx_add_input_msg);
113+
114+
let _tx_complete_msg =
115+
get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id());
116+
117+
let tx_add_output_msg = TxAddOutput {
118+
channel_id,
119+
serial_id: 4,
120+
sats: funding_satoshis,
121+
script: make_funding_redeemscript(
122+
&open_channel_v2_msg.common_fields.funding_pubkey,
123+
&accept_channel_v2_msg.common_fields.funding_pubkey,
124+
)
125+
.to_p2wsh(),
126+
};
127+
nodes[1].node.handle_tx_add_output(nodes[0].node.get_our_node_id(), &tx_add_output_msg);
128+
129+
let _tx_complete_msg =
130+
get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id());
131+
132+
let tx_complete_msg = TxComplete { channel_id };
133+
134+
nodes[1].node.handle_tx_complete(nodes[0].node.get_our_node_id(), &tx_complete_msg);
135+
let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
136+
assert_eq!(msg_events.len(), 1);
137+
let _msg_commitment_signed_from_1 = match msg_events[0] {
138+
MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => {
139+
assert_eq!(*node_id, nodes[0].node.get_our_node_id());
140+
updates.commitment_signed.clone()
141+
},
142+
_ => panic!("Unexpected event"),
143+
};
144+
145+
let (funding_outpoint, channel_type_features) = {
146+
let per_peer_state = nodes[1].node.per_peer_state.read().unwrap();
147+
let peer_state =
148+
per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap();
149+
let channel_context =
150+
peer_state.channel_by_id.get(&tx_complete_msg.channel_id).unwrap().context();
151+
(channel_context.get_funding_txo(), channel_context.get_channel_type().clone())
152+
};
153+
154+
let channel_transaction_parameters = ChannelTransactionParameters {
155+
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
156+
pubkeys: ChannelPublicKeys {
157+
funding_pubkey: accept_channel_v2_msg.common_fields.funding_pubkey,
158+
revocation_basepoint: RevocationBasepoint(
159+
accept_channel_v2_msg.common_fields.revocation_basepoint,
160+
),
161+
payment_point: accept_channel_v2_msg.common_fields.payment_basepoint,
162+
delayed_payment_basepoint: DelayedPaymentBasepoint(
163+
accept_channel_v2_msg.common_fields.delayed_payment_basepoint,
164+
),
165+
htlc_basepoint: HtlcBasepoint(accept_channel_v2_msg.common_fields.htlc_basepoint),
166+
},
167+
selected_contest_delay: accept_channel_v2_msg.common_fields.to_self_delay,
168+
}),
169+
holder_pubkeys: ChannelPublicKeys {
170+
funding_pubkey: open_channel_v2_msg.common_fields.funding_pubkey,
171+
revocation_basepoint: RevocationBasepoint(
172+
open_channel_v2_msg.common_fields.revocation_basepoint,
173+
),
174+
payment_point: open_channel_v2_msg.common_fields.payment_basepoint,
175+
delayed_payment_basepoint: DelayedPaymentBasepoint(
176+
open_channel_v2_msg.common_fields.delayed_payment_basepoint,
177+
),
178+
htlc_basepoint: HtlcBasepoint(open_channel_v2_msg.common_fields.htlc_basepoint),
179+
},
180+
holder_selected_contest_delay: open_channel_v2_msg.common_fields.to_self_delay,
181+
is_outbound_from_holder: true,
182+
funding_outpoint,
183+
channel_type_features,
184+
};
185+
186+
channel
187+
.context
188+
.get_mut_signer()
189+
.as_mut_ecdsa()
190+
.unwrap()
191+
.provide_channel_parameters(&channel_transaction_parameters);
192+
193+
let msg_commitment_signed_from_0 = CommitmentSigned {
194+
channel_id,
195+
signature: channel
196+
.context
197+
.get_initial_counterparty_commitment_signature_for_test(
198+
&&logger_a,
199+
channel_transaction_parameters,
200+
accept_channel_v2_msg.common_fields.first_per_commitment_point,
201+
)
202+
.unwrap(),
203+
htlc_signatures: vec![],
204+
batch: None,
205+
#[cfg(taproot)]
206+
partial_signature_with_nonce: None,
207+
};
208+
209+
if test_async_persist {
210+
chanmon_cfgs[1]
211+
.persister
212+
.set_update_ret(crate::chain::ChannelMonitorUpdateStatus::InProgress);
213+
}
214+
215+
// Handle the initial commitment_signed exchange. Order is not important here.
216+
nodes[1]
217+
.node
218+
.handle_commitment_signed(nodes[0].node.get_our_node_id(), &msg_commitment_signed_from_0);
219+
check_added_monitors(&nodes[1], 1);
220+
221+
if test_async_persist {
222+
let events = nodes[1].node.get_and_clear_pending_events();
223+
assert!(events.is_empty());
224+
225+
chanmon_cfgs[1]
226+
.persister
227+
.set_update_ret(crate::chain::ChannelMonitorUpdateStatus::Completed);
228+
let (outpoint, latest_update, _) = *nodes[1]
229+
.chain_monitor
230+
.latest_monitor_update_id
231+
.lock()
232+
.unwrap()
233+
.get(&channel_id)
234+
.unwrap();
235+
nodes[1].chain_monitor.chain_monitor.force_channel_monitor_updated(outpoint, latest_update);
236+
}
237+
238+
let events = nodes[1].node.get_and_clear_pending_events();
239+
assert_eq!(events.len(), 1);
240+
match events[0] {
241+
Event::ChannelPending { channel_id: chan_id, .. } => assert_eq!(chan_id, channel_id),
242+
_ => panic!("Unexpected event"),
243+
};
244+
245+
let tx_signatures_msg = get_event_msg!(
246+
nodes[1],
247+
MessageSendEvent::SendTxSignatures,
248+
nodes[0].node.get_our_node_id()
249+
);
250+
251+
assert_eq!(tx_signatures_msg.channel_id, channel_id);
252+
}
253+
254+
#[test]
255+
fn test_v2_channel_establishment() {
256+
// Only initiator contributes, no persist pending
257+
do_test_v2_channel_establishment(
258+
V2ChannelEstablishmentTestSession { initiator_input_value_satoshis: 100_000 },
259+
false,
260+
);
261+
// Only initiator contributes, persist pending
262+
do_test_v2_channel_establishment(
263+
V2ChannelEstablishmentTestSession { initiator_input_value_satoshis: 100_000 },
264+
true,
265+
);
266+
}

0 commit comments

Comments
 (0)