Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -396,12 +396,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
available: Arc::new(Mutex::new(true)),
})
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1924,9 +1924,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
pub fn do_mut_signer_call<F: FnMut(&mut Signer) -> ()>(&self, mut f: F) {
let mut inner = self.inner.lock().unwrap();
f(&mut inner.onchain_tx_handler.signer);
}
}

Expand Down
55 changes: 39 additions & 16 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::util::test_channel_signer::SignerOp;

#[test]
fn test_async_commitment_signature_for_funding_created() {
Expand All@@ -43,7 +44,7 @@ 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].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &temporary_channel_id, false);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
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@@ -57,7 +58,7 @@ fn test_async_commitment_signature_for_funding_created() {
channels[0].channel_id
};

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
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)));

let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand DownExpand Up@@ -98,7 +99,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -111,7 +112,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
Expand DownExpand Up@@ -152,14 +153,14 @@ fn test_async_commitment_signature_for_commitment_signed() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

let events = dst.node.get_and_clear_pending_msg_events();
Expand DownExpand Up@@ -215,7 +216,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -230,7 +231,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

let (funding_signed, channel_ready_1) = {
Expand DownExpand Up@@ -299,7 +300,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

Expand All@@ -314,7 +315,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {
reconnect_nodes(reconnect_args);

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

{
Expand DownExpand Up@@ -366,7 +367,6 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let error_message = "Channel force-closed";

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);

if remote_commitment {
// Make the counterparty broadcast its latest commitment.
Expand All@@ -375,6 +375,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
check_closed_broadcast(&nodes[1], 1, true);
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, false, &[nodes[0].node.get_our_node_id()], 100_000);
} else {
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience we'll probably want some wrapper methods that set/clear everything, no matter the internal representation.

// We'll connect blocks until the sender has to go onchain to time out the HTLC.
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);

Expand All@@ -383,7 +385,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());

// Mark it as available now, we should see the signed commitment transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);
}

Expand All@@ -409,7 +412,13 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

// Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such
// that the HTLC transaction is retried.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);
let sign_htlc_op = if remote_commitment {
SignerOp::SignCounterpartyHtlcTransaction
} else {
SignerOp::SignHolderHtlcTransaction
};
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
mine_transaction(&nodes[0], &commitment_tx);

check_added_monitors(&nodes[0], 1);
Expand All@@ -426,10 +435,12 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
if anchors && !remote_commitment {
handle_bump_htlc_event(&nodes[0], 1);
}
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn);

// Mark it as available now, we should see the signed HTLC transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);

if anchors && !remote_commitment {
Expand All@@ -443,9 +454,21 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
}

#[test]
fn test_async_holder_signatures() {
fn test_async_holder_signatures_no_anchors() {
do_test_async_holder_signatures(false, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_no_anchors() {
do_test_async_holder_signatures(false, true);
}

#[test]
fn test_async_holder_signatures_anchors() {
do_test_async_holder_signatures(true, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2118,8 +2118,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

/// Returns the holder signer for this channel.
#[cfg(test)]
pub fn get_signer(&self) -> &ChannelSignerType<SP> {
return &self.holder_signer
pub fn get_mut_signer(&mut self) -> &mut ChannelSignerType<SP> {
return &mut self.holder_signer
}

/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
Expand Down
74 changes: 52 additions & 22 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::scid_utils;
use crate::util::test_channel_signer::TestChannelSigner;
#[cfg(test)]
use crate::util::test_channel_signer::SignerOp;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};
Expand DownExpand Up@@ -482,46 +484,74 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> Header {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.

/// Toggles this node's signer to be available for the given signer operation.
/// This is useful for testing behavior for restoring an async signer that previously
/// could not return a signature immediately.
#[cfg(test)]
pub fn enable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, true);
}

/// Toggles this node's signer to be unavailable, returning `Err` for the given signer operation.
/// This is useful for testing behavior for an async signer that cannot return a signature
/// immediately.
#[cfg(test)]
pub fn disable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, false);
}

/// Changes the channel signer's availability for the specified peer, channel, and signer
/// operation.
///
/// When `available` is set to `true`, the channel signer will behave normally. When set to
/// `false`, the channel signer will act like an off-line remote signer and will return `Err` for
/// several of the signing methods. Currently, only `get_per_commitment_point` and
/// `release_commitment_secret` are affected by this setting.
/// For the specified signer operation, when `available` is set to `true`, the channel signer
/// will behave normally, returning `Ok`. When set to `false`, and the channel signer will
/// act like an off-line remote signer, returning `Err`. This applies to the signer in all
/// relevant places, i.e. the channel manager, chain monitor, and the keys manager.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
fn set_channel_signer_ops(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp, available: bool) {
use crate::sign::ChannelSigner;
log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available);

let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let mut chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();

let mut channel_keys_id = None;
if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) {
chan.get_signer().as_ecdsa().unwrap().set_available(available);
if let Some(chan) = chan_lock.channel_by_id.get_mut(chan_id).map(|phase| phase.context_mut()) {
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
channel_keys_id = Some(chan.channel_keys_id);
}

let mut monitor = None;
for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() {
if *chan_id == channel_id {
monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok();
}
}
let monitor = self.chain_monitor.chain_monitor.list_monitors().into_iter()
.find(|(_, channel_id)| *channel_id == *chan_id)
.and_then(|(funding_txo, _)| self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok());
if let Some(monitor) = monitor {
monitor.do_signer_call(|signer| {
monitor.do_mut_signer_call(|signer| {
channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id()));
signer.set_available(available)
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
});
}

let channel_keys_id = channel_keys_id.unwrap();
let mut unavailable_signers_ops = self.keys_manager.unavailable_signers_ops.lock().unwrap();
let entry = unavailable_signers_ops.entry(channel_keys_id).or_insert(new_hash_set());
if available {
self.keys_manager.unavailable_signers.lock().unwrap()
.remove(channel_keys_id.as_ref().unwrap());
entry.remove(&signer_op);
if entry.is_empty() {
unavailable_signers_ops.remove(&channel_keys_id);
}
} else {
self.keys_manager.unavailable_signers.lock().unwrap()
.insert(channel_keys_id.unwrap());
}
entry.insert(signer_op);
};
}
}

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -396,12 +396,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
available: Arc::new(Mutex::new(true)),
})
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1924,9 +1924,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
pub fn do_mut_signer_call<F: FnMut(&mut Signer) -> ()>(&self, mut f: F) {
let mut inner = self.inner.lock().unwrap();
f(&mut inner.onchain_tx_handler.signer);
}
}

Expand Down
55 changes: 39 additions & 16 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::util::test_channel_signer::SignerOp;

#[test]
fn test_async_commitment_signature_for_funding_created() {
Expand All@@ -43,7 +44,7 @@ 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].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &temporary_channel_id, false);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
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@@ -57,7 +58,7 @@ fn test_async_commitment_signature_for_funding_created() {
channels[0].channel_id
};

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
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)));

let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand DownExpand Up@@ -98,7 +99,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -111,7 +112,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
Expand DownExpand Up@@ -152,14 +153,14 @@ fn test_async_commitment_signature_for_commitment_signed() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

let events = dst.node.get_and_clear_pending_msg_events();
Expand DownExpand Up@@ -215,7 +216,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -230,7 +231,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

let (funding_signed, channel_ready_1) = {
Expand DownExpand Up@@ -299,7 +300,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

Expand All@@ -314,7 +315,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {
reconnect_nodes(reconnect_args);

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

{
Expand DownExpand Up@@ -366,7 +367,6 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let error_message = "Channel force-closed";

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);

if remote_commitment {
// Make the counterparty broadcast its latest commitment.
Expand All@@ -375,6 +375,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
check_closed_broadcast(&nodes[1], 1, true);
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, false, &[nodes[0].node.get_our_node_id()], 100_000);
} else {
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience we'll probably want some wrapper methods that set/clear everything, no matter the internal representation.

// We'll connect blocks until the sender has to go onchain to time out the HTLC.
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);

Expand All@@ -383,7 +385,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());

// Mark it as available now, we should see the signed commitment transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);
}

Expand All@@ -409,7 +412,13 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

// Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such
// that the HTLC transaction is retried.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);
let sign_htlc_op = if remote_commitment {
SignerOp::SignCounterpartyHtlcTransaction
} else {
SignerOp::SignHolderHtlcTransaction
};
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
mine_transaction(&nodes[0], &commitment_tx);

check_added_monitors(&nodes[0], 1);
Expand All@@ -426,10 +435,12 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
if anchors && !remote_commitment {
handle_bump_htlc_event(&nodes[0], 1);
}
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn);

// Mark it as available now, we should see the signed HTLC transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);

if anchors && !remote_commitment {
Expand All@@ -443,9 +454,21 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
}

#[test]
fn test_async_holder_signatures() {
fn test_async_holder_signatures_no_anchors() {
do_test_async_holder_signatures(false, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_no_anchors() {
do_test_async_holder_signatures(false, true);
}

#[test]
fn test_async_holder_signatures_anchors() {
do_test_async_holder_signatures(true, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2118,8 +2118,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

/// Returns the holder signer for this channel.
#[cfg(test)]
pub fn get_signer(&self) -> &ChannelSignerType<SP> {
return &self.holder_signer
pub fn get_mut_signer(&mut self) -> &mut ChannelSignerType<SP> {
return &mut self.holder_signer
}

/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
Expand Down
74 changes: 52 additions & 22 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::scid_utils;
use crate::util::test_channel_signer::TestChannelSigner;
#[cfg(test)]
use crate::util::test_channel_signer::SignerOp;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};
Expand DownExpand Up@@ -482,46 +484,74 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> Header {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.

/// Toggles this node's signer to be available for the given signer operation.
/// This is useful for testing behavior for restoring an async signer that previously
/// could not return a signature immediately.
#[cfg(test)]
pub fn enable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, true);
}

/// Toggles this node's signer to be unavailable, returning `Err` for the given signer operation.
/// This is useful for testing behavior for an async signer that cannot return a signature
/// immediately.
#[cfg(test)]
pub fn disable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, false);
}

/// Changes the channel signer's availability for the specified peer, channel, and signer
/// operation.
///
/// When `available` is set to `true`, the channel signer will behave normally. When set to
/// `false`, the channel signer will act like an off-line remote signer and will return `Err` for
/// several of the signing methods. Currently, only `get_per_commitment_point` and
/// `release_commitment_secret` are affected by this setting.
/// For the specified signer operation, when `available` is set to `true`, the channel signer
/// will behave normally, returning `Ok`. When set to `false`, and the channel signer will
/// act like an off-line remote signer, returning `Err`. This applies to the signer in all
/// relevant places, i.e. the channel manager, chain monitor, and the keys manager.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
fn set_channel_signer_ops(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp, available: bool) {
use crate::sign::ChannelSigner;
log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available);

let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let mut chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();

let mut channel_keys_id = None;
if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) {
chan.get_signer().as_ecdsa().unwrap().set_available(available);
if let Some(chan) = chan_lock.channel_by_id.get_mut(chan_id).map(|phase| phase.context_mut()) {
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
channel_keys_id = Some(chan.channel_keys_id);
}

let mut monitor = None;
for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() {
if *chan_id == channel_id {
monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok();
}
}
let monitor = self.chain_monitor.chain_monitor.list_monitors().into_iter()
.find(|(_, channel_id)| *channel_id == *chan_id)
.and_then(|(funding_txo, _)| self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok());
if let Some(monitor) = monitor {
monitor.do_signer_call(|signer| {
monitor.do_mut_signer_call(|signer| {
channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id()));
signer.set_available(available)
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
});
}

let channel_keys_id = channel_keys_id.unwrap();
let mut unavailable_signers_ops = self.keys_manager.unavailable_signers_ops.lock().unwrap();
let entry = unavailable_signers_ops.entry(channel_keys_id).or_insert(new_hash_set());
if available {
self.keys_manager.unavailable_signers.lock().unwrap()
.remove(channel_keys_id.as_ref().unwrap());
entry.remove(&signer_op);
if entry.is_empty() {
unavailable_signers_ops.remove(&channel_keys_id);
}
} else {
self.keys_manager.unavailable_signers.lock().unwrap()
.insert(channel_keys_id.unwrap());
}
entry.insert(signer_op);
};
}
}

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -396,12 +396,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
available: Arc::new(Mutex::new(true)),
})
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1924,9 +1924,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
pub fn do_mut_signer_call<F: FnMut(&mut Signer) -> ()>(&self, mut f: F) {
let mut inner = self.inner.lock().unwrap();
f(&mut inner.onchain_tx_handler.signer);
}
}

Expand Down
55 changes: 39 additions & 16 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::util::test_channel_signer::SignerOp;

#[test]
fn test_async_commitment_signature_for_funding_created() {
Expand All@@ -43,7 +44,7 @@ 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].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &temporary_channel_id, false);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
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@@ -57,7 +58,7 @@ fn test_async_commitment_signature_for_funding_created() {
channels[0].channel_id
};

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
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)));

let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand DownExpand Up@@ -98,7 +99,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -111,7 +112,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
Expand DownExpand Up@@ -152,14 +153,14 @@ fn test_async_commitment_signature_for_commitment_signed() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

let events = dst.node.get_and_clear_pending_msg_events();
Expand DownExpand Up@@ -215,7 +216,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -230,7 +231,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

let (funding_signed, channel_ready_1) = {
Expand DownExpand Up@@ -299,7 +300,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

Expand All@@ -314,7 +315,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {
reconnect_nodes(reconnect_args);

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

{
Expand DownExpand Up@@ -366,7 +367,6 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let error_message = "Channel force-closed";

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);

if remote_commitment {
// Make the counterparty broadcast its latest commitment.
Expand All@@ -375,6 +375,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
check_closed_broadcast(&nodes[1], 1, true);
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, false, &[nodes[0].node.get_our_node_id()], 100_000);
} else {
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience we'll probably want some wrapper methods that set/clear everything, no matter the internal representation.

// We'll connect blocks until the sender has to go onchain to time out the HTLC.
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);

Expand All@@ -383,7 +385,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());

// Mark it as available now, we should see the signed commitment transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);
}

Expand All@@ -409,7 +412,13 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

// Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such
// that the HTLC transaction is retried.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);
let sign_htlc_op = if remote_commitment {
SignerOp::SignCounterpartyHtlcTransaction
} else {
SignerOp::SignHolderHtlcTransaction
};
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
mine_transaction(&nodes[0], &commitment_tx);

check_added_monitors(&nodes[0], 1);
Expand All@@ -426,10 +435,12 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
if anchors && !remote_commitment {
handle_bump_htlc_event(&nodes[0], 1);
}
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn);

// Mark it as available now, we should see the signed HTLC transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);

if anchors && !remote_commitment {
Expand All@@ -443,9 +454,21 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
}

#[test]
fn test_async_holder_signatures() {
fn test_async_holder_signatures_no_anchors() {
do_test_async_holder_signatures(false, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_no_anchors() {
do_test_async_holder_signatures(false, true);
}

#[test]
fn test_async_holder_signatures_anchors() {
do_test_async_holder_signatures(true, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2118,8 +2118,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

/// Returns the holder signer for this channel.
#[cfg(test)]
pub fn get_signer(&self) -> &ChannelSignerType<SP> {
return &self.holder_signer
pub fn get_mut_signer(&mut self) -> &mut ChannelSignerType<SP> {
return &mut self.holder_signer
}

/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
Expand Down
74 changes: 52 additions & 22 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::scid_utils;
use crate::util::test_channel_signer::TestChannelSigner;
#[cfg(test)]
use crate::util::test_channel_signer::SignerOp;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};
Expand DownExpand Up@@ -482,46 +484,74 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> Header {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.

/// Toggles this node's signer to be available for the given signer operation.
/// This is useful for testing behavior for restoring an async signer that previously
/// could not return a signature immediately.
#[cfg(test)]
pub fn enable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, true);
}

/// Toggles this node's signer to be unavailable, returning `Err` for the given signer operation.
/// This is useful for testing behavior for an async signer that cannot return a signature
/// immediately.
#[cfg(test)]
pub fn disable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, false);
}

/// Changes the channel signer's availability for the specified peer, channel, and signer
/// operation.
///
/// When `available` is set to `true`, the channel signer will behave normally. When set to
/// `false`, the channel signer will act like an off-line remote signer and will return `Err` for
/// several of the signing methods. Currently, only `get_per_commitment_point` and
/// `release_commitment_secret` are affected by this setting.
/// For the specified signer operation, when `available` is set to `true`, the channel signer
/// will behave normally, returning `Ok`. When set to `false`, and the channel signer will
/// act like an off-line remote signer, returning `Err`. This applies to the signer in all
/// relevant places, i.e. the channel manager, chain monitor, and the keys manager.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
fn set_channel_signer_ops(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp, available: bool) {
use crate::sign::ChannelSigner;
log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available);

let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let mut chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();

let mut channel_keys_id = None;
if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) {
chan.get_signer().as_ecdsa().unwrap().set_available(available);
if let Some(chan) = chan_lock.channel_by_id.get_mut(chan_id).map(|phase| phase.context_mut()) {
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
channel_keys_id = Some(chan.channel_keys_id);
}

let mut monitor = None;
for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() {
if *chan_id == channel_id {
monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok();
}
}
let monitor = self.chain_monitor.chain_monitor.list_monitors().into_iter()
.find(|(_, channel_id)| *channel_id == *chan_id)
.and_then(|(funding_txo, _)| self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok());
if let Some(monitor) = monitor {
monitor.do_signer_call(|signer| {
monitor.do_mut_signer_call(|signer| {
channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id()));
signer.set_available(available)
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
});
}

let channel_keys_id = channel_keys_id.unwrap();
let mut unavailable_signers_ops = self.keys_manager.unavailable_signers_ops.lock().unwrap();
let entry = unavailable_signers_ops.entry(channel_keys_id).or_insert(new_hash_set());
if available {
self.keys_manager.unavailable_signers.lock().unwrap()
.remove(channel_keys_id.as_ref().unwrap());
entry.remove(&signer_op);
if entry.is_empty() {
unavailable_signers_ops.remove(&channel_keys_id);
}
} else {
self.keys_manager.unavailable_signers.lock().unwrap()
.insert(channel_keys_id.unwrap());
}
entry.insert(signer_op);
};
}
}

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -396,12 +396,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
available: Arc::new(Mutex::new(true)),
})
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1924,9 +1924,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
pub fn do_mut_signer_call<F: FnMut(&mut Signer) -> ()>(&self, mut f: F) {
let mut inner = self.inner.lock().unwrap();
f(&mut inner.onchain_tx_handler.signer);
}
}

Expand Down
55 changes: 39 additions & 16 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::util::test_channel_signer::SignerOp;

#[test]
fn test_async_commitment_signature_for_funding_created() {
Expand All@@ -43,7 +44,7 @@ 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].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &temporary_channel_id, false);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
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@@ -57,7 +58,7 @@ fn test_async_commitment_signature_for_funding_created() {
channels[0].channel_id
};

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
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)));

let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand DownExpand Up@@ -98,7 +99,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -111,7 +112,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
Expand DownExpand Up@@ -152,14 +153,14 @@ fn test_async_commitment_signature_for_commitment_signed() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

let events = dst.node.get_and_clear_pending_msg_events();
Expand DownExpand Up@@ -215,7 +216,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -230,7 +231,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

let (funding_signed, channel_ready_1) = {
Expand DownExpand Up@@ -299,7 +300,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

Expand All@@ -314,7 +315,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {
reconnect_nodes(reconnect_args);

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

{
Expand DownExpand Up@@ -366,7 +367,6 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let error_message = "Channel force-closed";

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);

if remote_commitment {
// Make the counterparty broadcast its latest commitment.
Expand All@@ -375,6 +375,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
check_closed_broadcast(&nodes[1], 1, true);
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, false, &[nodes[0].node.get_our_node_id()], 100_000);
} else {
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience we'll probably want some wrapper methods that set/clear everything, no matter the internal representation.

// We'll connect blocks until the sender has to go onchain to time out the HTLC.
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);

Expand All@@ -383,7 +385,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());

// Mark it as available now, we should see the signed commitment transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);
}

Expand All@@ -409,7 +412,13 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

// Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such
// that the HTLC transaction is retried.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);
let sign_htlc_op = if remote_commitment {
SignerOp::SignCounterpartyHtlcTransaction
} else {
SignerOp::SignHolderHtlcTransaction
};
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
mine_transaction(&nodes[0], &commitment_tx);

check_added_monitors(&nodes[0], 1);
Expand All@@ -426,10 +435,12 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
if anchors && !remote_commitment {
handle_bump_htlc_event(&nodes[0], 1);
}
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn);

// Mark it as available now, we should see the signed HTLC transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);

if anchors && !remote_commitment {
Expand All@@ -443,9 +454,21 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
}

#[test]
fn test_async_holder_signatures() {
fn test_async_holder_signatures_no_anchors() {
do_test_async_holder_signatures(false, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_no_anchors() {
do_test_async_holder_signatures(false, true);
}

#[test]
fn test_async_holder_signatures_anchors() {
do_test_async_holder_signatures(true, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2118,8 +2118,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

/// Returns the holder signer for this channel.
#[cfg(test)]
pub fn get_signer(&self) -> &ChannelSignerType<SP> {
return &self.holder_signer
pub fn get_mut_signer(&mut self) -> &mut ChannelSignerType<SP> {
return &mut self.holder_signer
}

/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
Expand Down
74 changes: 52 additions & 22 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::scid_utils;
use crate::util::test_channel_signer::TestChannelSigner;
#[cfg(test)]
use crate::util::test_channel_signer::SignerOp;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};
Expand DownExpand Up@@ -482,46 +484,74 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> Header {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.

/// Toggles this node's signer to be available for the given signer operation.
/// This is useful for testing behavior for restoring an async signer that previously
/// could not return a signature immediately.
#[cfg(test)]
pub fn enable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, true);
}

/// Toggles this node's signer to be unavailable, returning `Err` for the given signer operation.
/// This is useful for testing behavior for an async signer that cannot return a signature
/// immediately.
#[cfg(test)]
pub fn disable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, false);
}

/// Changes the channel signer's availability for the specified peer, channel, and signer
/// operation.
///
/// When `available` is set to `true`, the channel signer will behave normally. When set to
/// `false`, the channel signer will act like an off-line remote signer and will return `Err` for
/// several of the signing methods. Currently, only `get_per_commitment_point` and
/// `release_commitment_secret` are affected by this setting.
/// For the specified signer operation, when `available` is set to `true`, the channel signer
/// will behave normally, returning `Ok`. When set to `false`, and the channel signer will
/// act like an off-line remote signer, returning `Err`. This applies to the signer in all
/// relevant places, i.e. the channel manager, chain monitor, and the keys manager.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
fn set_channel_signer_ops(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp, available: bool) {
use crate::sign::ChannelSigner;
log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available);

let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let mut chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();

let mut channel_keys_id = None;
if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) {
chan.get_signer().as_ecdsa().unwrap().set_available(available);
if let Some(chan) = chan_lock.channel_by_id.get_mut(chan_id).map(|phase| phase.context_mut()) {
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
channel_keys_id = Some(chan.channel_keys_id);
}

let mut monitor = None;
for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() {
if *chan_id == channel_id {
monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok();
}
}
let monitor = self.chain_monitor.chain_monitor.list_monitors().into_iter()
.find(|(_, channel_id)| *channel_id == *chan_id)
.and_then(|(funding_txo, _)| self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok());
if let Some(monitor) = monitor {
monitor.do_signer_call(|signer| {
monitor.do_mut_signer_call(|signer| {
channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id()));
signer.set_available(available)
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
});
}

let channel_keys_id = channel_keys_id.unwrap();
let mut unavailable_signers_ops = self.keys_manager.unavailable_signers_ops.lock().unwrap();
let entry = unavailable_signers_ops.entry(channel_keys_id).or_insert(new_hash_set());
if available {
self.keys_manager.unavailable_signers.lock().unwrap()
.remove(channel_keys_id.as_ref().unwrap());
entry.remove(&signer_op);
if entry.is_empty() {
unavailable_signers_ops.remove(&channel_keys_id);
}
} else {
self.keys_manager.unavailable_signers.lock().unwrap()
.insert(channel_keys_id.unwrap());
}
entry.insert(signer_op);
};
}
}

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -396,12 +396,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
available: Arc::new(Mutex::new(true)),
})
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1924,9 +1924,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
pub fn do_mut_signer_call<F: FnMut(&mut Signer) -> ()>(&self, mut f: F) {
let mut inner = self.inner.lock().unwrap();
f(&mut inner.onchain_tx_handler.signer);
}
}

Expand Down
55 changes: 39 additions & 16 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::util::test_channel_signer::SignerOp;

#[test]
fn test_async_commitment_signature_for_funding_created() {
Expand All@@ -43,7 +44,7 @@ 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].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &temporary_channel_id, false);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
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@@ -57,7 +58,7 @@ fn test_async_commitment_signature_for_funding_created() {
channels[0].channel_id
};

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
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)));

let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand DownExpand Up@@ -98,7 +99,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -111,7 +112,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
Expand DownExpand Up@@ -152,14 +153,14 @@ fn test_async_commitment_signature_for_commitment_signed() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

let events = dst.node.get_and_clear_pending_msg_events();
Expand DownExpand Up@@ -215,7 +216,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -230,7 +231,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

let (funding_signed, channel_ready_1) = {
Expand DownExpand Up@@ -299,7 +300,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

Expand All@@ -314,7 +315,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {
reconnect_nodes(reconnect_args);

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

{
Expand DownExpand Up@@ -366,7 +367,6 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let error_message = "Channel force-closed";

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);

if remote_commitment {
// Make the counterparty broadcast its latest commitment.
Expand All@@ -375,6 +375,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
check_closed_broadcast(&nodes[1], 1, true);
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, false, &[nodes[0].node.get_our_node_id()], 100_000);
} else {
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience we'll probably want some wrapper methods that set/clear everything, no matter the internal representation.

// We'll connect blocks until the sender has to go onchain to time out the HTLC.
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);

Expand All@@ -383,7 +385,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());

// Mark it as available now, we should see the signed commitment transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);
}

Expand All@@ -409,7 +412,13 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

// Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such
// that the HTLC transaction is retried.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);
let sign_htlc_op = if remote_commitment {
SignerOp::SignCounterpartyHtlcTransaction
} else {
SignerOp::SignHolderHtlcTransaction
};
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
mine_transaction(&nodes[0], &commitment_tx);

check_added_monitors(&nodes[0], 1);
Expand All@@ -426,10 +435,12 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
if anchors && !remote_commitment {
handle_bump_htlc_event(&nodes[0], 1);
}
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn);

// Mark it as available now, we should see the signed HTLC transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);

if anchors && !remote_commitment {
Expand All@@ -443,9 +454,21 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
}

#[test]
fn test_async_holder_signatures() {
fn test_async_holder_signatures_no_anchors() {
do_test_async_holder_signatures(false, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_no_anchors() {
do_test_async_holder_signatures(false, true);
}

#[test]
fn test_async_holder_signatures_anchors() {
do_test_async_holder_signatures(true, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2118,8 +2118,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

/// Returns the holder signer for this channel.
#[cfg(test)]
pub fn get_signer(&self) -> &ChannelSignerType<SP> {
return &self.holder_signer
pub fn get_mut_signer(&mut self) -> &mut ChannelSignerType<SP> {
return &mut self.holder_signer
}

/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
Expand Down
74 changes: 52 additions & 22 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::scid_utils;
use crate::util::test_channel_signer::TestChannelSigner;
#[cfg(test)]
use crate::util::test_channel_signer::SignerOp;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};
Expand DownExpand Up@@ -482,46 +484,74 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> Header {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.

/// Toggles this node's signer to be available for the given signer operation.
/// This is useful for testing behavior for restoring an async signer that previously
/// could not return a signature immediately.
#[cfg(test)]
pub fn enable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, true);
}

/// Toggles this node's signer to be unavailable, returning `Err` for the given signer operation.
/// This is useful for testing behavior for an async signer that cannot return a signature
/// immediately.
#[cfg(test)]
pub fn disable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, false);
}

/// Changes the channel signer's availability for the specified peer, channel, and signer
/// operation.
///
/// When `available` is set to `true`, the channel signer will behave normally. When set to
/// `false`, the channel signer will act like an off-line remote signer and will return `Err` for
/// several of the signing methods. Currently, only `get_per_commitment_point` and
/// `release_commitment_secret` are affected by this setting.
/// For the specified signer operation, when `available` is set to `true`, the channel signer
/// will behave normally, returning `Ok`. When set to `false`, and the channel signer will
/// act like an off-line remote signer, returning `Err`. This applies to the signer in all
/// relevant places, i.e. the channel manager, chain monitor, and the keys manager.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
fn set_channel_signer_ops(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp, available: bool) {
use crate::sign::ChannelSigner;
log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available);

let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let mut chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();

let mut channel_keys_id = None;
if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) {
chan.get_signer().as_ecdsa().unwrap().set_available(available);
if let Some(chan) = chan_lock.channel_by_id.get_mut(chan_id).map(|phase| phase.context_mut()) {
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
channel_keys_id = Some(chan.channel_keys_id);
}

let mut monitor = None;
for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() {
if *chan_id == channel_id {
monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok();
}
}
let monitor = self.chain_monitor.chain_monitor.list_monitors().into_iter()
.find(|(_, channel_id)| *channel_id == *chan_id)
.and_then(|(funding_txo, _)| self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok());
if let Some(monitor) = monitor {
monitor.do_signer_call(|signer| {
monitor.do_mut_signer_call(|signer| {
channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id()));
signer.set_available(available)
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
});
}

let channel_keys_id = channel_keys_id.unwrap();
let mut unavailable_signers_ops = self.keys_manager.unavailable_signers_ops.lock().unwrap();
let entry = unavailable_signers_ops.entry(channel_keys_id).or_insert(new_hash_set());
if available {
self.keys_manager.unavailable_signers.lock().unwrap()
.remove(channel_keys_id.as_ref().unwrap());
entry.remove(&signer_op);
if entry.is_empty() {
unavailable_signers_ops.remove(&channel_keys_id);
}
} else {
self.keys_manager.unavailable_signers.lock().unwrap()
.insert(channel_keys_id.unwrap());
}
entry.insert(signer_op);
};
}
}

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -396,12 +396,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
available: Arc::new(Mutex::new(true)),
})
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1924,9 +1924,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
pub fn do_mut_signer_call<F: FnMut(&mut Signer) -> ()>(&self, mut f: F) {
let mut inner = self.inner.lock().unwrap();
f(&mut inner.onchain_tx_handler.signer);
}
}

Expand Down
55 changes: 39 additions & 16 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::util::test_channel_signer::SignerOp;

#[test]
fn test_async_commitment_signature_for_funding_created() {
Expand All@@ -43,7 +44,7 @@ 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].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &temporary_channel_id, false);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
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@@ -57,7 +58,7 @@ fn test_async_commitment_signature_for_funding_created() {
channels[0].channel_id
};

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
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)));

let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand DownExpand Up@@ -98,7 +99,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -111,7 +112,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
Expand DownExpand Up@@ -152,14 +153,14 @@ fn test_async_commitment_signature_for_commitment_signed() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

let events = dst.node.get_and_clear_pending_msg_events();
Expand DownExpand Up@@ -215,7 +216,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -230,7 +231,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

let (funding_signed, channel_ready_1) = {
Expand DownExpand Up@@ -299,7 +300,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

Expand All@@ -314,7 +315,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {
reconnect_nodes(reconnect_args);

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

{
Expand DownExpand Up@@ -366,7 +367,6 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let error_message = "Channel force-closed";

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);

if remote_commitment {
// Make the counterparty broadcast its latest commitment.
Expand All@@ -375,6 +375,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
check_closed_broadcast(&nodes[1], 1, true);
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, false, &[nodes[0].node.get_our_node_id()], 100_000);
} else {
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience we'll probably want some wrapper methods that set/clear everything, no matter the internal representation.

// We'll connect blocks until the sender has to go onchain to time out the HTLC.
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);

Expand All@@ -383,7 +385,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());

// Mark it as available now, we should see the signed commitment transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);
}

Expand All@@ -409,7 +412,13 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

// Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such
// that the HTLC transaction is retried.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);
let sign_htlc_op = if remote_commitment {
SignerOp::SignCounterpartyHtlcTransaction
} else {
SignerOp::SignHolderHtlcTransaction
};
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
mine_transaction(&nodes[0], &commitment_tx);

check_added_monitors(&nodes[0], 1);
Expand All@@ -426,10 +435,12 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
if anchors && !remote_commitment {
handle_bump_htlc_event(&nodes[0], 1);
}
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn);

// Mark it as available now, we should see the signed HTLC transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);

if anchors && !remote_commitment {
Expand All@@ -443,9 +454,21 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
}

#[test]
fn test_async_holder_signatures() {
fn test_async_holder_signatures_no_anchors() {
do_test_async_holder_signatures(false, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_no_anchors() {
do_test_async_holder_signatures(false, true);
}

#[test]
fn test_async_holder_signatures_anchors() {
do_test_async_holder_signatures(true, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2118,8 +2118,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

/// Returns the holder signer for this channel.
#[cfg(test)]
pub fn get_signer(&self) -> &ChannelSignerType<SP> {
return &self.holder_signer
pub fn get_mut_signer(&mut self) -> &mut ChannelSignerType<SP> {
return &mut self.holder_signer
}

/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
Expand Down
74 changes: 52 additions & 22 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::scid_utils;
use crate::util::test_channel_signer::TestChannelSigner;
#[cfg(test)]
use crate::util::test_channel_signer::SignerOp;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};
Expand DownExpand Up@@ -482,46 +484,74 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> Header {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.

/// Toggles this node's signer to be available for the given signer operation.
/// This is useful for testing behavior for restoring an async signer that previously
/// could not return a signature immediately.
#[cfg(test)]
pub fn enable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, true);
}

/// Toggles this node's signer to be unavailable, returning `Err` for the given signer operation.
/// This is useful for testing behavior for an async signer that cannot return a signature
/// immediately.
#[cfg(test)]
pub fn disable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, false);
}

/// Changes the channel signer's availability for the specified peer, channel, and signer
/// operation.
///
/// When `available` is set to `true`, the channel signer will behave normally. When set to
/// `false`, the channel signer will act like an off-line remote signer and will return `Err` for
/// several of the signing methods. Currently, only `get_per_commitment_point` and
/// `release_commitment_secret` are affected by this setting.
/// For the specified signer operation, when `available` is set to `true`, the channel signer
/// will behave normally, returning `Ok`. When set to `false`, and the channel signer will
/// act like an off-line remote signer, returning `Err`. This applies to the signer in all
/// relevant places, i.e. the channel manager, chain monitor, and the keys manager.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
fn set_channel_signer_ops(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp, available: bool) {
use crate::sign::ChannelSigner;
log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available);

let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let mut chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();

let mut channel_keys_id = None;
if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) {
chan.get_signer().as_ecdsa().unwrap().set_available(available);
if let Some(chan) = chan_lock.channel_by_id.get_mut(chan_id).map(|phase| phase.context_mut()) {
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
channel_keys_id = Some(chan.channel_keys_id);
}

let mut monitor = None;
for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() {
if *chan_id == channel_id {
monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok();
}
}
let monitor = self.chain_monitor.chain_monitor.list_monitors().into_iter()
.find(|(_, channel_id)| *channel_id == *chan_id)
.and_then(|(funding_txo, _)| self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok());
if let Some(monitor) = monitor {
monitor.do_signer_call(|signer| {
monitor.do_mut_signer_call(|signer| {
channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id()));
signer.set_available(available)
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
});
}

let channel_keys_id = channel_keys_id.unwrap();
let mut unavailable_signers_ops = self.keys_manager.unavailable_signers_ops.lock().unwrap();
let entry = unavailable_signers_ops.entry(channel_keys_id).or_insert(new_hash_set());
if available {
self.keys_manager.unavailable_signers.lock().unwrap()
.remove(channel_keys_id.as_ref().unwrap());
entry.remove(&signer_op);
if entry.is_empty() {
unavailable_signers_ops.remove(&channel_keys_id);
}
} else {
self.keys_manager.unavailable_signers.lock().unwrap()
.insert(channel_keys_id.unwrap());
}
entry.insert(signer_op);
};
}
}

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -396,12 +396,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
available: Arc::new(Mutex::new(true)),
})
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1924,9 +1924,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
pub fn do_mut_signer_call<F: FnMut(&mut Signer) -> ()>(&self, mut f: F) {
let mut inner = self.inner.lock().unwrap();
f(&mut inner.onchain_tx_handler.signer);
}
}

Expand Down
55 changes: 39 additions & 16 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::util::test_channel_signer::SignerOp;

#[test]
fn test_async_commitment_signature_for_funding_created() {
Expand All@@ -43,7 +44,7 @@ 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].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &temporary_channel_id, false);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
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@@ -57,7 +58,7 @@ fn test_async_commitment_signature_for_funding_created() {
channels[0].channel_id
};

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
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)));

let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand DownExpand Up@@ -98,7 +99,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -111,7 +112,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
Expand DownExpand Up@@ -152,14 +153,14 @@ fn test_async_commitment_signature_for_commitment_signed() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

let events = dst.node.get_and_clear_pending_msg_events();
Expand DownExpand Up@@ -215,7 +216,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -230,7 +231,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

let (funding_signed, channel_ready_1) = {
Expand DownExpand Up@@ -299,7 +300,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

Expand All@@ -314,7 +315,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {
reconnect_nodes(reconnect_args);

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

{
Expand DownExpand Up@@ -366,7 +367,6 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let error_message = "Channel force-closed";

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);

if remote_commitment {
// Make the counterparty broadcast its latest commitment.
Expand All@@ -375,6 +375,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
check_closed_broadcast(&nodes[1], 1, true);
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, false, &[nodes[0].node.get_our_node_id()], 100_000);
} else {
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience we'll probably want some wrapper methods that set/clear everything, no matter the internal representation.

// We'll connect blocks until the sender has to go onchain to time out the HTLC.
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);

Expand All@@ -383,7 +385,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());

// Mark it as available now, we should see the signed commitment transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);
}

Expand All@@ -409,7 +412,13 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

// Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such
// that the HTLC transaction is retried.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);
let sign_htlc_op = if remote_commitment {
SignerOp::SignCounterpartyHtlcTransaction
} else {
SignerOp::SignHolderHtlcTransaction
};
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
mine_transaction(&nodes[0], &commitment_tx);

check_added_monitors(&nodes[0], 1);
Expand All@@ -426,10 +435,12 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
if anchors && !remote_commitment {
handle_bump_htlc_event(&nodes[0], 1);
}
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn);

// Mark it as available now, we should see the signed HTLC transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);

if anchors && !remote_commitment {
Expand All@@ -443,9 +454,21 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
}

#[test]
fn test_async_holder_signatures() {
fn test_async_holder_signatures_no_anchors() {
do_test_async_holder_signatures(false, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_no_anchors() {
do_test_async_holder_signatures(false, true);
}

#[test]
fn test_async_holder_signatures_anchors() {
do_test_async_holder_signatures(true, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2118,8 +2118,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

/// Returns the holder signer for this channel.
#[cfg(test)]
pub fn get_signer(&self) -> &ChannelSignerType<SP> {
return &self.holder_signer
pub fn get_mut_signer(&mut self) -> &mut ChannelSignerType<SP> {
return &mut self.holder_signer
}

/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
Expand Down
74 changes: 52 additions & 22 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::scid_utils;
use crate::util::test_channel_signer::TestChannelSigner;
#[cfg(test)]
use crate::util::test_channel_signer::SignerOp;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};
Expand DownExpand Up@@ -482,46 +484,74 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> Header {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.

/// Toggles this node's signer to be available for the given signer operation.
/// This is useful for testing behavior for restoring an async signer that previously
/// could not return a signature immediately.
#[cfg(test)]
pub fn enable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, true);
}

/// Toggles this node's signer to be unavailable, returning `Err` for the given signer operation.
/// This is useful for testing behavior for an async signer that cannot return a signature
/// immediately.
#[cfg(test)]
pub fn disable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, false);
}

/// Changes the channel signer's availability for the specified peer, channel, and signer
/// operation.
///
/// When `available` is set to `true`, the channel signer will behave normally. When set to
/// `false`, the channel signer will act like an off-line remote signer and will return `Err` for
/// several of the signing methods. Currently, only `get_per_commitment_point` and
/// `release_commitment_secret` are affected by this setting.
/// For the specified signer operation, when `available` is set to `true`, the channel signer
/// will behave normally, returning `Ok`. When set to `false`, and the channel signer will
/// act like an off-line remote signer, returning `Err`. This applies to the signer in all
/// relevant places, i.e. the channel manager, chain monitor, and the keys manager.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
fn set_channel_signer_ops(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp, available: bool) {
use crate::sign::ChannelSigner;
log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available);

let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let mut chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();

let mut channel_keys_id = None;
if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) {
chan.get_signer().as_ecdsa().unwrap().set_available(available);
if let Some(chan) = chan_lock.channel_by_id.get_mut(chan_id).map(|phase| phase.context_mut()) {
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
channel_keys_id = Some(chan.channel_keys_id);
}

let mut monitor = None;
for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() {
if *chan_id == channel_id {
monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok();
}
}
let monitor = self.chain_monitor.chain_monitor.list_monitors().into_iter()
.find(|(_, channel_id)| *channel_id == *chan_id)
.and_then(|(funding_txo, _)| self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok());
if let Some(monitor) = monitor {
monitor.do_signer_call(|signer| {
monitor.do_mut_signer_call(|signer| {
channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id()));
signer.set_available(available)
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
});
}

let channel_keys_id = channel_keys_id.unwrap();
let mut unavailable_signers_ops = self.keys_manager.unavailable_signers_ops.lock().unwrap();
let entry = unavailable_signers_ops.entry(channel_keys_id).or_insert(new_hash_set());
if available {
self.keys_manager.unavailable_signers.lock().unwrap()
.remove(channel_keys_id.as_ref().unwrap());
entry.remove(&signer_op);
if entry.is_empty() {
unavailable_signers_ops.remove(&channel_keys_id);
}
} else {
self.keys_manager.unavailable_signers.lock().unwrap()
.insert(channel_keys_id.unwrap());
}
entry.insert(signer_op);
};
}
}

Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions fuzz/src/chanmon_consistency.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -396,12 +396,7 @@ impl SignerProvider for KeyProvider {
let inner: InMemorySigner = ReadableArgs::read(&mut reader, self)?;
let state = self.make_enforcement_state_cell(inner.commitment_seed);

Ok(TestChannelSigner {
inner,
state,
disable_revocation_policy_check: false,
available: Arc::new(Mutex::new(true)),
})
Ok(TestChannelSigner::new_with_revoked(inner, state, false))
}

fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result<ScriptBuf, ()> {
Expand Down
6 changes: 3 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1924,9 +1924,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
pub fn do_mut_signer_call<F: FnMut(&mut Signer) -> ()>(&self, mut f: F) {
let mut inner = self.inner.lock().unwrap();
f(&mut inner.onchain_tx_handler.signer);
}
}

Expand Down
55 changes: 39 additions & 16 deletions lightning/src/ln/async_signer_tests.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,6 +20,7 @@ use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureR
use crate::ln::functional_test_utils::*;
use crate::ln::msgs::ChannelMessageHandler;
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
use crate::util::test_channel_signer::SignerOp;

#[test]
fn test_async_commitment_signature_for_funding_created() {
Expand All@@ -43,7 +44,7 @@ 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].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &temporary_channel_id, false);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
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@@ -57,7 +58,7 @@ fn test_async_commitment_signature_for_funding_created() {
channels[0].channel_id
};

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
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)));

let mut funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand DownExpand Up@@ -98,7 +99,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -111,7 +112,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
Expand DownExpand Up@@ -152,14 +153,14 @@ fn test_async_commitment_signature_for_commitment_signed() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

get_event_msg!(dst, MessageSendEvent::SendRevokeAndACK, src.node.get_our_node_id());

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

let events = dst.node.get_and_clear_pending_msg_events();
Expand DownExpand Up@@ -215,7 +216,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &temporary_channel_id, false);
nodes[1].disable_channel_signer_op(&nodes[0].node.get_our_node_id(), &temporary_channel_id, SignerOp::SignCounterpartyCommitment);
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created_msg);
check_added_monitors(&nodes[1], 1);

Expand All@@ -230,7 +231,7 @@ 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].set_channel_signer_available(&nodes[0].node.get_our_node_id(), &chan_id, true);
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)));

let (funding_signed, channel_ready_1) = {
Expand DownExpand Up@@ -299,7 +300,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {

// Mark dst's signer as unavailable and handle src's commitment_signed: while dst won't yet have a
// `commitment_signed` of its own to offer, it should publish a `revoke_and_ack`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, false);
dst.disable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.handle_commitment_signed(&src.node.get_our_node_id(), &payment_event.commitment_msg);
check_added_monitors(dst, 1);

Expand All@@ -314,7 +315,7 @@ fn test_async_commitment_signature_for_peer_disconnect() {
reconnect_nodes(reconnect_args);

// Mark dst's signer as available and retry: we now expect to see dst's `commitment_signed`.
dst.set_channel_signer_available(&src.node.get_our_node_id(), &chan_id, true);
dst.enable_channel_signer_op(&src.node.get_our_node_id(), &chan_id, SignerOp::SignCounterpartyCommitment);
dst.node.signer_unblocked(Some((src.node.get_our_node_id(), chan_id)));

{
Expand DownExpand Up@@ -366,7 +367,6 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
let error_message = "Channel force-closed";

nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);

if remote_commitment {
// Make the counterparty broadcast its latest commitment.
Expand All@@ -375,6 +375,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
check_closed_broadcast(&nodes[1], 1, true);
check_closed_event(&nodes[1], 1, ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, false, &[nodes[0].node.get_our_node_id()], 100_000);
} else {
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenience we'll probably want some wrapper methods that set/clear everything, no matter the internal representation.

// We'll connect blocks until the sender has to go onchain to time out the HTLC.
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);

Expand All@@ -383,7 +385,8 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());

// Mark it as available now, we should see the signed commitment transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderHtlcTransaction);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);
}

Expand All@@ -409,7 +412,13 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {

// Mark it as unavailable again to now test the HTLC transaction. We'll mine the commitment such
// that the HTLC transaction is retried.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, false);
let sign_htlc_op = if remote_commitment {
SignerOp::SignCounterpartyHtlcTransaction
} else {
SignerOp::SignHolderHtlcTransaction
};
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].disable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
mine_transaction(&nodes[0], &commitment_tx);

check_added_monitors(&nodes[0], 1);
Expand All@@ -426,10 +435,12 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
if anchors && !remote_commitment {
handle_bump_htlc_event(&nodes[0], 1);
}
assert!(nodes[0].tx_broadcaster.txn_broadcast().is_empty());
let txn = nodes[0].tx_broadcaster.txn_broadcast();
assert!(txn.is_empty(), "expected no transaction to be broadcast, got {:?}", txn);

// Mark it as available now, we should see the signed HTLC transaction.
nodes[0].set_channel_signer_available(&nodes[1].node.get_our_node_id(), &chan_id, true);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, SignerOp::SignHolderCommitment);
nodes[0].enable_channel_signer_op(&nodes[1].node.get_our_node_id(), &chan_id, sign_htlc_op);
get_monitor!(nodes[0], chan_id).signer_unblocked(nodes[0].tx_broadcaster, nodes[0].fee_estimator, &nodes[0].logger);

if anchors && !remote_commitment {
Expand All@@ -443,9 +454,21 @@ fn do_test_async_holder_signatures(anchors: bool, remote_commitment: bool) {
}

#[test]
fn test_async_holder_signatures() {
fn test_async_holder_signatures_no_anchors() {
do_test_async_holder_signatures(false, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_no_anchors() {
do_test_async_holder_signatures(false, true);
}

#[test]
fn test_async_holder_signatures_anchors() {
do_test_async_holder_signatures(true, false);
}

#[test]
fn test_async_holder_signatures_remote_commitment_anchors() {
do_test_async_holder_signatures(true, true);
}
4 changes: 2 additions & 2 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2118,8 +2118,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

/// Returns the holder signer for this channel.
#[cfg(test)]
pub fn get_signer(&self) -> &ChannelSignerType<SP> {
return &self.holder_signer
pub fn get_mut_signer(&mut self) -> &mut ChannelSignerType<SP> {
return &mut self.holder_signer
}

/// Only allowed immediately after deserialization if get_outbound_scid_alias returns 0,
Expand Down
74 changes: 52 additions & 22 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,6 +31,8 @@ use crate::util::errors::APIError;
use crate::util::logger::Logger;
use crate::util::scid_utils;
use crate::util::test_channel_signer::TestChannelSigner;
#[cfg(test)]
use crate::util::test_channel_signer::SignerOp;
use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::ser::{ReadableArgs, Writeable};
Expand DownExpand Up@@ -482,46 +484,74 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> Header {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.

/// Toggles this node's signer to be available for the given signer operation.
/// This is useful for testing behavior for restoring an async signer that previously
/// could not return a signature immediately.
#[cfg(test)]
pub fn enable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, true);
}

/// Toggles this node's signer to be unavailable, returning `Err` for the given signer operation.
/// This is useful for testing behavior for an async signer that cannot return a signature
/// immediately.
#[cfg(test)]
pub fn disable_channel_signer_op(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp) {
self.set_channel_signer_ops(peer_id, chan_id, signer_op, false);
}

/// Changes the channel signer's availability for the specified peer, channel, and signer
/// operation.
///
/// When `available` is set to `true`, the channel signer will behave normally. When set to
/// `false`, the channel signer will act like an off-line remote signer and will return `Err` for
/// several of the signing methods. Currently, only `get_per_commitment_point` and
/// `release_commitment_secret` are affected by this setting.
/// For the specified signer operation, when `available` is set to `true`, the channel signer
/// will behave normally, returning `Ok`. When set to `false`, and the channel signer will
/// act like an off-line remote signer, returning `Err`. This applies to the signer in all
/// relevant places, i.e. the channel manager, chain monitor, and the keys manager.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
fn set_channel_signer_ops(&self, peer_id: &PublicKey, chan_id: &ChannelId, signer_op: SignerOp, available: bool) {
use crate::sign::ChannelSigner;
log_debug!(self.logger, "Setting channel signer for {} as available={}", chan_id, available);

let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let mut chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();

let mut channel_keys_id = None;
if let Some(chan) = chan_lock.channel_by_id.get(chan_id).map(|phase| phase.context()) {
chan.get_signer().as_ecdsa().unwrap().set_available(available);
if let Some(chan) = chan_lock.channel_by_id.get_mut(chan_id).map(|phase| phase.context_mut()) {
let signer = chan.get_mut_signer().as_mut_ecdsa().unwrap();
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
channel_keys_id = Some(chan.channel_keys_id);
}

let mut monitor = None;
for (funding_txo, channel_id) in self.chain_monitor.chain_monitor.list_monitors() {
if *chan_id == channel_id {
monitor = self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok();
}
}
let monitor = self.chain_monitor.chain_monitor.list_monitors().into_iter()
.find(|(_, channel_id)| *channel_id == *chan_id)
.and_then(|(funding_txo, _)| self.chain_monitor.chain_monitor.get_monitor(funding_txo).ok());
if let Some(monitor) = monitor {
monitor.do_signer_call(|signer| {
monitor.do_mut_signer_call(|signer| {
channel_keys_id = channel_keys_id.or(Some(signer.inner.channel_keys_id()));
signer.set_available(available)
if available {
signer.enable_op(signer_op);
} else {
signer.disable_op(signer_op);
}
});
}

let channel_keys_id = channel_keys_id.unwrap();
let mut unavailable_signers_ops = self.keys_manager.unavailable_signers_ops.lock().unwrap();
let entry = unavailable_signers_ops.entry(channel_keys_id).or_insert(new_hash_set());
if available {
self.keys_manager.unavailable_signers.lock().unwrap()
.remove(channel_keys_id.as_ref().unwrap());
entry.remove(&signer_op);
if entry.is_empty() {
unavailable_signers_ops.remove(&channel_keys_id);
}
} else {
self.keys_manager.unavailable_signers.lock().unwrap()
.insert(channel_keys_id.unwrap());
}
entry.insert(signer_op);
};
}
}

Expand Down
Loading