diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 3889af9a049..895182e852f 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3987,6 +3987,20 @@ impl ChannelContext where SP::Target: SignerProvider { _ => todo!(), } } + + #[cfg(test)] + pub fn get_initial_counterparty_commitment_signature_for_test( + &mut self, logger: &L, channel_transaction_parameters: ChannelTransactionParameters, + counterparty_cur_commitment_point_override: PublicKey, + ) -> Result + where + SP::Target: SignerProvider, + L::Target: Logger + { + self.counterparty_cur_commitment_point = Some(counterparty_cur_commitment_point_override); + self.channel_transaction_parameters = channel_transaction_parameters; + self.get_initial_counterparty_commitment_signature(logger) + } } // Internal utility functions for channels @@ -8862,7 +8876,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { is_initiator: false, inputs_to_contribute: channel.dual_funding_context.our_funding_inputs.clone(), outputs_to_contribute: Vec::new(), - expected_remote_shared_funding_output: Some((channel.context().get_funding_redeemscript(), channel.context().channel_value_satoshis)), + expected_remote_shared_funding_output: Some((channel.context().get_funding_redeemscript().to_p2wsh(), channel.context().channel_value_satoshis)), } ) { Ok(tx_constructor) => { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index a3a4bf78648..08e66601c1e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -13514,19 +13514,23 @@ mod tests { use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use core::sync::atomic::Ordering; + use crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; use crate::events::{Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, ClosureReason}; + use crate::ln::chan_utils::{make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters}; + use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint}; use crate::ln::types::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret}; + use crate::ln::channel::{calculate_our_funding_satoshis, OutboundV2Channel, MIN_CHAN_DUST_LIMIT_SATOSHIS}; use crate::ln::channelmanager::{create_recv_pending_htlc_info, HTLCForwardInfo, inbound_payment, PaymentId, PaymentSendFailure, RecipientOnionFields, InterceptId}; use crate::ln::functional_test_utils::*; - use crate::ln::msgs::{self, ErrorAction}; + use crate::ln::msgs::{self, CommitmentSigned, ErrorAction, TxAddInput, TxAddOutput, TxComplete}; use crate::ln::msgs::ChannelMessageHandler; use crate::prelude::*; use crate::routing::router::{PaymentParameters, RouteParameters, find_route}; use crate::util::errors::APIError; - use crate::util::ser::Writeable; + use crate::util::ser::{TransactionU16LenLimited, Writeable}; use crate::util::test_utils; use crate::util::config::{ChannelConfig, ChannelConfigUpdate}; - use crate::sign::EntropySource; + use crate::sign::{EntropySource, ChannelSigner as _}; #[test] fn test_notify_limits() { @@ -14917,7 +14921,151 @@ mod tests { } // Dual-funding: V2 Channel Establishment Tests - // TODO(dual_funding): Complete these. + struct V2ChannelEstablishmentTestSession { + initiator_input_value_satoshis: u64, + } + + // TODO(dual_funding): Use real node and API for creating V2 channels as initiator when available, + // instead of manually constructing messages. + fn do_test_v2_channel_establishment(session: V2ChannelEstablishmentTestSession) { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + let logger_a = test_utils::TestLogger::with_id("node a".to_owned()); + + // Create a funding input for the new channel along with its previous transaction. + let initiator_funding_inputs: Vec<_> = create_dual_funding_utxos_with_prev_txs( + &nodes[0], &[session.initiator_input_value_satoshis] + ).into_iter().map(|(txin, tx)| (txin, TransactionU16LenLimited::new(tx).unwrap())).collect(); + + // Alice creates a dual-funded channel as initiator. + let funding_feerate = node_cfgs[0].fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); + let funding_satoshis = calculate_our_funding_satoshis( + true, &initiator_funding_inputs[..], funding_feerate, MIN_CHAN_DUST_LIMIT_SATOSHIS + ).unwrap(); + let mut channel = OutboundV2Channel::new( + &LowerBoundedFeeEstimator(node_cfgs[0].fee_estimator), &nodes[0].node.entropy_source, &nodes[0].node.signer_provider, + nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), funding_satoshis, + initiator_funding_inputs.clone(), 42 /* user_channel_id */, &nodes[0].node.default_configuration, nodes[0].best_block_info().1, + nodes[0].node.create_and_insert_outbound_scid_alias(), ConfirmationTarget::NonAnchorChannelFee, &logger_a).unwrap(); + let open_channel_v2_msg = channel.get_open_channel_v2(nodes[0].chain_source.chain_hash); + + nodes[1].node.handle_open_channel_v2(nodes[0].node.get_our_node_id(), &open_channel_v2_msg); + + let accept_channel_v2_msg = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannelV2, nodes[0].node.get_our_node_id()); + let channel_id = ChannelId::v2_from_revocation_basepoints( + &RevocationBasepoint::from(accept_channel_v2_msg.common_fields.revocation_basepoint), + &RevocationBasepoint::from(open_channel_v2_msg.common_fields.revocation_basepoint), + ); + + let tx_add_input_msg = TxAddInput { + channel_id, + serial_id: 2, // Even serial_id from initiator. + prevtx: initiator_funding_inputs[0].1.clone(), + prevtx_out: 0, + sequence: initiator_funding_inputs[0].0.sequence.0, + shared_input_txid: None, + }; + let input_value = tx_add_input_msg.prevtx.as_transaction().output[tx_add_input_msg.prevtx_out as usize].value; + assert_eq!(input_value.to_sat(), session.initiator_input_value_satoshis); + + nodes[1].node.handle_tx_add_input(nodes[0].node.get_our_node_id(), &tx_add_input_msg); + + let _tx_complete_msg = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); + + let tx_add_output_msg = TxAddOutput { + channel_id, + serial_id: 4, + sats: funding_satoshis, + script: make_funding_redeemscript( + &open_channel_v2_msg.common_fields.funding_pubkey, + &accept_channel_v2_msg.common_fields.funding_pubkey, + ).to_p2wsh(), + }; + nodes[1].node.handle_tx_add_output(nodes[0].node.get_our_node_id(), &tx_add_output_msg); + + let _tx_complete_msg = get_event_msg!(nodes[1], MessageSendEvent::SendTxComplete, nodes[0].node.get_our_node_id()); + + let tx_complete_msg = TxComplete { + channel_id, + }; + + nodes[1].node.handle_tx_complete(nodes[0].node.get_our_node_id(), &tx_complete_msg); + let msg_events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(msg_events.len(), 1); + let _msg_commitment_signed_from_1 = match msg_events[0] { + MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => { + assert_eq!(*node_id, nodes[0].node.get_our_node_id()); + updates.commitment_signed.clone() + }, + _ => panic!("Unexpected event"), + }; + + let (funding_outpoint, channel_type_features) = { + let per_peer_state = nodes[1].node.per_peer_state.read().unwrap(); + let peer_state = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap(); + let channel_context = peer_state + .channel_by_id.get(&tx_complete_msg.channel_id).unwrap().context(); + (channel_context.get_funding_txo(), channel_context.get_channel_type().clone()) + }; + + let channel_transaction_parameters = ChannelTransactionParameters { + counterparty_parameters: Some(CounterpartyChannelTransactionParameters { + pubkeys: ChannelPublicKeys { + funding_pubkey: accept_channel_v2_msg.common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint(accept_channel_v2_msg.common_fields.revocation_basepoint), + payment_point: accept_channel_v2_msg.common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint(accept_channel_v2_msg.common_fields.delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint(accept_channel_v2_msg.common_fields.htlc_basepoint), + }, + selected_contest_delay: accept_channel_v2_msg.common_fields.to_self_delay, + }), + holder_pubkeys: ChannelPublicKeys { + funding_pubkey: open_channel_v2_msg.common_fields.funding_pubkey, + revocation_basepoint: RevocationBasepoint(open_channel_v2_msg.common_fields.revocation_basepoint), + payment_point: open_channel_v2_msg.common_fields.payment_basepoint, + delayed_payment_basepoint: DelayedPaymentBasepoint(open_channel_v2_msg.common_fields.delayed_payment_basepoint), + htlc_basepoint: HtlcBasepoint(open_channel_v2_msg.common_fields.htlc_basepoint), + }, + holder_selected_contest_delay: open_channel_v2_msg.common_fields.to_self_delay, + is_outbound_from_holder: true, + funding_outpoint, + channel_type_features, + }; + + channel.context.get_mut_signer().as_mut_ecdsa().unwrap().provide_channel_parameters(&channel_transaction_parameters); + + let msg_commitment_signed_from_0 = CommitmentSigned { + channel_id, + signature: channel.context.get_initial_counterparty_commitment_signature_for_test( + &&logger_a, + channel_transaction_parameters, + accept_channel_v2_msg.common_fields.first_per_commitment_point, + ).unwrap(), + htlc_signatures: vec![], + batch: None, + }; + + // Handle the initial commitment_signed exchange. Order is not important here. + nodes[1].node.handle_commitment_signed(nodes[0].node.get_our_node_id(), &msg_commitment_signed_from_0); + check_added_monitors(&nodes[1], 1); + + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + match events[0] { + Event::ChannelPending { channel_id, .. } => channel_id == channel.context.channel_id(), + _ => panic!("Unexpected event"), + }; + } + + #[test] + fn test_v2_channel_establishment() { + // Only initiator contributes + do_test_v2_channel_establishment(V2ChannelEstablishmentTestSession { + initiator_input_value_satoshis: 100_000, + }); + } } #[cfg(ldk_bench)] diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 7776966b285..c49a7523793 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -37,17 +37,20 @@ use crate::util::test_utils; use crate::util::test_utils::{TestChainMonitor, TestScorer, TestKeysInterface}; use crate::util::ser::{ReadableArgs, Writeable}; +use bitcoin::WPubkeyHash; use bitcoin::amount::Amount; -use bitcoin::block::{Block, Header, Version}; -use bitcoin::locktime::absolute::LockTime; -use bitcoin::transaction::{Transaction, TxIn, TxOut}; +use bitcoin::block::{Block, Header, Version as BlockVersion}; +use bitcoin::locktime::absolute::{LockTime, LOCK_TIME_THRESHOLD}; +use bitcoin::transaction::{Sequence, Transaction, TxIn, TxOut}; use bitcoin::hash_types::{BlockHash, TxMerkleNode}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash as _; use bitcoin::network::Network; use bitcoin::pow::CompactTarget; +use bitcoin::script::ScriptBuf; use bitcoin::secp256k1::{PublicKey, SecretKey}; -use bitcoin::transaction; +use bitcoin::transaction::{self, Version as TxVersion}; +use bitcoin::witness::Witness; use alloc::rc::Rc; use core::cell::RefCell; @@ -89,7 +92,7 @@ pub fn mine_transaction_without_consistency_checks<'a, 'b, 'c, 'd>(node: &'a Nod let height = node.best_block_info().1 + 1; let mut block = Block { header: Header { - version: Version::NO_SOFT_FORK_SIGNALLING, + version: BlockVersion::NO_SOFT_FORK_SIGNALLING, prev_blockhash: node.best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: height, @@ -216,7 +219,7 @@ impl ConnectStyle { pub fn create_dummy_header(prev_blockhash: BlockHash, time: u32) -> Header { Header { - version: Version::NO_SOFT_FORK_SIGNALLING, + version: BlockVersion::NO_SOFT_FORK_SIGNALLING, prev_blockhash, merkle_root: TxMerkleNode::all_zeros(), time, @@ -1220,6 +1223,37 @@ fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, } } +pub fn create_dual_funding_utxos_with_prev_txs( + node: &Node<'_, '_, '_>, utxo_values_in_satoshis: &[u64], +) -> Vec<(TxIn, Transaction)> { + // Ensure we have unique transactions per node by using the locktime. + let tx = Transaction { + version: TxVersion::TWO, + lock_time: LockTime::from_height( + u32::from_be_bytes(node.keys_manager.get_secure_random_bytes()[0..4].try_into().unwrap()) % LOCK_TIME_THRESHOLD + ).unwrap(), + input: vec![], + output: utxo_values_in_satoshis.iter().map(|value_satoshis| TxOut { + value: Amount::from_sat(*value_satoshis), script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()), + }).collect() + }; + + let mut result = vec![]; + for i in 0..utxo_values_in_satoshis.len() { + result.push( + (TxIn { + previous_output: OutPoint { + txid: tx.compute_txid(), + index: i as u16, + }.into_bitcoin_outpoint(), + script_sig: ScriptBuf::new(), + sequence: Sequence::ZERO, + witness: Witness::new(), + }, tx.clone())); + } + result +} + 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 { let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, &node_b.node.get_our_node_id(), channel_value, 42); assert_eq!(temporary_channel_id, expected_temporary_channel_id);