/// [`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`] for outbound
+ /// channels, or to [`ChannelManager::accept_inbound_channel`] for inbound channels if
+ /// [`UserConfig::manually_accept_inbound_channels`] config flag is set to true. Otherwise
+ /// `user_channel_id` will be randomized for an inbound channel.
+ ///
+ /// [`ChannelManager::create_channel`]: crate::ln::channelmanager::ChannelManager::create_channel
+ /// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
+ /// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
+ user_channel_id: u128,
+ /// Channel funding transaction
+ funding_tx: Transaction,
+ /// 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`].
///
write_tlv_fields!(writer, {
(0, peer_node_id, required),
});
- }
+ },
+ &Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, ref funding_tx, ref counterparty_node_id, ref former_temporary_channel_id} => {
+ 41u8.write(writer)?;
+ write_tlv_fields!(writer, {
+ (0, channel_id, required),
+ (2, user_channel_id, required),
+ (4, funding_tx, 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
// data via `write_tlv_fields`.
};
f()
},
+ 41u8 => {
+ let mut channel_id = RequiredWrapper(None);
+ let mut user_channel_id = RequiredWrapper(None);
+ let mut funding_tx = 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_tx, 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_tx: funding_tx.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.
// 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,
/// If we can't release a [`ChannelMonitorUpdate`] until some external action completes, we
/// store it here and only release it to the `ChannelManager` once it asks for it.
blocked_monitor_updates: Vec<PendingChannelMonitorUpdate>,
+
+ /// Using this flag will prevent the funding transaction from being broadcasted
+ /// and will allow the user to manually broadcast it.
+ ///
+ /// The funding transaction can be accessed through the [`Event::FundingTxBroadcastSafe`] event.
+ ///
+ /// [`Event::FundingTxBroadcastSafe`]: crate::events::Event::FundingTxBroadcastSafe
+ is_manual_broadcast: bool,
}
impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
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),
+ (53, funding_tx_broadcast_safe_event_emitted, option)
});
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,
}
}}
}
+macro_rules! emit_funding_tx_broadcast_safe_event {
+ ($locked_events: expr, $channel: expr, $funding_tx: 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_tx: $funding_tx,
+ 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.124.0 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) => {
/// 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>>(
&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)
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?");
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 })
- })
+ }, 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 check if the funding transaction is signed, i.e., if the
+ /// witness data is empty or not. It is the caller's responsibility to ensure that the funding
+ /// transaction is final.
+ ///
+ /// If you wish to use a safer method, use [`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 an [`APIError::APIMisuseError`] if no output was found which matches the parameters
+ /// in [`Event::FundingGenerationReady`].
+ ///
+ /// 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 output found in the funding transaction 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_transaction: Transaction) -> 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, funding_transaction, true);
+
+ }
+
/// Call this upon creation of a batch funding transaction for the given channels.
///
/// Return values are identical to [`Self::funding_transaction_generated`], respective to
}
}
}
+ result.and(self.batch_funding_transaction_generated_intern(temporary_channels, funding_transaction, false))
+ }
+
+ fn batch_funding_transaction_generated_intern(&self, temporary_channels: &[(&ChannelId, &PublicKey)], funding_transaction: Transaction, is_manual_broadcast: bool) -> Result<(), APIError> {
+ let mut result = Ok(());
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()
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();
+ emit_funding_tx_broadcast_safe_event!(pending_events, channel, tx);
+ } else {
+ log_info!(logger, "Broadcasting funding transaction with txid {}", tx.txid());
+ self.tx_broadcaster.broadcast_transactions(&[&tx]);
+ }
}
{
internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, true)
}
+pub fn create_funding_tx_without_witness_data<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
+ expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128) -> (ChannelId, Transaction, OutPoint) {
+ let chan_id = *node.network_chan_count.borrow();
+
+ let events = node.node.get_and_clear_pending_events();
+ assert_eq!(events.len(), 1);
+ match events[0] {
+ Event::FundingGenerationReady { ref temporary_channel_id, ref counterparty_node_id, ref channel_value_satoshis, ref output_script, user_channel_id } => {
+ assert_eq!(counterparty_node_id, expected_counterparty_node_id);
+ assert_eq!(*channel_value_satoshis, expected_chan_value);
+ assert_eq!(user_channel_id, expected_user_chan_id);
+
+ let dummy_outpoint = bitcoin::OutPoint { txid: bitcoin::Txid::from_slice(&[1; 32]).unwrap(), vout: 0 };
+ let dummy_script_sig = bitcoin::ScriptBuf::new();
+ let dummy_sequence = bitcoin::Sequence::ZERO;
+ let dummy_witness = bitcoin::Witness::new();
+ let input = vec![TxIn {
+ previous_output: dummy_outpoint,
+ script_sig: dummy_script_sig,
+ sequence: dummy_sequence,
+ witness: dummy_witness,
+ }];
+
+ let tx = Transaction { version: transaction::Version(chan_id as i32), lock_time: LockTime::ZERO, input, output: vec![TxOut {
+ value: Amount::from_sat(*channel_value_satoshis), script_pubkey: output_script.clone(),
+ }]};
+ let funding_outpoint = OutPoint { txid: tx.txid(), index: 0 };
+ (*temporary_channel_id, tx, funding_outpoint)
+ },
+ _ => panic!("Unexpected event"),
+ }
+}
+
fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128,
coinbase: bool) -> (ChannelId, Transaction, OutPoint) {
, [nodes[0].node.get_our_node_id()], 1000000);
}
+#[test]
+fn test_unsafe_manual_funding_transaction_generated() {
+ 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 expected_temporary_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 1_000_000, 500_000_000, 42, None, None).unwrap();
+ 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_output) = create_funding_tx_without_witness_data(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
+ assert_eq!(temporary_channel_id, expected_temporary_channel_id);
+
+ assert!(nodes[0].node.unsafe_manual_funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).is_ok());
+ let node_0_msg_events = nodes[0].node.get_and_clear_pending_msg_events();
+ match node_0_msg_events[0] {
+ MessageSendEvent::SendFundingCreated { ref node_id, .. } => {
+ assert_eq!(node_id, &nodes[1].node.get_our_node_id());
+ },
+ _ => panic!("Unexpected event"),
+ }
+}
+
#[test]
fn test_simple_peer_disconnect() {
// Test that we can reconnect when there are no lost messages
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, _) = 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(), tx.clone()).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_tx, .. } => {
+ assert_eq!(funding_tx.txid(), funding_created.funding_txid);
+ },
+ _ => 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();
+}
+