Skip to content
Closed
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
4 changes: 1 addition & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2752,9 +2752,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
},
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
),
per_commitment_point: htlc.per_commitment_point,
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
Expand Down
8 changes: 8 additions & 0 deletions lightning/src/chain/onchaintx.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -179,6 +179,7 @@ pub(crate) struct ExternalHTLCClaim {
pub(crate) htlc: HTLCOutputInCommitment,
pub(crate) preimage: Option<PaymentPreimage>,
pub(crate) counterparty_sig: Signature,
pub(crate) per_commitment_point: bitcoin::secp256k1::PublicKey,
}

// Represents the different types of claims for which events are yielded externally to satisfy said
Expand DownExpand Up@@ -1188,9 +1189,16 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
})
.map(|(htlc_idx, htlc)| {
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];

// TODO(waterson) fallible: move this somewhere!
let per_commitment_point = self.signer.get_per_commitment_point(
trusted_tx.commitment_number(), &self.secp_ctx,
).unwrap();

ExternalHTLCClaim {
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: per_commitment_point,
htlc: htlc.clone(),
preimage: *preimage,
counterparty_sig: counterparty_htlc_sig,
Expand Down
218 changes: 183 additions & 35 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

190 changes: 172 additions & 18 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -470,7 +470,7 @@ impl MsgHandleErrInternal {
log_level: Level::Warn,
},
},
ChannelError::Ignore(msg) => LightningError {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => LightningError {
err: msg,
action: msgs::ErrorAction::IgnoreError,
},
Expand DownExpand Up@@ -663,6 +663,15 @@ impl_writeable_tlv_based_enum!(RAAMonitorUpdateBlockingAction,
(0, ForwardedPaymentInboundClaim) => { (0, channel_id, required), (2, htlc_id, required) }
;);

pub(super) enum ChannelRetryState {
FundingCreated(msgs::FundingCreated),
FundingSigned(msgs::FundingSigned),
ChannelReestablish(msgs::ChannelReestablish),
CommitmentSigned(msgs::CommitmentSigned),
CompleteAcceptingInboundV1Channel(),
CompleteCreatingOutboundV1Channel(),
}


/// State we hold per-peer.
pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
Expand All@@ -689,6 +698,8 @@ pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
/// removed, and an InboundV1Channel is created and placed in the `inbound_v1_channel_by_id` table. If
/// the channel is rejected, then the entry is simply removed.
pub(super) inbound_channel_request_by_id: HashMap<ChannelId, InboundChannelRequest>,
/// Messages that we attempted to process but returned `ChannelError::Retry`.
pub(super) retry_state_by_id: HashMap<ChannelId, ChannelRetryState>,
/// The latest `InitFeatures` we heard from the peer.
latest_features: InitFeatures,
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
Expand DownExpand Up@@ -1812,7 +1823,7 @@ macro_rules! convert_chan_err {
ChannelError::Warn(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
},
ChannelError::Ignore(msg) => {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
},
ChannelError::Close(msg) => {
Expand All@@ -1828,7 +1839,7 @@ macro_rules! convert_chan_err {
match $err {
// We should only ever have `ChannelError::Close` when unfunded channels error.
// In any case, just close the channel.
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Retry(msg) | ChannelError::Close(msg) => {
log_error!($self.logger, "Closing unfunded channel {} due to an error: {}", &$channel_id, msg);
update_maps_on_chan_removal!($self, &$channel_context);
let shutdown_res = $channel_context.force_shutdown(false);
Expand DownExpand Up@@ -2286,9 +2297,18 @@ where
},
}
};
let res = channel.get_open_channel(self.genesis_hash.clone());

let temporary_channel_id = channel.context.channel_id();
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(temporary_channel_id.clone(), ChannelRetryState::CompleteCreatingOutboundV1Channel());
}

match peer_state.outbound_v1_channel_by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
if cfg!(fuzzing) {
Expand All@@ -2300,13 +2320,35 @@ where
hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
}

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: res,
});
Ok(temporary_channel_id)
}

fn complete_creating_outbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying create for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.outbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find outbound channel with temporary ID {temporary_channel_id} for which to complete creation");
return;
}

log_info!(self.logger, "Found outbound channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for outbound channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for outbound channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
});
}

fn list_funded_channels_with_filter<Fn: FnMut(&(&ChannelId, &Channel<SP>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
Expand DownExpand Up@@ -5423,16 +5465,66 @@ where
let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
channel.context.set_outbound_scid_alias(outbound_scid_alias);

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
// If the channel context has the first commitment point, then we can immediately complete the
// channel acceptance by sending the accept_channel message. Otherwise, we'll need to wait for
// the signer to assign us the first commitment point.
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(*temporary_channel_id, ChannelRetryState::CompleteAcceptingInboundV1Channel());
}

peer_state.inbound_v1_channel_by_id.insert(temporary_channel_id.clone(), channel);

Ok(())
}

/// Performs `f` with the mutable peer state for the specified counterparty node.
///
/// Returns `None` if no such counterparty node exists; otherwise, returns the result of `f`
/// wrapped in `Some`.
fn with_mut_peer_state<Out, Fn>(&self, counterparty_node_id: &PublicKey, f: Fn) -> Option<Out>
where Fn: FnOnce(&mut PeerState<SP>) -> Out
{
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Some(f(&mut *peer_state_lock))
}

/// Retries an `InboundV1Channel` accept.
///
/// Attempts to resolve the first per-commitment point, and if successful broadcasts the
/// `accept_channel` message to the peer.
fn retry_accepting_inbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying accept for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.inbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find channel with temporary ID {temporary_channel_id} for which to complete accepting");
return;
}

log_info!(self.logger, "Found channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
});
}

/// Gets the number of peers which match the given filter and do not have any funded, outbound,
/// or 0-conf channels.
///
Expand DownExpand Up@@ -5622,6 +5714,13 @@ where
Some(inbound_chan) => {
match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &self.logger) {
Ok(res) => res,
Err((inbound_chan, err @ ChannelError::Retry(_))) => {
// If we get an `Retry` error then something transient went wrong. Put the channel
// back into the table and bail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

when does this get retried (would be good to doc here)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I am assuming that there will be a to-be-determined mechanism that allows the remote signer's implementation to initiate a restart.

peer_state.inbound_v1_channel_by_id.insert(msg.temporary_channel_id, inbound_chan);
peer_state.retry_state_by_id.insert(msg.temporary_channel_id, ChannelRetryState::FundingCreated(msg.clone()));
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.temporary_channel_id));
},
Err((mut inbound_chan, err)) => {
// We've already removed this inbound channel from the map in `PeerState`
// above so at this point we just need to clean up any lingering entries
Expand DownExpand Up@@ -5698,8 +5797,13 @@ where
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let monitor = try_chan_entry!(self,
chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
let res = chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::FundingSigned(msg.clone()));
}
}
let monitor = try_chan_entry!(self, res, chan);
let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
Expand DownExpand Up@@ -6008,7 +6112,13 @@ where
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let funding_txo = chan.get().context.get_funding_txo();
let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
let res = chan.get_mut().commitment_signed(&msg, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::CommitmentSigned(msg.clone()));
}
}
let monitor_update_opt = try_chan_entry!(self, res, chan);
if let Some(monitor_update) = monitor_update_opt {
handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
peer_state, per_peer_state, chan).map(|_| ())
Expand DownExpand Up@@ -6280,9 +6390,15 @@ where
// disconnect, so Channel's reestablish will never hand us any holding cell
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_entry!(self, chan.get_mut().channel_reestablish(
let res = chan.get_mut().channel_reestablish(
msg, &self.logger, &self.node_signer, self.genesis_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan);
&self.default_configuration, &*self.best_block.read().unwrap());
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::ChannelReestablish(msg.clone()));
}
}
let responses = try_chan_entry!(self, res, chan);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
Expand DownExpand Up@@ -6830,6 +6946,42 @@ where
let mut ev;
process_events_body!(self, ev, { handler(ev).await });
}

/// Resumes processing for a channel awaiting a signature.
///
/// If the `ChannelSigner` for a channel has previously returned an `Err`, then activity on the
/// channel will have been suspended pending the required signature becoming available. This
/// resumes the channel's operation.
pub fn retry_channel(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey) -> Result<(), APIError> {
log_info!(self.logger, "Running retry_channel for {channel_id} on {counterparty_node_id}");
let retry_state = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or(APIError::APIMisuseError {
err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id)
})?;

let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.retry_state_by_id.remove(channel_id) {
Some(retry_state) => Ok(retry_state),
None => Err(APIError::APIMisuseError {
err: format!("Can't find a channel matching the passed channel_id {}", channel_id)
}),
}
}?;

match retry_state {
ChannelRetryState::FundingCreated(ref msg) => self.handle_funding_created(counterparty_node_id, msg),
ChannelRetryState::FundingSigned(ref msg) => self.handle_funding_signed(counterparty_node_id, msg),
ChannelRetryState::ChannelReestablish(ref msg) => self.handle_channel_reestablish(counterparty_node_id, msg),
ChannelRetryState::CommitmentSigned(ref msg) => self.handle_commitment_signed(counterparty_node_id, msg),
ChannelRetryState::CompleteAcceptingInboundV1Channel() => self.retry_accepting_inbound_v1_channel(counterparty_node_id, channel_id),
ChannelRetryState::CompleteCreatingOutboundV1Channel() => self.complete_creating_outbound_v1_channel(counterparty_node_id, channel_id),
};

Ok(())
}
}

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> MessageSendEventsProvider for ChannelManager<M, T, ES, NS, SP, F, R, L>
Expand DownExpand Up@@ -7504,6 +7656,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: init_msg.features.clone(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand DownExpand Up@@ -8752,6 +8905,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand Down
34 changes: 31 additions & 3 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,8 @@ use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
#[cfg(test)]
use crate::util::logger::Logger;
use crate::util::ser::{ReadableArgs, Writeable};

use bitcoin::blockdata::block::{Block, BlockHeader};
Expand DownExpand Up@@ -414,6 +416,31 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> BlockHeader {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.
///
/// 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.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let signer = (|| {
if let Some(local_chan) = chan_lock.channel_by_id.get(chan_id) {
return local_chan.get_signer();
}
if let Some(local_chan) = chan_lock.inbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
if let Some(local_chan) = chan_lock.outbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
panic!("Couldn't find a channel with id {}", chan_id);
})();
log_debug!(self.logger, "Setting channel {} as available={}", chan_id, available);
signer.as_ecdsa().unwrap().set_available(available);
}
}

/// If we need an unsafe pointer to a `Node` (ie to reference it in a thread
Expand DownExpand Up@@ -2031,12 +2058,13 @@ macro_rules! expect_channel_shutdown_state {
}

#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) {
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) -> ChannelId {
let events = node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
crate::events::Event::ChannelPending { ref counterparty_node_id, .. } => {
match &events[0] {
crate::events::Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
assert_eq!(*expected_counterparty_node_id, *counterparty_node_id);
*channel_id
},
_ => panic!("Unexpected event"),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Allow get_per_commitment_point to fail. by waterson · Pull Request #2487 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
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
4 changes: 1 addition & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2752,9 +2752,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
},
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
),
per_commitment_point: htlc.per_commitment_point,
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
Expand Down
8 changes: 8 additions & 0 deletions lightning/src/chain/onchaintx.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -179,6 +179,7 @@ pub(crate) struct ExternalHTLCClaim {
pub(crate) htlc: HTLCOutputInCommitment,
pub(crate) preimage: Option<PaymentPreimage>,
pub(crate) counterparty_sig: Signature,
pub(crate) per_commitment_point: bitcoin::secp256k1::PublicKey,
}

// Represents the different types of claims for which events are yielded externally to satisfy said
Expand DownExpand Up@@ -1188,9 +1189,16 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
})
.map(|(htlc_idx, htlc)| {
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];

// TODO(waterson) fallible: move this somewhere!
let per_commitment_point = self.signer.get_per_commitment_point(
trusted_tx.commitment_number(), &self.secp_ctx,
).unwrap();

ExternalHTLCClaim {
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: per_commitment_point,
htlc: htlc.clone(),
preimage: *preimage,
counterparty_sig: counterparty_htlc_sig,
Expand Down
218 changes: 183 additions & 35 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

190 changes: 172 additions & 18 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -470,7 +470,7 @@ impl MsgHandleErrInternal {
log_level: Level::Warn,
},
},
ChannelError::Ignore(msg) => LightningError {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => LightningError {
err: msg,
action: msgs::ErrorAction::IgnoreError,
},
Expand DownExpand Up@@ -663,6 +663,15 @@ impl_writeable_tlv_based_enum!(RAAMonitorUpdateBlockingAction,
(0, ForwardedPaymentInboundClaim) => { (0, channel_id, required), (2, htlc_id, required) }
;);

pub(super) enum ChannelRetryState {
FundingCreated(msgs::FundingCreated),
FundingSigned(msgs::FundingSigned),
ChannelReestablish(msgs::ChannelReestablish),
CommitmentSigned(msgs::CommitmentSigned),
CompleteAcceptingInboundV1Channel(),
CompleteCreatingOutboundV1Channel(),
}


/// State we hold per-peer.
pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
Expand All@@ -689,6 +698,8 @@ pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
/// removed, and an InboundV1Channel is created and placed in the `inbound_v1_channel_by_id` table. If
/// the channel is rejected, then the entry is simply removed.
pub(super) inbound_channel_request_by_id: HashMap<ChannelId, InboundChannelRequest>,
/// Messages that we attempted to process but returned `ChannelError::Retry`.
pub(super) retry_state_by_id: HashMap<ChannelId, ChannelRetryState>,
/// The latest `InitFeatures` we heard from the peer.
latest_features: InitFeatures,
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
Expand DownExpand Up@@ -1812,7 +1823,7 @@ macro_rules! convert_chan_err {
ChannelError::Warn(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
},
ChannelError::Ignore(msg) => {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
},
ChannelError::Close(msg) => {
Expand All@@ -1828,7 +1839,7 @@ macro_rules! convert_chan_err {
match $err {
// We should only ever have `ChannelError::Close` when unfunded channels error.
// In any case, just close the channel.
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Retry(msg) | ChannelError::Close(msg) => {
log_error!($self.logger, "Closing unfunded channel {} due to an error: {}", &$channel_id, msg);
update_maps_on_chan_removal!($self, &$channel_context);
let shutdown_res = $channel_context.force_shutdown(false);
Expand DownExpand Up@@ -2286,9 +2297,18 @@ where
},
}
};
let res = channel.get_open_channel(self.genesis_hash.clone());

let temporary_channel_id = channel.context.channel_id();
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(temporary_channel_id.clone(), ChannelRetryState::CompleteCreatingOutboundV1Channel());
}

match peer_state.outbound_v1_channel_by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
if cfg!(fuzzing) {
Expand All@@ -2300,13 +2320,35 @@ where
hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
}

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: res,
});
Ok(temporary_channel_id)
}

fn complete_creating_outbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying create for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.outbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find outbound channel with temporary ID {temporary_channel_id} for which to complete creation");
return;
}

log_info!(self.logger, "Found outbound channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for outbound channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for outbound channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
});
}

fn list_funded_channels_with_filter<Fn: FnMut(&(&ChannelId, &Channel<SP>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
Expand DownExpand Up@@ -5423,16 +5465,66 @@ where
let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
channel.context.set_outbound_scid_alias(outbound_scid_alias);

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
// If the channel context has the first commitment point, then we can immediately complete the
// channel acceptance by sending the accept_channel message. Otherwise, we'll need to wait for
// the signer to assign us the first commitment point.
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(*temporary_channel_id, ChannelRetryState::CompleteAcceptingInboundV1Channel());
}

peer_state.inbound_v1_channel_by_id.insert(temporary_channel_id.clone(), channel);

Ok(())
}

/// Performs `f` with the mutable peer state for the specified counterparty node.
///
/// Returns `None` if no such counterparty node exists; otherwise, returns the result of `f`
/// wrapped in `Some`.
fn with_mut_peer_state<Out, Fn>(&self, counterparty_node_id: &PublicKey, f: Fn) -> Option<Out>
where Fn: FnOnce(&mut PeerState<SP>) -> Out
{
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Some(f(&mut *peer_state_lock))
}

/// Retries an `InboundV1Channel` accept.
///
/// Attempts to resolve the first per-commitment point, and if successful broadcasts the
/// `accept_channel` message to the peer.
fn retry_accepting_inbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying accept for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.inbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find channel with temporary ID {temporary_channel_id} for which to complete accepting");
return;
}

log_info!(self.logger, "Found channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
});
}

/// Gets the number of peers which match the given filter and do not have any funded, outbound,
/// or 0-conf channels.
///
Expand DownExpand Up@@ -5622,6 +5714,13 @@ where
Some(inbound_chan) => {
match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &self.logger) {
Ok(res) => res,
Err((inbound_chan, err @ ChannelError::Retry(_))) => {
// If we get an `Retry` error then something transient went wrong. Put the channel
// back into the table and bail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

when does this get retried (would be good to doc here)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I am assuming that there will be a to-be-determined mechanism that allows the remote signer's implementation to initiate a restart.

peer_state.inbound_v1_channel_by_id.insert(msg.temporary_channel_id, inbound_chan);
peer_state.retry_state_by_id.insert(msg.temporary_channel_id, ChannelRetryState::FundingCreated(msg.clone()));
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.temporary_channel_id));
},
Err((mut inbound_chan, err)) => {
// We've already removed this inbound channel from the map in `PeerState`
// above so at this point we just need to clean up any lingering entries
Expand DownExpand Up@@ -5698,8 +5797,13 @@ where
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let monitor = try_chan_entry!(self,
chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
let res = chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::FundingSigned(msg.clone()));
}
}
let monitor = try_chan_entry!(self, res, chan);
let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
Expand DownExpand Up@@ -6008,7 +6112,13 @@ where
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let funding_txo = chan.get().context.get_funding_txo();
let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
let res = chan.get_mut().commitment_signed(&msg, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::CommitmentSigned(msg.clone()));
}
}
let monitor_update_opt = try_chan_entry!(self, res, chan);
if let Some(monitor_update) = monitor_update_opt {
handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
peer_state, per_peer_state, chan).map(|_| ())
Expand DownExpand Up@@ -6280,9 +6390,15 @@ where
// disconnect, so Channel's reestablish will never hand us any holding cell
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_entry!(self, chan.get_mut().channel_reestablish(
let res = chan.get_mut().channel_reestablish(
msg, &self.logger, &self.node_signer, self.genesis_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan);
&self.default_configuration, &*self.best_block.read().unwrap());
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::ChannelReestablish(msg.clone()));
}
}
let responses = try_chan_entry!(self, res, chan);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
Expand DownExpand Up@@ -6830,6 +6946,42 @@ where
let mut ev;
process_events_body!(self, ev, { handler(ev).await });
}

/// Resumes processing for a channel awaiting a signature.
///
/// If the `ChannelSigner` for a channel has previously returned an `Err`, then activity on the
/// channel will have been suspended pending the required signature becoming available. This
/// resumes the channel's operation.
pub fn retry_channel(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey) -> Result<(), APIError> {
log_info!(self.logger, "Running retry_channel for {channel_id} on {counterparty_node_id}");
let retry_state = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or(APIError::APIMisuseError {
err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id)
})?;

let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.retry_state_by_id.remove(channel_id) {
Some(retry_state) => Ok(retry_state),
None => Err(APIError::APIMisuseError {
err: format!("Can't find a channel matching the passed channel_id {}", channel_id)
}),
}
}?;

match retry_state {
ChannelRetryState::FundingCreated(ref msg) => self.handle_funding_created(counterparty_node_id, msg),
ChannelRetryState::FundingSigned(ref msg) => self.handle_funding_signed(counterparty_node_id, msg),
ChannelRetryState::ChannelReestablish(ref msg) => self.handle_channel_reestablish(counterparty_node_id, msg),
ChannelRetryState::CommitmentSigned(ref msg) => self.handle_commitment_signed(counterparty_node_id, msg),
ChannelRetryState::CompleteAcceptingInboundV1Channel() => self.retry_accepting_inbound_v1_channel(counterparty_node_id, channel_id),
ChannelRetryState::CompleteCreatingOutboundV1Channel() => self.complete_creating_outbound_v1_channel(counterparty_node_id, channel_id),
};

Ok(())
}
}

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> MessageSendEventsProvider for ChannelManager<M, T, ES, NS, SP, F, R, L>
Expand DownExpand Up@@ -7504,6 +7656,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: init_msg.features.clone(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand DownExpand Up@@ -8752,6 +8905,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand Down
34 changes: 31 additions & 3 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,8 @@ use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
#[cfg(test)]
use crate::util::logger::Logger;
use crate::util::ser::{ReadableArgs, Writeable};

use bitcoin::blockdata::block::{Block, BlockHeader};
Expand DownExpand Up@@ -414,6 +416,31 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> BlockHeader {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.
///
/// 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.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let signer = (|| {
if let Some(local_chan) = chan_lock.channel_by_id.get(chan_id) {
return local_chan.get_signer();
}
if let Some(local_chan) = chan_lock.inbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
if let Some(local_chan) = chan_lock.outbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
panic!("Couldn't find a channel with id {}", chan_id);
})();
log_debug!(self.logger, "Setting channel {} as available={}", chan_id, available);
signer.as_ecdsa().unwrap().set_available(available);
}
}

/// If we need an unsafe pointer to a `Node` (ie to reference it in a thread
Expand DownExpand Up@@ -2031,12 +2058,13 @@ macro_rules! expect_channel_shutdown_state {
}

#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) {
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) -> ChannelId {
let events = node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
crate::events::Event::ChannelPending { ref counterparty_node_id, .. } => {
match &events[0] {
crate::events::Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
assert_eq!(*expected_counterparty_node_id, *counterparty_node_id);
*channel_id
},
_ => panic!("Unexpected event"),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow get_per_commitment_point to fail. by waterson · Pull Request #2487 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
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
4 changes: 1 addition & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2752,9 +2752,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
},
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
),
per_commitment_point: htlc.per_commitment_point,
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
Expand Down
8 changes: 8 additions & 0 deletions lightning/src/chain/onchaintx.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -179,6 +179,7 @@ pub(crate) struct ExternalHTLCClaim {
pub(crate) htlc: HTLCOutputInCommitment,
pub(crate) preimage: Option<PaymentPreimage>,
pub(crate) counterparty_sig: Signature,
pub(crate) per_commitment_point: bitcoin::secp256k1::PublicKey,
}

// Represents the different types of claims for which events are yielded externally to satisfy said
Expand DownExpand Up@@ -1188,9 +1189,16 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
})
.map(|(htlc_idx, htlc)| {
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];

// TODO(waterson) fallible: move this somewhere!
let per_commitment_point = self.signer.get_per_commitment_point(
trusted_tx.commitment_number(), &self.secp_ctx,
).unwrap();

ExternalHTLCClaim {
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: per_commitment_point,
htlc: htlc.clone(),
preimage: *preimage,
counterparty_sig: counterparty_htlc_sig,
Expand Down
218 changes: 183 additions & 35 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

190 changes: 172 additions & 18 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -470,7 +470,7 @@ impl MsgHandleErrInternal {
log_level: Level::Warn,
},
},
ChannelError::Ignore(msg) => LightningError {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => LightningError {
err: msg,
action: msgs::ErrorAction::IgnoreError,
},
Expand DownExpand Up@@ -663,6 +663,15 @@ impl_writeable_tlv_based_enum!(RAAMonitorUpdateBlockingAction,
(0, ForwardedPaymentInboundClaim) => { (0, channel_id, required), (2, htlc_id, required) }
;);

pub(super) enum ChannelRetryState {
FundingCreated(msgs::FundingCreated),
FundingSigned(msgs::FundingSigned),
ChannelReestablish(msgs::ChannelReestablish),
CommitmentSigned(msgs::CommitmentSigned),
CompleteAcceptingInboundV1Channel(),
CompleteCreatingOutboundV1Channel(),
}


/// State we hold per-peer.
pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
Expand All@@ -689,6 +698,8 @@ pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
/// removed, and an InboundV1Channel is created and placed in the `inbound_v1_channel_by_id` table. If
/// the channel is rejected, then the entry is simply removed.
pub(super) inbound_channel_request_by_id: HashMap<ChannelId, InboundChannelRequest>,
/// Messages that we attempted to process but returned `ChannelError::Retry`.
pub(super) retry_state_by_id: HashMap<ChannelId, ChannelRetryState>,
/// The latest `InitFeatures` we heard from the peer.
latest_features: InitFeatures,
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
Expand DownExpand Up@@ -1812,7 +1823,7 @@ macro_rules! convert_chan_err {
ChannelError::Warn(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
},
ChannelError::Ignore(msg) => {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
},
ChannelError::Close(msg) => {
Expand All@@ -1828,7 +1839,7 @@ macro_rules! convert_chan_err {
match $err {
// We should only ever have `ChannelError::Close` when unfunded channels error.
// In any case, just close the channel.
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Retry(msg) | ChannelError::Close(msg) => {
log_error!($self.logger, "Closing unfunded channel {} due to an error: {}", &$channel_id, msg);
update_maps_on_chan_removal!($self, &$channel_context);
let shutdown_res = $channel_context.force_shutdown(false);
Expand DownExpand Up@@ -2286,9 +2297,18 @@ where
},
}
};
let res = channel.get_open_channel(self.genesis_hash.clone());

let temporary_channel_id = channel.context.channel_id();
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(temporary_channel_id.clone(), ChannelRetryState::CompleteCreatingOutboundV1Channel());
}

match peer_state.outbound_v1_channel_by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
if cfg!(fuzzing) {
Expand All@@ -2300,13 +2320,35 @@ where
hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
}

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: res,
});
Ok(temporary_channel_id)
}

fn complete_creating_outbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying create for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.outbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find outbound channel with temporary ID {temporary_channel_id} for which to complete creation");
return;
}

log_info!(self.logger, "Found outbound channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for outbound channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for outbound channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
});
}

fn list_funded_channels_with_filter<Fn: FnMut(&(&ChannelId, &Channel<SP>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
Expand DownExpand Up@@ -5423,16 +5465,66 @@ where
let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
channel.context.set_outbound_scid_alias(outbound_scid_alias);

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
// If the channel context has the first commitment point, then we can immediately complete the
// channel acceptance by sending the accept_channel message. Otherwise, we'll need to wait for
// the signer to assign us the first commitment point.
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(*temporary_channel_id, ChannelRetryState::CompleteAcceptingInboundV1Channel());
}

peer_state.inbound_v1_channel_by_id.insert(temporary_channel_id.clone(), channel);

Ok(())
}

/// Performs `f` with the mutable peer state for the specified counterparty node.
///
/// Returns `None` if no such counterparty node exists; otherwise, returns the result of `f`
/// wrapped in `Some`.
fn with_mut_peer_state<Out, Fn>(&self, counterparty_node_id: &PublicKey, f: Fn) -> Option<Out>
where Fn: FnOnce(&mut PeerState<SP>) -> Out
{
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Some(f(&mut *peer_state_lock))
}

/// Retries an `InboundV1Channel` accept.
///
/// Attempts to resolve the first per-commitment point, and if successful broadcasts the
/// `accept_channel` message to the peer.
fn retry_accepting_inbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying accept for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.inbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find channel with temporary ID {temporary_channel_id} for which to complete accepting");
return;
}

log_info!(self.logger, "Found channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
});
}

/// Gets the number of peers which match the given filter and do not have any funded, outbound,
/// or 0-conf channels.
///
Expand DownExpand Up@@ -5622,6 +5714,13 @@ where
Some(inbound_chan) => {
match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &self.logger) {
Ok(res) => res,
Err((inbound_chan, err @ ChannelError::Retry(_))) => {
// If we get an `Retry` error then something transient went wrong. Put the channel
// back into the table and bail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

when does this get retried (would be good to doc here)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I am assuming that there will be a to-be-determined mechanism that allows the remote signer's implementation to initiate a restart.

peer_state.inbound_v1_channel_by_id.insert(msg.temporary_channel_id, inbound_chan);
peer_state.retry_state_by_id.insert(msg.temporary_channel_id, ChannelRetryState::FundingCreated(msg.clone()));
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.temporary_channel_id));
},
Err((mut inbound_chan, err)) => {
// We've already removed this inbound channel from the map in `PeerState`
// above so at this point we just need to clean up any lingering entries
Expand DownExpand Up@@ -5698,8 +5797,13 @@ where
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let monitor = try_chan_entry!(self,
chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
let res = chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::FundingSigned(msg.clone()));
}
}
let monitor = try_chan_entry!(self, res, chan);
let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
Expand DownExpand Up@@ -6008,7 +6112,13 @@ where
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let funding_txo = chan.get().context.get_funding_txo();
let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
let res = chan.get_mut().commitment_signed(&msg, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::CommitmentSigned(msg.clone()));
}
}
let monitor_update_opt = try_chan_entry!(self, res, chan);
if let Some(monitor_update) = monitor_update_opt {
handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
peer_state, per_peer_state, chan).map(|_| ())
Expand DownExpand Up@@ -6280,9 +6390,15 @@ where
// disconnect, so Channel's reestablish will never hand us any holding cell
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_entry!(self, chan.get_mut().channel_reestablish(
let res = chan.get_mut().channel_reestablish(
msg, &self.logger, &self.node_signer, self.genesis_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan);
&self.default_configuration, &*self.best_block.read().unwrap());
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::ChannelReestablish(msg.clone()));
}
}
let responses = try_chan_entry!(self, res, chan);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
Expand DownExpand Up@@ -6830,6 +6946,42 @@ where
let mut ev;
process_events_body!(self, ev, { handler(ev).await });
}

/// Resumes processing for a channel awaiting a signature.
///
/// If the `ChannelSigner` for a channel has previously returned an `Err`, then activity on the
/// channel will have been suspended pending the required signature becoming available. This
/// resumes the channel's operation.
pub fn retry_channel(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey) -> Result<(), APIError> {
log_info!(self.logger, "Running retry_channel for {channel_id} on {counterparty_node_id}");
let retry_state = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or(APIError::APIMisuseError {
err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id)
})?;

let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.retry_state_by_id.remove(channel_id) {
Some(retry_state) => Ok(retry_state),
None => Err(APIError::APIMisuseError {
err: format!("Can't find a channel matching the passed channel_id {}", channel_id)
}),
}
}?;

match retry_state {
ChannelRetryState::FundingCreated(ref msg) => self.handle_funding_created(counterparty_node_id, msg),
ChannelRetryState::FundingSigned(ref msg) => self.handle_funding_signed(counterparty_node_id, msg),
ChannelRetryState::ChannelReestablish(ref msg) => self.handle_channel_reestablish(counterparty_node_id, msg),
ChannelRetryState::CommitmentSigned(ref msg) => self.handle_commitment_signed(counterparty_node_id, msg),
ChannelRetryState::CompleteAcceptingInboundV1Channel() => self.retry_accepting_inbound_v1_channel(counterparty_node_id, channel_id),
ChannelRetryState::CompleteCreatingOutboundV1Channel() => self.complete_creating_outbound_v1_channel(counterparty_node_id, channel_id),
};

Ok(())
}
}

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> MessageSendEventsProvider for ChannelManager<M, T, ES, NS, SP, F, R, L>
Expand DownExpand Up@@ -7504,6 +7656,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: init_msg.features.clone(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand DownExpand Up@@ -8752,6 +8905,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand Down
34 changes: 31 additions & 3 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,8 @@ use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
#[cfg(test)]
use crate::util::logger::Logger;
use crate::util::ser::{ReadableArgs, Writeable};

use bitcoin::blockdata::block::{Block, BlockHeader};
Expand DownExpand Up@@ -414,6 +416,31 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> BlockHeader {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.
///
/// 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.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let signer = (|| {
if let Some(local_chan) = chan_lock.channel_by_id.get(chan_id) {
return local_chan.get_signer();
}
if let Some(local_chan) = chan_lock.inbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
if let Some(local_chan) = chan_lock.outbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
panic!("Couldn't find a channel with id {}", chan_id);
})();
log_debug!(self.logger, "Setting channel {} as available={}", chan_id, available);
signer.as_ecdsa().unwrap().set_available(available);
}
}

/// If we need an unsafe pointer to a `Node` (ie to reference it in a thread
Expand DownExpand Up@@ -2031,12 +2058,13 @@ macro_rules! expect_channel_shutdown_state {
}

#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) {
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) -> ChannelId {
let events = node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
crate::events::Event::ChannelPending { ref counterparty_node_id, .. } => {
match &events[0] {
crate::events::Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
assert_eq!(*expected_counterparty_node_id, *counterparty_node_id);
*channel_id
},
_ => panic!("Unexpected event"),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow get_per_commitment_point to fail. by waterson · Pull Request #2487 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
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
4 changes: 1 addition & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2752,9 +2752,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
},
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
),
per_commitment_point: htlc.per_commitment_point,
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
Expand Down
8 changes: 8 additions & 0 deletions lightning/src/chain/onchaintx.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -179,6 +179,7 @@ pub(crate) struct ExternalHTLCClaim {
pub(crate) htlc: HTLCOutputInCommitment,
pub(crate) preimage: Option<PaymentPreimage>,
pub(crate) counterparty_sig: Signature,
pub(crate) per_commitment_point: bitcoin::secp256k1::PublicKey,
}

// Represents the different types of claims for which events are yielded externally to satisfy said
Expand DownExpand Up@@ -1188,9 +1189,16 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
})
.map(|(htlc_idx, htlc)| {
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];

// TODO(waterson) fallible: move this somewhere!
let per_commitment_point = self.signer.get_per_commitment_point(
trusted_tx.commitment_number(), &self.secp_ctx,
).unwrap();

ExternalHTLCClaim {
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: per_commitment_point,
htlc: htlc.clone(),
preimage: *preimage,
counterparty_sig: counterparty_htlc_sig,
Expand Down
218 changes: 183 additions & 35 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

190 changes: 172 additions & 18 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -470,7 +470,7 @@ impl MsgHandleErrInternal {
log_level: Level::Warn,
},
},
ChannelError::Ignore(msg) => LightningError {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => LightningError {
err: msg,
action: msgs::ErrorAction::IgnoreError,
},
Expand DownExpand Up@@ -663,6 +663,15 @@ impl_writeable_tlv_based_enum!(RAAMonitorUpdateBlockingAction,
(0, ForwardedPaymentInboundClaim) => { (0, channel_id, required), (2, htlc_id, required) }
;);

pub(super) enum ChannelRetryState {
FundingCreated(msgs::FundingCreated),
FundingSigned(msgs::FundingSigned),
ChannelReestablish(msgs::ChannelReestablish),
CommitmentSigned(msgs::CommitmentSigned),
CompleteAcceptingInboundV1Channel(),
CompleteCreatingOutboundV1Channel(),
}


/// State we hold per-peer.
pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
Expand All@@ -689,6 +698,8 @@ pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
/// removed, and an InboundV1Channel is created and placed in the `inbound_v1_channel_by_id` table. If
/// the channel is rejected, then the entry is simply removed.
pub(super) inbound_channel_request_by_id: HashMap<ChannelId, InboundChannelRequest>,
/// Messages that we attempted to process but returned `ChannelError::Retry`.
pub(super) retry_state_by_id: HashMap<ChannelId, ChannelRetryState>,
/// The latest `InitFeatures` we heard from the peer.
latest_features: InitFeatures,
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
Expand DownExpand Up@@ -1812,7 +1823,7 @@ macro_rules! convert_chan_err {
ChannelError::Warn(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
},
ChannelError::Ignore(msg) => {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
},
ChannelError::Close(msg) => {
Expand All@@ -1828,7 +1839,7 @@ macro_rules! convert_chan_err {
match $err {
// We should only ever have `ChannelError::Close` when unfunded channels error.
// In any case, just close the channel.
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Retry(msg) | ChannelError::Close(msg) => {
log_error!($self.logger, "Closing unfunded channel {} due to an error: {}", &$channel_id, msg);
update_maps_on_chan_removal!($self, &$channel_context);
let shutdown_res = $channel_context.force_shutdown(false);
Expand DownExpand Up@@ -2286,9 +2297,18 @@ where
},
}
};
let res = channel.get_open_channel(self.genesis_hash.clone());

let temporary_channel_id = channel.context.channel_id();
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(temporary_channel_id.clone(), ChannelRetryState::CompleteCreatingOutboundV1Channel());
}

match peer_state.outbound_v1_channel_by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
if cfg!(fuzzing) {
Expand All@@ -2300,13 +2320,35 @@ where
hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
}

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: res,
});
Ok(temporary_channel_id)
}

fn complete_creating_outbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying create for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.outbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find outbound channel with temporary ID {temporary_channel_id} for which to complete creation");
return;
}

log_info!(self.logger, "Found outbound channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for outbound channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for outbound channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
});
}

fn list_funded_channels_with_filter<Fn: FnMut(&(&ChannelId, &Channel<SP>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
Expand DownExpand Up@@ -5423,16 +5465,66 @@ where
let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
channel.context.set_outbound_scid_alias(outbound_scid_alias);

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
// If the channel context has the first commitment point, then we can immediately complete the
// channel acceptance by sending the accept_channel message. Otherwise, we'll need to wait for
// the signer to assign us the first commitment point.
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(*temporary_channel_id, ChannelRetryState::CompleteAcceptingInboundV1Channel());
}

peer_state.inbound_v1_channel_by_id.insert(temporary_channel_id.clone(), channel);

Ok(())
}

/// Performs `f` with the mutable peer state for the specified counterparty node.
///
/// Returns `None` if no such counterparty node exists; otherwise, returns the result of `f`
/// wrapped in `Some`.
fn with_mut_peer_state<Out, Fn>(&self, counterparty_node_id: &PublicKey, f: Fn) -> Option<Out>
where Fn: FnOnce(&mut PeerState<SP>) -> Out
{
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Some(f(&mut *peer_state_lock))
}

/// Retries an `InboundV1Channel` accept.
///
/// Attempts to resolve the first per-commitment point, and if successful broadcasts the
/// `accept_channel` message to the peer.
fn retry_accepting_inbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying accept for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.inbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find channel with temporary ID {temporary_channel_id} for which to complete accepting");
return;
}

log_info!(self.logger, "Found channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
});
}

/// Gets the number of peers which match the given filter and do not have any funded, outbound,
/// or 0-conf channels.
///
Expand DownExpand Up@@ -5622,6 +5714,13 @@ where
Some(inbound_chan) => {
match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &self.logger) {
Ok(res) => res,
Err((inbound_chan, err @ ChannelError::Retry(_))) => {
// If we get an `Retry` error then something transient went wrong. Put the channel
// back into the table and bail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

when does this get retried (would be good to doc here)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I am assuming that there will be a to-be-determined mechanism that allows the remote signer's implementation to initiate a restart.

peer_state.inbound_v1_channel_by_id.insert(msg.temporary_channel_id, inbound_chan);
peer_state.retry_state_by_id.insert(msg.temporary_channel_id, ChannelRetryState::FundingCreated(msg.clone()));
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.temporary_channel_id));
},
Err((mut inbound_chan, err)) => {
// We've already removed this inbound channel from the map in `PeerState`
// above so at this point we just need to clean up any lingering entries
Expand DownExpand Up@@ -5698,8 +5797,13 @@ where
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let monitor = try_chan_entry!(self,
chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
let res = chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::FundingSigned(msg.clone()));
}
}
let monitor = try_chan_entry!(self, res, chan);
let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
Expand DownExpand Up@@ -6008,7 +6112,13 @@ where
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let funding_txo = chan.get().context.get_funding_txo();
let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
let res = chan.get_mut().commitment_signed(&msg, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::CommitmentSigned(msg.clone()));
}
}
let monitor_update_opt = try_chan_entry!(self, res, chan);
if let Some(monitor_update) = monitor_update_opt {
handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
peer_state, per_peer_state, chan).map(|_| ())
Expand DownExpand Up@@ -6280,9 +6390,15 @@ where
// disconnect, so Channel's reestablish will never hand us any holding cell
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_entry!(self, chan.get_mut().channel_reestablish(
let res = chan.get_mut().channel_reestablish(
msg, &self.logger, &self.node_signer, self.genesis_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan);
&self.default_configuration, &*self.best_block.read().unwrap());
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::ChannelReestablish(msg.clone()));
}
}
let responses = try_chan_entry!(self, res, chan);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
Expand DownExpand Up@@ -6830,6 +6946,42 @@ where
let mut ev;
process_events_body!(self, ev, { handler(ev).await });
}

/// Resumes processing for a channel awaiting a signature.
///
/// If the `ChannelSigner` for a channel has previously returned an `Err`, then activity on the
/// channel will have been suspended pending the required signature becoming available. This
/// resumes the channel's operation.
pub fn retry_channel(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey) -> Result<(), APIError> {
log_info!(self.logger, "Running retry_channel for {channel_id} on {counterparty_node_id}");
let retry_state = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or(APIError::APIMisuseError {
err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id)
})?;

let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.retry_state_by_id.remove(channel_id) {
Some(retry_state) => Ok(retry_state),
None => Err(APIError::APIMisuseError {
err: format!("Can't find a channel matching the passed channel_id {}", channel_id)
}),
}
}?;

match retry_state {
ChannelRetryState::FundingCreated(ref msg) => self.handle_funding_created(counterparty_node_id, msg),
ChannelRetryState::FundingSigned(ref msg) => self.handle_funding_signed(counterparty_node_id, msg),
ChannelRetryState::ChannelReestablish(ref msg) => self.handle_channel_reestablish(counterparty_node_id, msg),
ChannelRetryState::CommitmentSigned(ref msg) => self.handle_commitment_signed(counterparty_node_id, msg),
ChannelRetryState::CompleteAcceptingInboundV1Channel() => self.retry_accepting_inbound_v1_channel(counterparty_node_id, channel_id),
ChannelRetryState::CompleteCreatingOutboundV1Channel() => self.complete_creating_outbound_v1_channel(counterparty_node_id, channel_id),
};

Ok(())
}
}

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> MessageSendEventsProvider for ChannelManager<M, T, ES, NS, SP, F, R, L>
Expand DownExpand Up@@ -7504,6 +7656,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: init_msg.features.clone(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand DownExpand Up@@ -8752,6 +8905,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand Down
34 changes: 31 additions & 3 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,8 @@ use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
#[cfg(test)]
use crate::util::logger::Logger;
use crate::util::ser::{ReadableArgs, Writeable};

use bitcoin::blockdata::block::{Block, BlockHeader};
Expand DownExpand Up@@ -414,6 +416,31 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> BlockHeader {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.
///
/// 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.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let signer = (|| {
if let Some(local_chan) = chan_lock.channel_by_id.get(chan_id) {
return local_chan.get_signer();
}
if let Some(local_chan) = chan_lock.inbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
if let Some(local_chan) = chan_lock.outbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
panic!("Couldn't find a channel with id {}", chan_id);
})();
log_debug!(self.logger, "Setting channel {} as available={}", chan_id, available);
signer.as_ecdsa().unwrap().set_available(available);
}
}

/// If we need an unsafe pointer to a `Node` (ie to reference it in a thread
Expand DownExpand Up@@ -2031,12 +2058,13 @@ macro_rules! expect_channel_shutdown_state {
}

#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) {
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) -> ChannelId {
let events = node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
crate::events::Event::ChannelPending { ref counterparty_node_id, .. } => {
match &events[0] {
crate::events::Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
assert_eq!(*expected_counterparty_node_id, *counterparty_node_id);
*channel_id
},
_ => panic!("Unexpected event"),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Allow get_per_commitment_point to fail. by waterson · Pull Request #2487 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
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
4 changes: 1 addition & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2752,9 +2752,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
},
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
),
per_commitment_point: htlc.per_commitment_point,
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
Expand Down
8 changes: 8 additions & 0 deletions lightning/src/chain/onchaintx.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -179,6 +179,7 @@ pub(crate) struct ExternalHTLCClaim {
pub(crate) htlc: HTLCOutputInCommitment,
pub(crate) preimage: Option<PaymentPreimage>,
pub(crate) counterparty_sig: Signature,
pub(crate) per_commitment_point: bitcoin::secp256k1::PublicKey,
}

// Represents the different types of claims for which events are yielded externally to satisfy said
Expand DownExpand Up@@ -1188,9 +1189,16 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
})
.map(|(htlc_idx, htlc)| {
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];

// TODO(waterson) fallible: move this somewhere!
let per_commitment_point = self.signer.get_per_commitment_point(
trusted_tx.commitment_number(), &self.secp_ctx,
).unwrap();

ExternalHTLCClaim {
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: per_commitment_point,
htlc: htlc.clone(),
preimage: *preimage,
counterparty_sig: counterparty_htlc_sig,
Expand Down
218 changes: 183 additions & 35 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

190 changes: 172 additions & 18 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -470,7 +470,7 @@ impl MsgHandleErrInternal {
log_level: Level::Warn,
},
},
ChannelError::Ignore(msg) => LightningError {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => LightningError {
err: msg,
action: msgs::ErrorAction::IgnoreError,
},
Expand DownExpand Up@@ -663,6 +663,15 @@ impl_writeable_tlv_based_enum!(RAAMonitorUpdateBlockingAction,
(0, ForwardedPaymentInboundClaim) => { (0, channel_id, required), (2, htlc_id, required) }
;);

pub(super) enum ChannelRetryState {
FundingCreated(msgs::FundingCreated),
FundingSigned(msgs::FundingSigned),
ChannelReestablish(msgs::ChannelReestablish),
CommitmentSigned(msgs::CommitmentSigned),
CompleteAcceptingInboundV1Channel(),
CompleteCreatingOutboundV1Channel(),
}


/// State we hold per-peer.
pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
Expand All@@ -689,6 +698,8 @@ pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
/// removed, and an InboundV1Channel is created and placed in the `inbound_v1_channel_by_id` table. If
/// the channel is rejected, then the entry is simply removed.
pub(super) inbound_channel_request_by_id: HashMap<ChannelId, InboundChannelRequest>,
/// Messages that we attempted to process but returned `ChannelError::Retry`.
pub(super) retry_state_by_id: HashMap<ChannelId, ChannelRetryState>,
/// The latest `InitFeatures` we heard from the peer.
latest_features: InitFeatures,
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
Expand DownExpand Up@@ -1812,7 +1823,7 @@ macro_rules! convert_chan_err {
ChannelError::Warn(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
},
ChannelError::Ignore(msg) => {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
},
ChannelError::Close(msg) => {
Expand All@@ -1828,7 +1839,7 @@ macro_rules! convert_chan_err {
match $err {
// We should only ever have `ChannelError::Close` when unfunded channels error.
// In any case, just close the channel.
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Retry(msg) | ChannelError::Close(msg) => {
log_error!($self.logger, "Closing unfunded channel {} due to an error: {}", &$channel_id, msg);
update_maps_on_chan_removal!($self, &$channel_context);
let shutdown_res = $channel_context.force_shutdown(false);
Expand DownExpand Up@@ -2286,9 +2297,18 @@ where
},
}
};
let res = channel.get_open_channel(self.genesis_hash.clone());

let temporary_channel_id = channel.context.channel_id();
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(temporary_channel_id.clone(), ChannelRetryState::CompleteCreatingOutboundV1Channel());
}

match peer_state.outbound_v1_channel_by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
if cfg!(fuzzing) {
Expand All@@ -2300,13 +2320,35 @@ where
hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
}

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: res,
});
Ok(temporary_channel_id)
}

fn complete_creating_outbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying create for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.outbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find outbound channel with temporary ID {temporary_channel_id} for which to complete creation");
return;
}

log_info!(self.logger, "Found outbound channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for outbound channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for outbound channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
});
}

fn list_funded_channels_with_filter<Fn: FnMut(&(&ChannelId, &Channel<SP>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
Expand DownExpand Up@@ -5423,16 +5465,66 @@ where
let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
channel.context.set_outbound_scid_alias(outbound_scid_alias);

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
// If the channel context has the first commitment point, then we can immediately complete the
// channel acceptance by sending the accept_channel message. Otherwise, we'll need to wait for
// the signer to assign us the first commitment point.
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(*temporary_channel_id, ChannelRetryState::CompleteAcceptingInboundV1Channel());
}

peer_state.inbound_v1_channel_by_id.insert(temporary_channel_id.clone(), channel);

Ok(())
}

/// Performs `f` with the mutable peer state for the specified counterparty node.
///
/// Returns `None` if no such counterparty node exists; otherwise, returns the result of `f`
/// wrapped in `Some`.
fn with_mut_peer_state<Out, Fn>(&self, counterparty_node_id: &PublicKey, f: Fn) -> Option<Out>
where Fn: FnOnce(&mut PeerState<SP>) -> Out
{
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Some(f(&mut *peer_state_lock))
}

/// Retries an `InboundV1Channel` accept.
///
/// Attempts to resolve the first per-commitment point, and if successful broadcasts the
/// `accept_channel` message to the peer.
fn retry_accepting_inbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying accept for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.inbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find channel with temporary ID {temporary_channel_id} for which to complete accepting");
return;
}

log_info!(self.logger, "Found channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
});
}

/// Gets the number of peers which match the given filter and do not have any funded, outbound,
/// or 0-conf channels.
///
Expand DownExpand Up@@ -5622,6 +5714,13 @@ where
Some(inbound_chan) => {
match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &self.logger) {
Ok(res) => res,
Err((inbound_chan, err @ ChannelError::Retry(_))) => {
// If we get an `Retry` error then something transient went wrong. Put the channel
// back into the table and bail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

when does this get retried (would be good to doc here)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I am assuming that there will be a to-be-determined mechanism that allows the remote signer's implementation to initiate a restart.

peer_state.inbound_v1_channel_by_id.insert(msg.temporary_channel_id, inbound_chan);
peer_state.retry_state_by_id.insert(msg.temporary_channel_id, ChannelRetryState::FundingCreated(msg.clone()));
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.temporary_channel_id));
},
Err((mut inbound_chan, err)) => {
// We've already removed this inbound channel from the map in `PeerState`
// above so at this point we just need to clean up any lingering entries
Expand DownExpand Up@@ -5698,8 +5797,13 @@ where
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let monitor = try_chan_entry!(self,
chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
let res = chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::FundingSigned(msg.clone()));
}
}
let monitor = try_chan_entry!(self, res, chan);
let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
Expand DownExpand Up@@ -6008,7 +6112,13 @@ where
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let funding_txo = chan.get().context.get_funding_txo();
let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
let res = chan.get_mut().commitment_signed(&msg, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::CommitmentSigned(msg.clone()));
}
}
let monitor_update_opt = try_chan_entry!(self, res, chan);
if let Some(monitor_update) = monitor_update_opt {
handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
peer_state, per_peer_state, chan).map(|_| ())
Expand DownExpand Up@@ -6280,9 +6390,15 @@ where
// disconnect, so Channel's reestablish will never hand us any holding cell
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_entry!(self, chan.get_mut().channel_reestablish(
let res = chan.get_mut().channel_reestablish(
msg, &self.logger, &self.node_signer, self.genesis_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan);
&self.default_configuration, &*self.best_block.read().unwrap());
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::ChannelReestablish(msg.clone()));
}
}
let responses = try_chan_entry!(self, res, chan);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
Expand DownExpand Up@@ -6830,6 +6946,42 @@ where
let mut ev;
process_events_body!(self, ev, { handler(ev).await });
}

/// Resumes processing for a channel awaiting a signature.
///
/// If the `ChannelSigner` for a channel has previously returned an `Err`, then activity on the
/// channel will have been suspended pending the required signature becoming available. This
/// resumes the channel's operation.
pub fn retry_channel(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey) -> Result<(), APIError> {
log_info!(self.logger, "Running retry_channel for {channel_id} on {counterparty_node_id}");
let retry_state = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or(APIError::APIMisuseError {
err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id)
})?;

let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.retry_state_by_id.remove(channel_id) {
Some(retry_state) => Ok(retry_state),
None => Err(APIError::APIMisuseError {
err: format!("Can't find a channel matching the passed channel_id {}", channel_id)
}),
}
}?;

match retry_state {
ChannelRetryState::FundingCreated(ref msg) => self.handle_funding_created(counterparty_node_id, msg),
ChannelRetryState::FundingSigned(ref msg) => self.handle_funding_signed(counterparty_node_id, msg),
ChannelRetryState::ChannelReestablish(ref msg) => self.handle_channel_reestablish(counterparty_node_id, msg),
ChannelRetryState::CommitmentSigned(ref msg) => self.handle_commitment_signed(counterparty_node_id, msg),
ChannelRetryState::CompleteAcceptingInboundV1Channel() => self.retry_accepting_inbound_v1_channel(counterparty_node_id, channel_id),
ChannelRetryState::CompleteCreatingOutboundV1Channel() => self.complete_creating_outbound_v1_channel(counterparty_node_id, channel_id),
};

Ok(())
}
}

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> MessageSendEventsProvider for ChannelManager<M, T, ES, NS, SP, F, R, L>
Expand DownExpand Up@@ -7504,6 +7656,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: init_msg.features.clone(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand DownExpand Up@@ -8752,6 +8905,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand Down
34 changes: 31 additions & 3 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,8 @@ use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
#[cfg(test)]
use crate::util::logger::Logger;
use crate::util::ser::{ReadableArgs, Writeable};

use bitcoin::blockdata::block::{Block, BlockHeader};
Expand DownExpand Up@@ -414,6 +416,31 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> BlockHeader {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.
///
/// 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.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let signer = (|| {
if let Some(local_chan) = chan_lock.channel_by_id.get(chan_id) {
return local_chan.get_signer();
}
if let Some(local_chan) = chan_lock.inbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
if let Some(local_chan) = chan_lock.outbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
panic!("Couldn't find a channel with id {}", chan_id);
})();
log_debug!(self.logger, "Setting channel {} as available={}", chan_id, available);
signer.as_ecdsa().unwrap().set_available(available);
}
}

/// If we need an unsafe pointer to a `Node` (ie to reference it in a thread
Expand DownExpand Up@@ -2031,12 +2058,13 @@ macro_rules! expect_channel_shutdown_state {
}

#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) {
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) -> ChannelId {
let events = node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
crate::events::Event::ChannelPending { ref counterparty_node_id, .. } => {
match &events[0] {
crate::events::Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
assert_eq!(*expected_counterparty_node_id, *counterparty_node_id);
*channel_id
},
_ => panic!("Unexpected event"),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Allow get_per_commitment_point to fail. by waterson · Pull Request #2487 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
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
4 changes: 1 addition & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2752,9 +2752,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
},
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
),
per_commitment_point: htlc.per_commitment_point,
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
Expand Down
8 changes: 8 additions & 0 deletions lightning/src/chain/onchaintx.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -179,6 +179,7 @@ pub(crate) struct ExternalHTLCClaim {
pub(crate) htlc: HTLCOutputInCommitment,
pub(crate) preimage: Option<PaymentPreimage>,
pub(crate) counterparty_sig: Signature,
pub(crate) per_commitment_point: bitcoin::secp256k1::PublicKey,
}

// Represents the different types of claims for which events are yielded externally to satisfy said
Expand DownExpand Up@@ -1188,9 +1189,16 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
})
.map(|(htlc_idx, htlc)| {
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];

// TODO(waterson) fallible: move this somewhere!
let per_commitment_point = self.signer.get_per_commitment_point(
trusted_tx.commitment_number(), &self.secp_ctx,
).unwrap();

ExternalHTLCClaim {
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: per_commitment_point,
htlc: htlc.clone(),
preimage: *preimage,
counterparty_sig: counterparty_htlc_sig,
Expand Down
218 changes: 183 additions & 35 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

190 changes: 172 additions & 18 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -470,7 +470,7 @@ impl MsgHandleErrInternal {
log_level: Level::Warn,
},
},
ChannelError::Ignore(msg) => LightningError {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => LightningError {
err: msg,
action: msgs::ErrorAction::IgnoreError,
},
Expand DownExpand Up@@ -663,6 +663,15 @@ impl_writeable_tlv_based_enum!(RAAMonitorUpdateBlockingAction,
(0, ForwardedPaymentInboundClaim) => { (0, channel_id, required), (2, htlc_id, required) }
;);

pub(super) enum ChannelRetryState {
FundingCreated(msgs::FundingCreated),
FundingSigned(msgs::FundingSigned),
ChannelReestablish(msgs::ChannelReestablish),
CommitmentSigned(msgs::CommitmentSigned),
CompleteAcceptingInboundV1Channel(),
CompleteCreatingOutboundV1Channel(),
}


/// State we hold per-peer.
pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
Expand All@@ -689,6 +698,8 @@ pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
/// removed, and an InboundV1Channel is created and placed in the `inbound_v1_channel_by_id` table. If
/// the channel is rejected, then the entry is simply removed.
pub(super) inbound_channel_request_by_id: HashMap<ChannelId, InboundChannelRequest>,
/// Messages that we attempted to process but returned `ChannelError::Retry`.
pub(super) retry_state_by_id: HashMap<ChannelId, ChannelRetryState>,
/// The latest `InitFeatures` we heard from the peer.
latest_features: InitFeatures,
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
Expand DownExpand Up@@ -1812,7 +1823,7 @@ macro_rules! convert_chan_err {
ChannelError::Warn(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
},
ChannelError::Ignore(msg) => {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
},
ChannelError::Close(msg) => {
Expand All@@ -1828,7 +1839,7 @@ macro_rules! convert_chan_err {
match $err {
// We should only ever have `ChannelError::Close` when unfunded channels error.
// In any case, just close the channel.
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Retry(msg) | ChannelError::Close(msg) => {
log_error!($self.logger, "Closing unfunded channel {} due to an error: {}", &$channel_id, msg);
update_maps_on_chan_removal!($self, &$channel_context);
let shutdown_res = $channel_context.force_shutdown(false);
Expand DownExpand Up@@ -2286,9 +2297,18 @@ where
},
}
};
let res = channel.get_open_channel(self.genesis_hash.clone());

let temporary_channel_id = channel.context.channel_id();
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(temporary_channel_id.clone(), ChannelRetryState::CompleteCreatingOutboundV1Channel());
}

match peer_state.outbound_v1_channel_by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
if cfg!(fuzzing) {
Expand All@@ -2300,13 +2320,35 @@ where
hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
}

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: res,
});
Ok(temporary_channel_id)
}

fn complete_creating_outbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying create for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.outbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find outbound channel with temporary ID {temporary_channel_id} for which to complete creation");
return;
}

log_info!(self.logger, "Found outbound channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for outbound channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for outbound channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
});
}

fn list_funded_channels_with_filter<Fn: FnMut(&(&ChannelId, &Channel<SP>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
Expand DownExpand Up@@ -5423,16 +5465,66 @@ where
let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
channel.context.set_outbound_scid_alias(outbound_scid_alias);

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
// If the channel context has the first commitment point, then we can immediately complete the
// channel acceptance by sending the accept_channel message. Otherwise, we'll need to wait for
// the signer to assign us the first commitment point.
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(*temporary_channel_id, ChannelRetryState::CompleteAcceptingInboundV1Channel());
}

peer_state.inbound_v1_channel_by_id.insert(temporary_channel_id.clone(), channel);

Ok(())
}

/// Performs `f` with the mutable peer state for the specified counterparty node.
///
/// Returns `None` if no such counterparty node exists; otherwise, returns the result of `f`
/// wrapped in `Some`.
fn with_mut_peer_state<Out, Fn>(&self, counterparty_node_id: &PublicKey, f: Fn) -> Option<Out>
where Fn: FnOnce(&mut PeerState<SP>) -> Out
{
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Some(f(&mut *peer_state_lock))
}

/// Retries an `InboundV1Channel` accept.
///
/// Attempts to resolve the first per-commitment point, and if successful broadcasts the
/// `accept_channel` message to the peer.
fn retry_accepting_inbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying accept for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.inbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find channel with temporary ID {temporary_channel_id} for which to complete accepting");
return;
}

log_info!(self.logger, "Found channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
});
}

/// Gets the number of peers which match the given filter and do not have any funded, outbound,
/// or 0-conf channels.
///
Expand DownExpand Up@@ -5622,6 +5714,13 @@ where
Some(inbound_chan) => {
match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &self.logger) {
Ok(res) => res,
Err((inbound_chan, err @ ChannelError::Retry(_))) => {
// If we get an `Retry` error then something transient went wrong. Put the channel
// back into the table and bail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

when does this get retried (would be good to doc here)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I am assuming that there will be a to-be-determined mechanism that allows the remote signer's implementation to initiate a restart.

peer_state.inbound_v1_channel_by_id.insert(msg.temporary_channel_id, inbound_chan);
peer_state.retry_state_by_id.insert(msg.temporary_channel_id, ChannelRetryState::FundingCreated(msg.clone()));
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.temporary_channel_id));
},
Err((mut inbound_chan, err)) => {
// We've already removed this inbound channel from the map in `PeerState`
// above so at this point we just need to clean up any lingering entries
Expand DownExpand Up@@ -5698,8 +5797,13 @@ where
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let monitor = try_chan_entry!(self,
chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
let res = chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::FundingSigned(msg.clone()));
}
}
let monitor = try_chan_entry!(self, res, chan);
let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
Expand DownExpand Up@@ -6008,7 +6112,13 @@ where
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let funding_txo = chan.get().context.get_funding_txo();
let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
let res = chan.get_mut().commitment_signed(&msg, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::CommitmentSigned(msg.clone()));
}
}
let monitor_update_opt = try_chan_entry!(self, res, chan);
if let Some(monitor_update) = monitor_update_opt {
handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
peer_state, per_peer_state, chan).map(|_| ())
Expand DownExpand Up@@ -6280,9 +6390,15 @@ where
// disconnect, so Channel's reestablish will never hand us any holding cell
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_entry!(self, chan.get_mut().channel_reestablish(
let res = chan.get_mut().channel_reestablish(
msg, &self.logger, &self.node_signer, self.genesis_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan);
&self.default_configuration, &*self.best_block.read().unwrap());
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::ChannelReestablish(msg.clone()));
}
}
let responses = try_chan_entry!(self, res, chan);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
Expand DownExpand Up@@ -6830,6 +6946,42 @@ where
let mut ev;
process_events_body!(self, ev, { handler(ev).await });
}

/// Resumes processing for a channel awaiting a signature.
///
/// If the `ChannelSigner` for a channel has previously returned an `Err`, then activity on the
/// channel will have been suspended pending the required signature becoming available. This
/// resumes the channel's operation.
pub fn retry_channel(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey) -> Result<(), APIError> {
log_info!(self.logger, "Running retry_channel for {channel_id} on {counterparty_node_id}");
let retry_state = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or(APIError::APIMisuseError {
err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id)
})?;

let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.retry_state_by_id.remove(channel_id) {
Some(retry_state) => Ok(retry_state),
None => Err(APIError::APIMisuseError {
err: format!("Can't find a channel matching the passed channel_id {}", channel_id)
}),
}
}?;

match retry_state {
ChannelRetryState::FundingCreated(ref msg) => self.handle_funding_created(counterparty_node_id, msg),
ChannelRetryState::FundingSigned(ref msg) => self.handle_funding_signed(counterparty_node_id, msg),
ChannelRetryState::ChannelReestablish(ref msg) => self.handle_channel_reestablish(counterparty_node_id, msg),
ChannelRetryState::CommitmentSigned(ref msg) => self.handle_commitment_signed(counterparty_node_id, msg),
ChannelRetryState::CompleteAcceptingInboundV1Channel() => self.retry_accepting_inbound_v1_channel(counterparty_node_id, channel_id),
ChannelRetryState::CompleteCreatingOutboundV1Channel() => self.complete_creating_outbound_v1_channel(counterparty_node_id, channel_id),
};

Ok(())
}
}

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> MessageSendEventsProvider for ChannelManager<M, T, ES, NS, SP, F, R, L>
Expand DownExpand Up@@ -7504,6 +7656,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: init_msg.features.clone(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand DownExpand Up@@ -8752,6 +8905,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand Down
34 changes: 31 additions & 3 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,8 @@ use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
#[cfg(test)]
use crate::util::logger::Logger;
use crate::util::ser::{ReadableArgs, Writeable};

use bitcoin::blockdata::block::{Block, BlockHeader};
Expand DownExpand Up@@ -414,6 +416,31 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> BlockHeader {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.
///
/// 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.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let signer = (|| {
if let Some(local_chan) = chan_lock.channel_by_id.get(chan_id) {
return local_chan.get_signer();
}
if let Some(local_chan) = chan_lock.inbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
if let Some(local_chan) = chan_lock.outbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
panic!("Couldn't find a channel with id {}", chan_id);
})();
log_debug!(self.logger, "Setting channel {} as available={}", chan_id, available);
signer.as_ecdsa().unwrap().set_available(available);
}
}

/// If we need an unsafe pointer to a `Node` (ie to reference it in a thread
Expand DownExpand Up@@ -2031,12 +2058,13 @@ macro_rules! expect_channel_shutdown_state {
}

#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) {
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) -> ChannelId {
let events = node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
crate::events::Event::ChannelPending { ref counterparty_node_id, .. } => {
match &events[0] {
crate::events::Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
assert_eq!(*expected_counterparty_node_id, *counterparty_node_id);
*channel_id
},
_ => panic!("Unexpected event"),
}
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Allow get_per_commitment_point to fail. by waterson · Pull Request #2487 · lightningdevkit/rust-lightning · GitHub
Skip to content
Closed
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
4 changes: 1 addition & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2752,9 +2752,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
},
commitment_txid: htlc.commitment_txid,
per_commitment_number: htlc.per_commitment_number,
per_commitment_point: self.onchain_tx_handler.signer.get_per_commitment_point(
htlc.per_commitment_number, &self.onchain_tx_handler.secp_ctx,
),
per_commitment_point: htlc.per_commitment_point,
htlc: htlc.htlc,
preimage: htlc.preimage,
counterparty_sig: htlc.counterparty_sig,
Expand Down
8 changes: 8 additions & 0 deletions lightning/src/chain/onchaintx.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -179,6 +179,7 @@ pub(crate) struct ExternalHTLCClaim {
pub(crate) htlc: HTLCOutputInCommitment,
pub(crate) preimage: Option<PaymentPreimage>,
pub(crate) counterparty_sig: Signature,
pub(crate) per_commitment_point: bitcoin::secp256k1::PublicKey,
}

// Represents the different types of claims for which events are yielded externally to satisfy said
Expand DownExpand Up@@ -1188,9 +1189,16 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
})
.map(|(htlc_idx, htlc)| {
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];

// TODO(waterson) fallible: move this somewhere!
let per_commitment_point = self.signer.get_per_commitment_point(
trusted_tx.commitment_number(), &self.secp_ctx,
).unwrap();

ExternalHTLCClaim {
commitment_txid: trusted_tx.txid(),
per_commitment_number: trusted_tx.commitment_number(),
per_commitment_point: per_commitment_point,
htlc: htlc.clone(),
preimage: *preimage,
counterparty_sig: counterparty_htlc_sig,
Expand Down
218 changes: 183 additions & 35 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

190 changes: 172 additions & 18 deletions lightning/src/ln/channelmanager.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -470,7 +470,7 @@ impl MsgHandleErrInternal {
log_level: Level::Warn,
},
},
ChannelError::Ignore(msg) => LightningError {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => LightningError {
err: msg,
action: msgs::ErrorAction::IgnoreError,
},
Expand DownExpand Up@@ -663,6 +663,15 @@ impl_writeable_tlv_based_enum!(RAAMonitorUpdateBlockingAction,
(0, ForwardedPaymentInboundClaim) => { (0, channel_id, required), (2, htlc_id, required) }
;);

pub(super) enum ChannelRetryState {
FundingCreated(msgs::FundingCreated),
FundingSigned(msgs::FundingSigned),
ChannelReestablish(msgs::ChannelReestablish),
CommitmentSigned(msgs::CommitmentSigned),
CompleteAcceptingInboundV1Channel(),
CompleteCreatingOutboundV1Channel(),
}


/// State we hold per-peer.
pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
Expand All@@ -689,6 +698,8 @@ pub(super) struct PeerState<SP: Deref> where SP::Target: SignerProvider {
/// removed, and an InboundV1Channel is created and placed in the `inbound_v1_channel_by_id` table. If
/// the channel is rejected, then the entry is simply removed.
pub(super) inbound_channel_request_by_id: HashMap<ChannelId, InboundChannelRequest>,
/// Messages that we attempted to process but returned `ChannelError::Retry`.
pub(super) retry_state_by_id: HashMap<ChannelId, ChannelRetryState>,
/// The latest `InitFeatures` we heard from the peer.
latest_features: InitFeatures,
/// Messages to send to the peer - pushed to in the same lock that they are generated in (except
Expand DownExpand Up@@ -1812,7 +1823,7 @@ macro_rules! convert_chan_err {
ChannelError::Warn(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Warn(msg), $channel_id.clone()))
},
ChannelError::Ignore(msg) => {
ChannelError::Ignore(msg) | ChannelError::Retry(msg) => {
(false, MsgHandleErrInternal::from_chan_no_close(ChannelError::Ignore(msg), $channel_id.clone()))
},
ChannelError::Close(msg) => {
Expand All@@ -1828,7 +1839,7 @@ macro_rules! convert_chan_err {
match $err {
// We should only ever have `ChannelError::Close` when unfunded channels error.
// In any case, just close the channel.
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Close(msg) => {
ChannelError::Warn(msg) | ChannelError::Ignore(msg) | ChannelError::Retry(msg) | ChannelError::Close(msg) => {
log_error!($self.logger, "Closing unfunded channel {} due to an error: {}", &$channel_id, msg);
update_maps_on_chan_removal!($self, &$channel_context);
let shutdown_res = $channel_context.force_shutdown(false);
Expand DownExpand Up@@ -2286,9 +2297,18 @@ where
},
}
};
let res = channel.get_open_channel(self.genesis_hash.clone());

let temporary_channel_id = channel.context.channel_id();
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(temporary_channel_id.clone(), ChannelRetryState::CompleteCreatingOutboundV1Channel());
}

match peer_state.outbound_v1_channel_by_id.entry(temporary_channel_id) {
hash_map::Entry::Occupied(_) => {
if cfg!(fuzzing) {
Expand All@@ -2300,13 +2320,35 @@ where
hash_map::Entry::Vacant(entry) => { entry.insert(channel); }
}

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: their_network_key,
msg: res,
});
Ok(temporary_channel_id)
}

fn complete_creating_outbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying create for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.outbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find outbound channel with temporary ID {temporary_channel_id} for which to complete creation");
return;
}

log_info!(self.logger, "Found outbound channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for outbound channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for outbound channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendOpenChannel {
node_id: *counterparty_node_id,
msg: channel.get_open_channel(self.genesis_hash.clone()),
});
});
}

fn list_funded_channels_with_filter<Fn: FnMut(&(&ChannelId, &Channel<SP>)) -> bool + Copy>(&self, f: Fn) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
Expand DownExpand Up@@ -5423,16 +5465,66 @@ where
let outbound_scid_alias = self.create_and_insert_outbound_scid_alias();
channel.context.set_outbound_scid_alias(outbound_scid_alias);

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
// If the channel context has the first commitment point, then we can immediately complete the
// channel acceptance by sending the accept_channel message. Otherwise, we'll need to wait for
// the signer to assign us the first commitment point.
if channel.context.has_first_holder_per_commitment_point() {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
} else {
log_info!(self.logger, "First per-commitment point is not available for {temporary_channel_id}, scheduling retry.");
peer_state.retry_state_by_id.insert(*temporary_channel_id, ChannelRetryState::CompleteAcceptingInboundV1Channel());
}

peer_state.inbound_v1_channel_by_id.insert(temporary_channel_id.clone(), channel);

Ok(())
}

/// Performs `f` with the mutable peer state for the specified counterparty node.
///
/// Returns `None` if no such counterparty node exists; otherwise, returns the result of `f`
/// wrapped in `Some`.
fn with_mut_peer_state<Out, Fn>(&self, counterparty_node_id: &PublicKey, f: Fn) -> Option<Out>
where Fn: FnOnce(&mut PeerState<SP>) -> Out
{
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)?;
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
Some(f(&mut *peer_state_lock))
}

/// Retries an `InboundV1Channel` accept.
///
/// Attempts to resolve the first per-commitment point, and if successful broadcasts the
/// `accept_channel` message to the peer.
fn retry_accepting_inbound_v1_channel(&self, counterparty_node_id: &PublicKey, temporary_channel_id: &ChannelId) {
log_info!(self.logger, "Retrying accept for channel {temporary_channel_id}");
self.with_mut_peer_state(counterparty_node_id, |peer_state: &mut PeerState<SP>| {
let channel_opt = peer_state.inbound_v1_channel_by_id.get_mut(temporary_channel_id);
if channel_opt.is_none() {
log_error!(self.logger, "Cannot find channel with temporary ID {temporary_channel_id} for which to complete accepting");
return;
}

log_info!(self.logger, "Found channel with temporary ID {temporary_channel_id}");

let channel = channel_opt.unwrap();
if channel.set_first_holder_per_commitment_point().is_err() {
log_error!(self.logger, "Commitment point for channel with temporary ID {temporary_channel_id} was not available on retry");
return;
}

log_info!(self.logger, "Set first per-commitment point for channel {temporary_channel_id}");

peer_state.pending_msg_events.push(events::MessageSendEvent::SendAcceptChannel {
node_id: channel.context.get_counterparty_node_id(),
msg: channel.accept_inbound_channel(),
});
});
}

/// Gets the number of peers which match the given filter and do not have any funded, outbound,
/// or 0-conf channels.
///
Expand DownExpand Up@@ -5622,6 +5714,13 @@ where
Some(inbound_chan) => {
match inbound_chan.funding_created(msg, best_block, &self.signer_provider, &self.logger) {
Ok(res) => res,
Err((inbound_chan, err @ ChannelError::Retry(_))) => {
// If we get an `Retry` error then something transient went wrong. Put the channel
// back into the table and bail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

when does this get retried (would be good to doc here)?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I am assuming that there will be a to-be-determined mechanism that allows the remote signer's implementation to initiate a restart.

peer_state.inbound_v1_channel_by_id.insert(msg.temporary_channel_id, inbound_chan);
peer_state.retry_state_by_id.insert(msg.temporary_channel_id, ChannelRetryState::FundingCreated(msg.clone()));
return Err(MsgHandleErrInternal::from_chan_no_close(err, msg.temporary_channel_id));
},
Err((mut inbound_chan, err)) => {
// We've already removed this inbound channel from the map in `PeerState`
// above so at this point we just need to clean up any lingering entries
Expand DownExpand Up@@ -5698,8 +5797,13 @@ where
let peer_state = &mut *peer_state_lock;
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let monitor = try_chan_entry!(self,
chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger), chan);
let res = chan.get_mut().funding_signed(&msg, best_block, &self.signer_provider, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::FundingSigned(msg.clone()));
}
}
let monitor = try_chan_entry!(self, res, chan);
let update_res = self.chain_monitor.watch_channel(chan.get().context.get_funding_txo().unwrap(), monitor);
let mut res = handle_new_monitor_update!(self, update_res, peer_state_lock, peer_state, per_peer_state, chan, INITIAL_MONITOR);
if let Err(MsgHandleErrInternal { ref mut shutdown_finish, .. }) = res {
Expand DownExpand Up@@ -6008,7 +6112,13 @@ where
match peer_state.channel_by_id.entry(msg.channel_id) {
hash_map::Entry::Occupied(mut chan) => {
let funding_txo = chan.get().context.get_funding_txo();
let monitor_update_opt = try_chan_entry!(self, chan.get_mut().commitment_signed(&msg, &self.logger), chan);
let res = chan.get_mut().commitment_signed(&msg, &self.logger);
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::CommitmentSigned(msg.clone()));
}
}
let monitor_update_opt = try_chan_entry!(self, res, chan);
if let Some(monitor_update) = monitor_update_opt {
handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock,
peer_state, per_peer_state, chan).map(|_| ())
Expand DownExpand Up@@ -6280,9 +6390,15 @@ where
// disconnect, so Channel's reestablish will never hand us any holding cell
// freed HTLCs to fail backwards. If in the future we no longer drop pending
// add-HTLCs on disconnect, we may be handed HTLCs to fail backwards here.
let responses = try_chan_entry!(self, chan.get_mut().channel_reestablish(
let res = chan.get_mut().channel_reestablish(
msg, &self.logger, &self.node_signer, self.genesis_hash,
&self.default_configuration, &*self.best_block.read().unwrap()), chan);
&self.default_configuration, &*self.best_block.read().unwrap());
if let Err(ref err) = &res {
if let ChannelError::Retry(_) = err {
peer_state.retry_state_by_id.insert(msg.channel_id, ChannelRetryState::ChannelReestablish(msg.clone()));
}
}
let responses = try_chan_entry!(self, res, chan);
let mut channel_update = None;
if let Some(msg) = responses.shutdown_msg {
peer_state.pending_msg_events.push(events::MessageSendEvent::SendShutdown {
Expand DownExpand Up@@ -6830,6 +6946,42 @@ where
let mut ev;
process_events_body!(self, ev, { handler(ev).await });
}

/// Resumes processing for a channel awaiting a signature.
///
/// If the `ChannelSigner` for a channel has previously returned an `Err`, then activity on the
/// channel will have been suspended pending the required signature becoming available. This
/// resumes the channel's operation.
pub fn retry_channel(&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey) -> Result<(), APIError> {
log_info!(self.logger, "Running retry_channel for {channel_id} on {counterparty_node_id}");
let retry_state = {
let per_peer_state = self.per_peer_state.read().unwrap();
let peer_state_mutex = per_peer_state.get(counterparty_node_id)
.ok_or(APIError::APIMisuseError {
err: format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id)
})?;

let mut peer_state_lock = peer_state_mutex.lock().unwrap();
let peer_state = &mut *peer_state_lock;
match peer_state.retry_state_by_id.remove(channel_id) {
Some(retry_state) => Ok(retry_state),
None => Err(APIError::APIMisuseError {
err: format!("Can't find a channel matching the passed channel_id {}", channel_id)
}),
}
}?;

match retry_state {
ChannelRetryState::FundingCreated(ref msg) => self.handle_funding_created(counterparty_node_id, msg),
ChannelRetryState::FundingSigned(ref msg) => self.handle_funding_signed(counterparty_node_id, msg),
ChannelRetryState::ChannelReestablish(ref msg) => self.handle_channel_reestablish(counterparty_node_id, msg),
ChannelRetryState::CommitmentSigned(ref msg) => self.handle_commitment_signed(counterparty_node_id, msg),
ChannelRetryState::CompleteAcceptingInboundV1Channel() => self.retry_accepting_inbound_v1_channel(counterparty_node_id, channel_id),
ChannelRetryState::CompleteCreatingOutboundV1Channel() => self.complete_creating_outbound_v1_channel(counterparty_node_id, channel_id),
};

Ok(())
}
}

impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref> MessageSendEventsProvider for ChannelManager<M, T, ES, NS, SP, F, R, L>
Expand DownExpand Up@@ -7504,6 +7656,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: init_msg.features.clone(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand DownExpand Up@@ -8752,6 +8905,7 @@ where
outbound_v1_channel_by_id: HashMap::new(),
inbound_v1_channel_by_id: HashMap::new(),
inbound_channel_request_by_id: HashMap::new(),
retry_state_by_id: HashMap::new(),
latest_features: InitFeatures::empty(),
pending_msg_events: Vec::new(),
in_flight_monitor_updates: BTreeMap::new(),
Expand Down
34 changes: 31 additions & 3 deletions lightning/src/ln/functional_test_utils.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,8 @@ use crate::util::test_utils;
use crate::util::test_utils::{panicking, TestChainMonitor, TestScorer, TestKeysInterface};
use crate::util::errors::APIError;
use crate::util::config::{UserConfig, MaxDustHTLCExposure};
#[cfg(test)]
use crate::util::logger::Logger;
use crate::util::ser::{ReadableArgs, Writeable};

use bitcoin::blockdata::block::{Block, BlockHeader};
Expand DownExpand Up@@ -414,6 +416,31 @@ impl<'a, 'b, 'c> Node<'a, 'b, 'c> {
pub fn get_block_header(&self, height: u32) -> BlockHeader {
self.blocks.lock().unwrap()[height as usize].0.header
}
/// Changes the channel signer's availability for the specified peer and channel.
///
/// 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.
#[cfg(test)]
pub fn set_channel_signer_available(&self, peer_id: &PublicKey, chan_id: &ChannelId, available: bool) {
let per_peer_state = self.node.per_peer_state.read().unwrap();
let chan_lock = per_peer_state.get(peer_id).unwrap().lock().unwrap();
let signer = (|| {
if let Some(local_chan) = chan_lock.channel_by_id.get(chan_id) {
return local_chan.get_signer();
}
if let Some(local_chan) = chan_lock.inbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
if let Some(local_chan) = chan_lock.outbound_v1_channel_by_id.get(chan_id) {
return local_chan.context.get_signer();
}
panic!("Couldn't find a channel with id {}", chan_id);
})();
log_debug!(self.logger, "Setting channel {} as available={}", chan_id, available);
signer.as_ecdsa().unwrap().set_available(available);
}
}

/// If we need an unsafe pointer to a `Node` (ie to reference it in a thread
Expand DownExpand Up@@ -2031,12 +2058,13 @@ macro_rules! expect_channel_shutdown_state {
}

#[cfg(any(test, ldk_bench, feature = "_test_utils"))]
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) {
pub fn expect_channel_pending_event<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, expected_counterparty_node_id: &PublicKey) -> ChannelId {
let events = node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
crate::events::Event::ChannelPending { ref counterparty_node_id, .. } => {
match &events[0] {
crate::events::Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
assert_eq!(*expected_counterparty_node_id, *counterparty_node_id);
*channel_id
},
_ => panic!("Unexpected event"),
}
Expand Down
Loading