/// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
user_channel_id: u128,
},
+ /// Used to indicate that the counterparty node has provided the signature(s) required to
+ /// recover our funds in case they go offline.
+ ///
+ /// It is safe (and your responsibility) to broadcast the funding transaction upon receiving this
+ /// event.
+ ///
+ /// This event is only emitted if you called
+ /// [`ChannelManager::unsafe_manual_funding_transaction_generated`] instead of
+ /// [`ChannelManager::funding_transaction_generated`].
+ ///
+ /// [`ChannelManager::unsafe_manual_funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::unsafe_manual_funding_transaction_generated
+ /// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated
+ FundingTxBroadcastSafe {
+ /// The `channel_id` indicating which channel has reached this stage.
+ channel_id: ChannelId,
+ /// The `user_channel_id` value passed in to [`ChannelManager::create_channel`].
+ ///
+ /// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel
+ user_channel_id: u128,
+ /// The outpoint of the channel's funding transaction.
+ funding_txo: OutPoint,
+ /// The `node_id` of the channel counterparty.
+ counterparty_node_id: PublicKey,
+ /// The `temporary_channel_id` this channel used to be known by during channel establishment.
+ former_temporary_channel_id: ChannelId,
+ },
/// Indicates that we've been offered a payment and it needs to be claimed via calling
/// [`ChannelManager::claim_funds`] with the preimage given in [`PaymentPurpose`].
///
(0, payment_id, required),
(2, invoice, required),
(4, responder, option),
- })
+ });
+ },
+ &Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, ref funding_txo, ref counterparty_node_id, ref former_temporary_channel_id} => {
+ 43u8.write(writer)?;
+ write_tlv_fields!(writer, {
+ (0, channel_id, required),
+ (2, user_channel_id, required),
+ (4, funding_txo, required),
+ (6, counterparty_node_id, required),
+ (8, former_temporary_channel_id, required),
+ });
},
// Note that, going forward, all new events must only write data inside of
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
};
f()
},
+ 43u8 => {
+ let mut channel_id = RequiredWrapper(None);
+ let mut user_channel_id = RequiredWrapper(None);
+ let mut funding_txo = RequiredWrapper(None);
+ let mut counterparty_node_id = RequiredWrapper(None);
+ let mut former_temporary_channel_id = RequiredWrapper(None);
+ read_tlv_fields!(reader, {
+ (0, channel_id, required),
+ (2, user_channel_id, required),
+ (4, funding_txo, required),
+ (6, counterparty_node_id, required),
+ (8, former_temporary_channel_id, required)
+ });
+ Ok(Some(Event::FundingTxBroadcastSafe {
+ channel_id: channel_id.0.unwrap(),
+ user_channel_id: user_channel_id.0.unwrap(),
+ funding_txo: funding_txo.0.unwrap(),
+ counterparty_node_id: counterparty_node_id.0.unwrap(),
+ former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
+ }))
+ },
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
// reads.
counterparty_forwarding_info: Option<CounterpartyForwardingInfo>,
pub(crate) channel_transaction_parameters: ChannelTransactionParameters,
+ /// The transaction which funds this channel. Note that for manually-funded channels (i.e.,
+ /// is_manual_broadcast is true) this will be a dummy empty transaction.
funding_transaction: Option<Transaction>,
+ /// This flag indicates that it is the user's responsibility to validated and broadcast the
+ /// funding transaction.
+ is_manual_broadcast: bool,
is_batch_funding: Option<()>,
counterparty_cur_commitment_point: Option<PublicKey>,
// We track whether we already emitted a `ChannelPending` event.
channel_pending_event_emitted: bool,
+ // We track whether we already emitted a `FundingTxBroadcastSafe` event.
+ funding_tx_broadcast_safe_event_emitted: bool,
+
// We track whether we already emitted a `ChannelReady` event.
channel_ready_event_emitted: bool,
outbound_scid_alias: 0,
channel_pending_event_emitted: false,
+ funding_tx_broadcast_safe_event_emitted: false,
channel_ready_event_emitted: false,
#[cfg(any(test, fuzzing))]
local_initiated_shutdown: None,
blocked_monitor_updates: Vec::new(),
+
+ is_manual_broadcast: false,
};
Ok(channel_context)
outbound_scid_alias,
channel_pending_event_emitted: false,
+ funding_tx_broadcast_safe_event_emitted: false,
channel_ready_event_emitted: false,
#[cfg(any(test, fuzzing))]
blocked_monitor_updates: Vec::new(),
local_initiated_shutdown: None,
+ is_manual_broadcast: false,
})
}
self.config.options.forwarding_fee_proportional_millionths
}
+ pub fn is_manual_broadcast(&self) -> bool {
+ self.is_manual_broadcast
+ }
+
pub fn get_cltv_expiry_delta(&self) -> u16 {
cmp::max(self.config.options.cltv_expiry_delta, MIN_CLTV_EXPIRY_DELTA)
}
self.channel_pending_event_emitted
}
+ // Returns whether we already emitted a `FundingTxBroadcastSafe` event.
+ pub(crate) fn funding_tx_broadcast_safe_event_emitted(&self) -> bool {
+ self.funding_tx_broadcast_safe_event_emitted
+ }
+
// Remembers that we already emitted a `ChannelPending` event.
pub(crate) fn set_channel_pending_event_emitted(&mut self) {
self.channel_pending_event_emitted = true;
self.channel_ready_event_emitted = true;
}
+ // Remembers that we already emitted a `FundingTxBroadcastSafe` event.
+ pub(crate) fn set_funding_tx_broadcast_safe_event_emitted(&mut self) {
+ self.funding_tx_broadcast_safe_event_emitted = true;
+ }
+
/// Tracks the number of ticks elapsed since the previous [`ChannelConfig`] was updated. Once
/// [`EXPIRE_PREV_CONFIG_TICKS`] is reached, the previous config is considered expired and will
/// no longer be considered when forwarding HTLCs.
did_channel_update
}
+ /// Marking the channel as manual broadcast is used in order to prevent LDK from automatically
+ /// broadcasting the funding transaction.
+ ///
+ /// This is useful if you wish to get hold of the funding transaction before it is broadcasted
+ /// via [`Event::FundingTxBroadcastSafe`] event.
+ ///
+ /// [`Event::FundingTxBroadcastSafe`]: crate::events::Event::FundingTxBroadcastSafe
+ pub fn set_manual_broadcast(&mut self) {
+ self.is_manual_broadcast = true;
+ }
+
/// Returns true if funding_signed was sent/received and the
/// funding transaction has been broadcast if necessary.
pub fn is_funding_broadcast(&self) -> bool {
let channel_pending_event_emitted = Some(self.context.channel_pending_event_emitted);
let channel_ready_event_emitted = Some(self.context.channel_ready_event_emitted);
+ let funding_tx_broadcast_safe_event_emitted = Some(self.context.funding_tx_broadcast_safe_event_emitted);
// `user_id` used to be a single u64 value. In order to remain backwards compatible with
// versions prior to 0.0.113, the u128 is serialized as two separate u64 values. Therefore,
if !self.context.monitor_pending_update_adds.is_empty() {
monitor_pending_update_adds = Some(&self.context.monitor_pending_update_adds);
}
+ let is_manual_broadcast = Some(self.context.is_manual_broadcast);
// `current_point` will become optional when async signing is implemented.
let cur_holder_commitment_point = Some(self.context.holder_commitment_point.current_point());
(45, cur_holder_commitment_point, option),
(47, next_holder_commitment_point, option),
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122
+ (51, is_manual_broadcast, option), // Added in 0.0.124
+ (53, funding_tx_broadcast_safe_event_emitted, option) // Added in 0.0.124
});
Ok(())
let mut outbound_scid_alias = None;
let mut channel_pending_event_emitted = None;
let mut channel_ready_event_emitted = None;
+ let mut funding_tx_broadcast_safe_event_emitted = None;
let mut user_id_high_opt: Option<u64> = None;
let mut channel_keys_id: Option<[u8; 32]> = None;
let mut cur_holder_commitment_point_opt: Option<PublicKey> = None;
let mut next_holder_commitment_point_opt: Option<PublicKey> = None;
+ let mut is_manual_broadcast = None;
read_tlv_fields!(reader, {
(0, announcement_sigs, option),
(45, cur_holder_commitment_point_opt, option),
(47, next_holder_commitment_point_opt, option),
(49, local_initiated_shutdown, option),
+ (51, is_manual_broadcast, option),
+ (53, funding_tx_broadcast_safe_event_emitted, option),
});
let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id {
// Later in the ChannelManager deserialization phase we scan for channels and assign scid aliases if its missing
outbound_scid_alias: outbound_scid_alias.unwrap_or(0),
+ funding_tx_broadcast_safe_event_emitted: funding_tx_broadcast_safe_event_emitted.unwrap_or(false),
channel_pending_event_emitted: channel_pending_event_emitted.unwrap_or(true),
channel_ready_event_emitted: channel_ready_event_emitted.unwrap_or(true),
local_initiated_shutdown,
blocked_monitor_updates: blocked_monitor_updates.unwrap(),
+ is_manual_broadcast: is_manual_broadcast.unwrap_or(false),
},
#[cfg(any(dual_funding, splicing))]
dual_funding_channel_context: None,
htlcs: Vec<ClaimableHTLC>,
}
+/// Represent the channel funding transaction type.
+enum FundingType {
+ /// This variant is useful when we want LDK to validate the funding transaction and
+ /// broadcast it automatically.
+ ///
+ /// This is the normal flow.
+ Checked(Transaction),
+ /// This variant is useful when we want to loosen the validation checks and allow to
+ /// manually broadcast the funding transaction, leaving the responsibility to the caller.
+ ///
+ /// This is useful in cases of constructing the funding transaction as part of another
+ /// flow and the caller wants to perform the validation and broadcasting. An example of such
+ /// scenario could be when constructing the funding transaction as part of a Payjoin
+ /// transaction.
+ Unchecked(OutPoint),
+}
+
+impl FundingType {
+ fn txid(&self) -> Txid {
+ match self {
+ FundingType::Checked(tx) => tx.txid(),
+ FundingType::Unchecked(outp) => outp.txid,
+ }
+ }
+
+ fn transaction_or_dummy(&self) -> Transaction {
+ match self {
+ FundingType::Checked(tx) => tx.clone(),
+ FundingType::Unchecked(_) => Transaction {
+ version: bitcoin::transaction::Version::TWO,
+ lock_time: bitcoin::absolute::LockTime::ZERO,
+ input: Vec::new(),
+ output: Vec::new(),
+ },
+ }
+ }
+
+ fn is_manual_broadcast(&self) -> bool {
+ match self {
+ FundingType::Checked(_) => false,
+ FundingType::Unchecked(_) => true,
+ }
+ }
+}
+
/// Information about claimable or being-claimed payments
struct ClaimablePayments {
/// Map from payment hash to the payment data and any HTLCs which are to us and can be
}
}}
}
+macro_rules! emit_funding_tx_broadcast_safe_event {
+ ($locked_events: expr, $channel: expr, $funding_txo: expr) => {
+ if !$channel.context.funding_tx_broadcast_safe_event_emitted() {
+ $locked_events.push_back((events::Event::FundingTxBroadcastSafe {
+ channel_id: $channel.context.channel_id(),
+ user_channel_id: $channel.context.get_user_id(),
+ funding_txo: $funding_txo,
+ counterparty_node_id: $channel.context.get_counterparty_node_id(),
+ former_temporary_channel_id: $channel.context.temporary_channel_id()
+ .expect("Unreachable: FundingTxBroadcastSafe event feature added to channel establishment process in LDK v0.0.124 where this should never be None."),
+ }, None));
+ $channel.context.set_funding_tx_broadcast_safe_event_emitted();
+ }
+ }
+}
macro_rules! emit_channel_pending_event {
($locked_events: expr, $channel: expr) => {
/// Handles the generation of a funding transaction, optionally (for tests) with a function
/// which checks the correctness of the funding transaction given the associated channel.
- fn funding_transaction_generated_intern<FundingOutput: FnMut(&OutboundV1Channel<SP>, &Transaction) -> Result<OutPoint, &'static str>>(
+ fn funding_transaction_generated_intern<FundingOutput: FnMut(&OutboundV1Channel<SP>) -> Result<OutPoint, &'static str>>(
&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction, is_batch_funding: bool,
- mut find_funding_output: FundingOutput,
+ mut find_funding_output: FundingOutput, is_manual_broadcast: bool,
) -> Result<(), APIError> {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
let _: Result<(), _> = handle_error!(self, Err(err), counterparty);
Err($api_err)
} } }
- match find_funding_output(&chan, &funding_transaction) {
+ match find_funding_output(&chan) {
Ok(found_funding_txo) => funding_txo = found_funding_txo,
Err(err) => {
let chan_err = ChannelError::close(err.to_owned());
msg,
});
}
+ if is_manual_broadcast {
+ chan.context.set_manual_broadcast();
+ }
match peer_state.channel_by_id.entry(chan.context.channel_id()) {
hash_map::Entry::Occupied(_) => {
panic!("Generated duplicate funding txid?");
#[cfg(test)]
pub(crate) fn funding_transaction_generated_unchecked(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction, output_index: u16) -> Result<(), APIError> {
- self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, false, |_, tx| {
- Ok(OutPoint { txid: tx.txid(), index: output_index })
- })
+ let txid = funding_transaction.txid();
+ self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, false, |_| {
+ Ok(OutPoint { txid, index: output_index })
+ }, false)
}
/// Call this upon creation of a funding transaction for the given channel.
self.batch_funding_transaction_generated(&[(temporary_channel_id, counterparty_node_id)], funding_transaction)
}
+
+ /// **Unsafe**: This method does not validate the spent output. It is the caller's
+ /// responsibility to ensure the spent outputs are SegWit, as well as making sure the funding
+ /// transaction has a final absolute locktime, i.e., its locktime is lower than the next block height.
+ ///
+ /// For a safer method, please refer to [`ChannelManager::funding_transaction_generated`].
+ ///
+ /// Call this in response to a [`Event::FundingGenerationReady`] event.
+ ///
+ /// Note that if this method is called successfully, the funding transaction won't be
+ /// broadcasted and you are expected to broadcast it manually when receiving the
+ /// [`Event::FundingTxBroadcastSafe`] event.
+ ///
+ /// Returns [`APIError::ChannelUnavailable`] if a funding transaction has already been provided
+ /// for the channel or if the channel has been closed as indicated by [`Event::ChannelClosed`].
+ ///
+ /// May panic if the funding output is duplicative with some other channel (note that this
+ /// should be trivially prevented by using unique funding transaction keys per-channel).
+ ///
+ /// Note to keep the miner incentives aligned in moving the blockchain forward, we recommend
+ /// the wallet software generating the funding transaction to apply anti-fee sniping as
+ /// implemented by Bitcoin Core wallet. See <https://bitcoinops.org/en/topics/fee-sniping/> for
+ /// more details.
+ ///
+ /// [`Event::FundingGenerationReady`]: crate::events::Event::FundingGenerationReady
+ /// [`Event::FundingTxBroadcastSafe`]: crate::events::Event::FundingTxBroadcastSafe
+ /// [`Event::ChannelClosed`]: crate::events::Event::ChannelClosed
+ /// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated
+ pub fn unsafe_manual_funding_transaction_generated(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding: OutPoint) -> Result<(), APIError> {
+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
+
+ let temporary_channels = &[(temporary_channel_id, counterparty_node_id)];
+ return self.batch_funding_transaction_generated_intern(temporary_channels, FundingType::Unchecked(funding));
+
+ }
+
/// Call this upon creation of a batch funding transaction for the given channels.
///
/// Return values are identical to [`Self::funding_transaction_generated`], respective to
}
}
}
- if funding_transaction.output.len() > u16::max_value() as usize {
- result = result.and(Err(APIError::APIMisuseError {
- err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
- }));
- }
- {
+ result.and(self.batch_funding_transaction_generated_intern(temporary_channels, FundingType::Checked(funding_transaction)))
+ }
+
+ fn batch_funding_transaction_generated_intern(&self, temporary_channels: &[(&ChannelId, &PublicKey)], funding: FundingType) -> Result<(), APIError> {
+ let mut result = Ok(());
+ if let FundingType::Checked(funding_transaction) = &funding {
+ if funding_transaction.output.len() > u16::max_value() as usize {
+ result = result.and(Err(APIError::APIMisuseError {
+ err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
+ }));
+ }
let height = self.best_block.read().unwrap().height;
// Transactions are evaluated as final by network mempools if their locktime is strictly
// lower than the next block height. However, the modules constituting our Lightning
// module is ahead of LDK, only allow one more block of headroom.
if !funding_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) &&
funding_transaction.lock_time.is_block_height() &&
- funding_transaction.lock_time.to_consensus_u32() > height + 1
+ funding_transaction.lock_time.to_consensus_u32() > height + 1
{
result = result.and(Err(APIError::APIMisuseError {
err: "Funding transaction absolute timelock is non-final".to_owned()
}
}
- let txid = funding_transaction.txid();
+ let txid = funding.txid();
let is_batch_funding = temporary_channels.len() > 1;
let mut funding_batch_states = if is_batch_funding {
Some(self.funding_batch_states.lock().unwrap())
btree_map::Entry::Vacant(vacant) => Some(vacant.insert(Vec::new())),
}
});
+ let is_manual_broadcast = funding.is_manual_broadcast();
for &(temporary_channel_id, counterparty_node_id) in temporary_channels {
result = result.and_then(|_| self.funding_transaction_generated_intern(
temporary_channel_id,
counterparty_node_id,
- funding_transaction.clone(),
+ funding.transaction_or_dummy(),
is_batch_funding,
- |chan, tx| {
+ |chan| {
let mut output_index = None;
let expected_spk = chan.context.get_funding_redeemscript().to_p2wsh();
- for (idx, outp) in tx.output.iter().enumerate() {
- if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.context.get_value_satoshis() {
- if output_index.is_some() {
- return Err("Multiple outputs matched the expected script and value");
+ let outpoint = match &funding {
+ FundingType::Checked(tx) => {
+ for (idx, outp) in tx.output.iter().enumerate() {
+ if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.context.get_value_satoshis() {
+ if output_index.is_some() {
+ return Err("Multiple outputs matched the expected script and value");
+ }
+ output_index = Some(idx as u16);
+ }
}
- output_index = Some(idx as u16);
- }
- }
- if output_index.is_none() {
- return Err("No output matched the script_pubkey and value in the FundingGenerationReady event");
- }
- let outpoint = OutPoint { txid: tx.txid(), index: output_index.unwrap() };
+ if output_index.is_none() {
+ return Err("No output matched the script_pubkey and value in the FundingGenerationReady event");
+ }
+ OutPoint { txid, index: output_index.unwrap() }
+ },
+ FundingType::Unchecked(outpoint) => outpoint.clone(),
+ };
if let Some(funding_batch_state) = funding_batch_state.as_mut() {
// TODO(dual_funding): We only do batch funding for V1 channels at the moment, but we'll probably
// need to fix this somehow to not rely on using the outpoint for the channel ID if we
funding_batch_state.push((ChannelId::v1_from_funding_outpoint(outpoint), *counterparty_node_id, false));
}
Ok(outpoint)
- })
+ },
+ is_manual_broadcast)
);
}
if let Err(ref e) = result {
}
if let Some(tx) = funding_broadcastable {
- log_info!(logger, "Broadcasting funding transaction with txid {}", tx.txid());
- self.tx_broadcaster.broadcast_transactions(&[&tx]);
+ if channel.context.is_manual_broadcast() {
+ log_info!(logger, "Not broadcasting funding transaction with txid {} as it is manually managed", tx.txid());
+ let mut pending_events = self.pending_events.lock().unwrap();
+ match channel.context.get_funding_txo() {
+ Some(funding_txo) => {
+ emit_funding_tx_broadcast_safe_event!(pending_events, channel, funding_txo.into_bitcoin_outpoint())
+ },
+ None => {
+ debug_assert!(false, "Channel resumed without a funding txo, this should never happen!");
+ return (htlc_forwards, decode_update_add_htlcs);
+ }
+ };
+ } else {
+ log_info!(logger, "Broadcasting funding transaction with txid {}", tx.txid());
+ self.tx_broadcaster.broadcast_transactions(&[&tx]);
+ }
}
{
assert_eq!(get_err_msg(&nodes[1], &nodes[0].node.get_our_node_id()).channel_id,
open_channel_msg.common_fields.temporary_channel_id);
}
+
+#[test]
+fn test_funding_signed_event() {
+ let mut cfg = UserConfig::default();
+ cfg.channel_handshake_config.minimum_depth = 1;
+ 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, &[Some(cfg), Some(cfg)]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ assert!(nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None, None).is_ok());
+ let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+
+ nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
+ let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+
+ nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
+ let (temporary_channel_id, tx, funding_outpoint) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100_000, 42);
+ nodes[0].node.unsafe_manual_funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), funding_outpoint).unwrap();
+ check_added_monitors!(nodes[0], 0);
+
+ let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
+ nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created);
+ check_added_monitors!(nodes[1], 1);
+ expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
+
+ let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
+ nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed);
+ check_added_monitors!(nodes[0], 1);
+ let events = &nodes[0].node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 2);
+ match &events[0] {
+ crate::events::Event::FundingTxBroadcastSafe { funding_txo, .. } => {
+ assert_eq!(funding_txo.txid, funding_outpoint.txid);
+ assert_eq!(funding_txo.vout, funding_outpoint.index.into());
+ },
+ _ => panic!("Unexpected event"),
+ };
+ match &events[1] {
+ crate::events::Event::ChannelPending { counterparty_node_id, .. } => {
+ assert_eq!(*&nodes[1].node.get_our_node_id(), *counterparty_node_id);
+ },
+ _ => panic!("Unexpected event"),
+ };
+
+ mine_transaction(&nodes[0], &tx);
+ mine_transaction(&nodes[1], &tx);
+
+ let as_channel_ready = get_event_msg!(nodes[1], MessageSendEvent::SendChannelReady, nodes[0].node.get_our_node_id());
+ nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &as_channel_ready);
+ let as_channel_ready = get_event_msg!(nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id());
+ nodes[0].node.handle_channel_ready(&nodes[1].node.get_our_node_id(), &as_channel_ready);
+
+ expect_channel_ready_event(&nodes[0], &nodes[1].node.get_our_node_id());
+ expect_channel_ready_event(&nodes[1], &nodes[0].node.get_our_node_id());
+ nodes[0].node.get_and_clear_pending_msg_events();
+ nodes[1].node.get_and_clear_pending_msg_events();
+}
+