Skip to content

Commit

Permalink
Add test for async open and accept channel
Browse files Browse the repository at this point in the history
  • Loading branch information
alecchendev committed Jul 3, 2024
1 parent 1fcf4f1 commit 0ac4ad8
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 25 deletions.
98 changes: 86 additions & 12 deletions lightning/src/ln/async_signer_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SignerOp>) {
// Simulate acquiring the signature for `funding_created` asynchronously.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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<SignerOp>) {
// Simulate acquiring the signature for `funding_signed` asynchronously.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
Expand All @@ -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);

Expand All @@ -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());

Expand Down Expand Up @@ -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<SignerOp>) {
// 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;
Expand Down Expand Up @@ -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);

Expand All @@ -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();
Expand Down
26 changes: 13 additions & 13 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7640,7 +7640,19 @@ impl<SP: Deref> OutboundV1Channel<SP> 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)]
Expand Down Expand Up @@ -7708,18 +7720,6 @@ impl<SP: Deref> OutboundV1Channel<SP> 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)
}

Expand Down
5 changes: 5 additions & 0 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions lightning/src/util/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,7 @@ pub struct TestKeysInterface {
expectations: Mutex<Option<VecDeque<OnGetShutdownScriptpubkey>>>,
pub unavailable_signers: Mutex<HashSet<[u8; 32]>>,
pub unavailable_signers_ops: Mutex<HashMap<[u8; 32], HashSet<SignerOp>>>,
pub next_signer_disabled_ops: Mutex<HashSet<SignerOp>>,
}

impl EntropySource for TestKeysInterface {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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()),
}
}

Expand Down

0 comments on commit 0ac4ad8

Please sign in to comment.