From 0bdd4ccc1710170abaa610326ad75a2e2bc134c0 Mon Sep 17 00:00:00 2001 From: Alec Chen Date: Mon, 10 Jun 2024 17:23:06 -0700 Subject: [PATCH] Add test for async open and accept channel --- lightning/src/ln/async_signer_tests.rs | 98 ++++++++++++++++++++--- lightning/src/ln/channel.rs | 26 +++--- lightning/src/ln/functional_test_utils.rs | 5 ++ lightning/src/util/test_utils.rs | 6 ++ 4 files changed, 110 insertions(+), 25 deletions(-) diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs index f32e455192a..d39a5e012a0 100644 --- a/lightning/src/ln/async_signer_tests.rs +++ b/lightning/src/ln/async_signer_tests.rs @@ -24,7 +24,59 @@ use crate::ln::channelmanager::{PaymentId, RAACommitmentOrder, RecipientOnionFie use crate::util::test_channel_signer::SignerOp; #[test] -fn test_async_commitment_signature_for_funding_created() { +fn test_open_channel() { + // Simulate acquiring the signature for `funding_created` asynchronously. + 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); + + // Open an outbound channel simulating an async signer. + let channel_value_satoshis = 100000; + let user_channel_id = 42; + nodes[0].disable_next_channel_signer_op(SignerOp::GetPerCommitmentPoint); + let channel_id_0 = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), channel_value_satoshis, 10001, user_channel_id, None, None).unwrap(); + + { + let msgs = nodes[0].node.get_and_clear_pending_msg_events(); + assert!(msgs.is_empty(), "Expected no message events; got {:?}", msgs); + } + + nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &channel_id_0, SignerOp::GetPerCommitmentPoint); + nodes[0].node.signer_unblocked(None); + + // nodes[0] --- open_channel --> nodes[1] + let mut open_chan_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()); + + // Handle an inbound channel simulating an async signer. + nodes[1].disable_next_channel_signer_op(SignerOp::GetPerCommitmentPoint); + nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_chan_msg); + + { + let msgs = nodes[1].node.get_and_clear_pending_msg_events(); + assert!(msgs.is_empty(), "Expected no message events; got {:?}", msgs); + } + + let channel_id_1 = { + let channels = nodes[1].node.list_channels(); + assert_eq!(channels.len(), 1, "expected one channel, not {}", channels.len()); + channels[0].channel_id + }; + + nodes[1].enable_channel_signer_op(&nodes[0].node.get_our_node_id(), &channel_id_1, SignerOp::GetPerCommitmentPoint); + nodes[1].node.signer_unblocked(None); + + // nodes[0] <-- accept_channel --- nodes[1] + get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()); +} + +#[test] +fn test_funding_created() { + do_test_funding_created(vec![SignerOp::SignCounterpartyCommitment, SignerOp::GetPerCommitmentPoint]); + do_test_funding_created(vec![SignerOp::GetPerCommitmentPoint, SignerOp::SignCounterpartyCommitment]); +} + +fn do_test_funding_created(signer_ops: Vec) { // Simulate acquiring the signature for `funding_created` asynchronously. let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); @@ -45,7 +97,9 @@ fn test_async_commitment_signature_for_funding_created() { // But! Let's make node[0]'s signer be unavailable: we should *not* broadcast a funding_created // message... let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42); - nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment); + for op in signer_ops.iter() { + nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, *op); + } nodes[0].node.funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap(); check_added_monitors(&nodes[0], 0); @@ -59,8 +113,10 @@ fn test_async_commitment_signature_for_funding_created() { channels[0].channel_id }; - nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment); - nodes[0].node.signer_unblocked(Some((nodes[1].node.get_our_node_id(), chan_id))); + for op in signer_ops.iter() { + nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, *op); + nodes[0].node.signer_unblocked(Some((nodes[1].node.get_our_node_id(), chan_id))); + } let mut funding_created_msg = 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_msg); @@ -75,7 +131,12 @@ fn test_async_commitment_signature_for_funding_created() { } #[test] -fn test_async_commitment_signature_for_funding_signed() { +fn test_funding_signed() { + do_test_funding_signed(vec![SignerOp::SignCounterpartyCommitment, SignerOp::GetPerCommitmentPoint]); + do_test_funding_signed(vec![SignerOp::GetPerCommitmentPoint, SignerOp::SignCounterpartyCommitment]); +} + +fn do_test_funding_signed(signer_ops: Vec) { // Simulate acquiring the signature for `funding_signed` asynchronously. let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); @@ -100,7 +161,9 @@ fn test_async_commitment_signature_for_funding_signed() { // Now let's make node[1]'s signer be unavailable while handling the `funding_created`. It should // *not* broadcast a `funding_signed`... - nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment); + for op in signer_ops.iter() { + nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, *op); + } nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg); check_added_monitors(&nodes[1], 1); @@ -113,8 +176,10 @@ fn test_async_commitment_signature_for_funding_signed() { assert_eq!(channels.len(), 1, "expected one channel, not {}", channels.len()); channels[0].channel_id }; - nodes[1].enable_channel_signer_op(&nodes[0].node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment); - nodes[1].node.signer_unblocked(Some((nodes[0].node.get_our_node_id(), chan_id))); + for op in signer_ops.iter() { + nodes[1].enable_channel_signer_op(&nodes[0].node.get_our_node_id(), &chan_id, *op); + nodes[1].node.signer_unblocked(Some((nodes[0].node.get_our_node_id(), chan_id))); + } expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id()); @@ -189,7 +254,12 @@ fn do_test_async_commitment_signature_for_commitment_signed_revoke_and_ack(test_ } #[test] -fn test_async_commitment_signature_for_funding_signed_0conf() { +fn test_funding_signed_0conf() { + do_test_funding_signed_0conf(vec![SignerOp::SignCounterpartyCommitment, SignerOp::SignCounterpartyCommitment]); + do_test_funding_signed_0conf(vec![SignerOp::SignCounterpartyCommitment, SignerOp::GetPerCommitmentPoint]); +} + +fn do_test_funding_signed_0conf(signer_ops: Vec) { // Simulate acquiring the signature for `funding_signed` asynchronously for a zero-conf channel. let mut manually_accept_config = test_default_channel_config(); manually_accept_config.manually_accept_inbound_channels = true; @@ -232,7 +302,9 @@ fn test_async_commitment_signature_for_funding_signed_0conf() { // Now let's make node[1]'s signer be unavailable while handling the `funding_created`. It should // *not* broadcast a `funding_signed`... - nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment); + for op in signer_ops.iter() { + nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, *op); + } nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg); check_added_monitors(&nodes[1], 1); @@ -247,8 +319,10 @@ fn test_async_commitment_signature_for_funding_signed_0conf() { }; // At this point, we basically expect the channel to open like a normal zero-conf channel. - nodes[1].enable_channel_signer_op(&nodes[0].node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment); - nodes[1].node.signer_unblocked(Some((nodes[0].node.get_our_node_id(), chan_id))); + for op in signer_ops.iter() { + nodes[1].enable_channel_signer_op(&nodes[0].node.get_our_node_id(), &chan_id, *op); + nodes[1].node.signer_unblocked(Some((nodes[0].node.get_our_node_id(), chan_id))); + } let (funding_signed, channel_ready_1) = { let events = nodes[1].node.get_and_clear_pending_msg_events(); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 38fe095ddf6..0b92032c069 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -7638,7 +7638,19 @@ impl OutboundV1Channel where SP::Target: SignerProvider { // TODO (taproot|arik): move match into calling method for Taproot ChannelSignerType::Ecdsa(ecdsa) => { ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &self.context.secp_ctx) - .map(|(sig, _)| sig).ok()? + .map(|(sig, _)| sig) + .map_err(|()| { + #[cfg(not(async_signing))] { + panic!("Failed to get signature for new funding creation"); + } + #[cfg(async_signing)] { + if !self.context.signer_pending_funding { + log_trace!(logger, "funding_created awaiting signer; setting signer_pending_funding"); + self.context.signer_pending_funding = true; + } + } + }) + .ok()? }, // TODO (taproot|arik) #[cfg(taproot)] @@ -7701,18 +7713,6 @@ impl OutboundV1Channel where SP::Target: SignerProvider { self.context.is_batch_funding = Some(()).filter(|_| is_batch_funding); let funding_created = self.get_funding_created_msg(logger); - if funding_created.is_none() { - #[cfg(not(async_signing))] { - panic!("Failed to get signature for new funding creation"); - } - #[cfg(async_signing)] { - if !self.context.signer_pending_funding { - log_trace!(logger, "funding_created awaiting signer; setting signer_pending_funding"); - self.context.signer_pending_funding = true; - } - } - } - Ok(funding_created) } diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 7f1ac4226ea..b5a59d94fc1 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -554,6 +554,11 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> { entry.insert(signer_op); }; } + + #[cfg(test)] + pub fn disable_next_channel_signer_op(&self, signer_op: SignerOp) { + self.keys_manager.next_signer_disabled_ops.lock().unwrap().insert(signer_op); + } } /// If we need an unsafe pointer to a `Node` (ie to reference it in a thread diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 4b2c3c2e374..1eeec306f64 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -1225,6 +1225,7 @@ pub struct TestKeysInterface { expectations: Mutex>>, pub unavailable_signers: Mutex>, pub unavailable_signers_ops: Mutex>>, + pub next_signer_disabled_ops: Mutex>, } impl EntropySource for TestKeysInterface { @@ -1289,6 +1290,10 @@ impl SignerProvider for TestKeysInterface { signer.disable_op(op); } } + #[cfg(test)] + for op in self.next_signer_disabled_ops.lock().unwrap().drain() { + signer.disable_op(op); + } signer } @@ -1329,6 +1334,7 @@ impl TestKeysInterface { expectations: Mutex::new(None), unavailable_signers: Mutex::new(new_hash_set()), unavailable_signers_ops: Mutex::new(new_hash_map()), + next_signer_disabled_ops: Mutex::new(new_hash_set()), } }