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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: capacity,
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
Expand Down
96 changes: 60 additions & 36 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,6 +710,11 @@ pub(super) struct Channel<Signer: Sign> {
// Our counterparty can offer us SCID aliases which they will map to this channel when routing
// outbound payments. These can be used in invoice route hints to avoid explicitly revealing
// the channel's funding UTXO.
//
// We also use this when sending our peer a channel_update that isn't to be broadcasted
// publicly - allowing them to re-use their map of SCID -> channel for channel_update ->
// associated channel mapping.
//
// We only bother storing the most recent SCID alias at any time, though our counterparty has
// to store all of them.
latest_inbound_scid_alias: Option<u64>,
Expand DownExpand Up@@ -1307,7 +1312,7 @@ impl<Signer: Sign> Channel<Signer> {
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
holder_htlc_minimum_msat: if config.own_channel_config.our_htlc_minimum_msat == 0 { 1 } else { config.own_channel_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
minimum_depth: Some(config.own_channel_config.minimum_depth),
minimum_depth: Some(cmp::max(config.own_channel_config.minimum_depth, 1)),

counterparty_forwarding_info: None,

Expand DownExpand Up@@ -1987,12 +1992,6 @@ impl<Signer: Sign> Channel<Signer> {
if msg.minimum_depth > peer_limits.max_minimum_depth {
return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth)));
}
if msg.minimum_depth == 0 {
// Note that if this changes we should update the serialization minimum version to
// indicate to older clients that they don't understand some features of the current
// channel.
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}

if let Some(ty) = &msg.channel_type {
if *ty != self.channel_type {
Expand DownExpand Up@@ -2029,7 +2028,12 @@ impl<Signer: Sign> Channel<Signer> {
self.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
self.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat;
self.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs;
self.minimum_depth = Some(msg.minimum_depth);

if peer_limits.trust_own_funding_0conf {
self.minimum_depth = Some(msg.minimum_depth);
} else {
self.minimum_depth = Some(cmp::max(1, msg.minimum_depth));
Comment thread
valentinewallace marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would previously return an error when min_depth == 0, whereas now we'll enforce a minimum of one, so they may think we've agreed to proceed with zero confs once we send funding_created back.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Right, this is kinda a 0conf philosophy question. This PR doesn't actually support the 0conf channel type (as it was written before the 0conf channel type existed) which is the "I require 0conf" option, but we'll add that in a followup.

In general, if you're doing a 0conf channel, you've proably pre-negotiated it in some way - most likely the recipient of the channel is pre-configured to trust an LSP. There's arguably not a lot of value in negotiating 0conf in the open/accept channel flow - by the time you're there both sides of the channel should really already know if the channel should be 0conf or not.

More generally, nodes can always delay channel_ready/funding_locked as long as they want - the minimum_depth field is just a hint, and the protocol can't enforce it in any way. We're not strictly-speaking "out of spec" by delaying more than the channel recipient wanted.

}

let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.funding_pubkey,
Expand DownExpand Up@@ -2089,7 +2093,7 @@ impl<Signer: Sign> Channel<Signer> {
&self.get_counterparty_pubkeys().funding_pubkey
}

pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>), ChannelError> where L::Target: Logger {
pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if self.is_outbound() {
return Err(ChannelError::Close("Received funding_created for an outbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2164,12 +2168,12 @@ impl<Signer: Sign> Channel<Signer> {
Ok((msgs::FundingSigned {
channel_id: self.channel_id,
signature
}, channel_monitor))
}, channel_monitor, self.check_get_funding_locked(0)))
}

/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction), ChannelError> where L::Target: Logger {
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if !self.is_outbound() {
return Err(ChannelError::Close("Received funding_signed for an inbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2238,7 +2242,7 @@ impl<Signer: Sign> Channel<Signer> {

log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.channel_id()));

Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap()))
Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap(), self.check_get_funding_locked(0)))
}

/// Handles a funding_locked message from our peer. If we've already sent our funding_locked
Expand DownExpand Up@@ -3540,12 +3544,13 @@ impl<Signer: Sign> Channel<Signer> {
/// monitor update failure must *not* have been sent to the remote end, and must instead
/// have been dropped. They will be regenerated when monitor_updating_restored is called.
pub fn monitor_update_failed(&mut self, resend_raa: bool, resend_commitment: bool,
mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
resend_funding_locked: bool, mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
mut pending_fails: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>,
mut pending_finalized_claimed_htlcs: Vec<HTLCSource>
) {
self.monitor_pending_revoke_and_ack |= resend_raa;
self.monitor_pending_commitment_signed |= resend_commitment;
self.monitor_pending_funding_locked |= resend_funding_locked;
self.monitor_pending_forwards.append(&mut pending_forwards);
self.monitor_pending_failures.append(&mut pending_fails);
self.monitor_pending_finalized_fulfills.append(&mut pending_finalized_claimed_htlcs);
Expand All@@ -3559,17 +3564,28 @@ impl<Signer: Sign> Channel<Signer> {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateFailed as u32, ChannelState::MonitorUpdateFailed as u32);
self.channel_state &= !(ChannelState::MonitorUpdateFailed as u32);

let funding_broadcastable = if self.channel_state & (ChannelState::FundingSent as u32) != 0 && self.is_outbound() {
self.funding_transaction.take()
} else { None };
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
if self.is_outbound() && self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change almost seems like it belongs in a separate commit or be documented in the commit message, seemed a bit unrelated at first

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Updated the commit message.

self.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
if self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelFunded as u32 && self.minimum_depth != Some(0) {
funding_broadcastable = None;
}

// We will never broadcast the funding transaction when we're in MonitorUpdateFailed (and
// we assume the user never directly broadcasts the funding transaction and waits for us to
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're an inbound
// channel which failed to persist the monitor on funding_created, and we got the funding
// transaction confirmed before the monitor was persisted.
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're
// * an inbound channel that failed to persist the monitor on funding_created and we got
// the funding transaction confirmed before the monitor was persisted, or
// * a 0-conf channel and intended to send the funding_locked before any broadcast at all.
let funding_locked = if self.monitor_pending_funding_locked {
assert!(!self.is_outbound(), "Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
assert!(!self.is_outbound() || self.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.monitor_pending_funding_locked = false;
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
Some(msgs::FundingLocked {
Expand DownExpand Up@@ -4551,6 +4567,11 @@ impl<Signer: Sign> Channel<Signer> {
self.channel_state >= ChannelState::FundingSent as u32
}

/// Returns true if our funding_locked has been sent
pub fn is_our_funding_locked(&self) -> bool {
(self.channel_state & ChannelState::OurFundingLocked as u32) != 0 || self.channel_state >= ChannelState::ChannelFunded as u32
}

/// Returns true if our peer has either initiated or agreed to shut down the channel.
pub fn received_shutdown(&self) -> bool {
(self.channel_state & ChannelState::RemoteShutdownSent as u32) != 0
Expand DownExpand Up@@ -4581,7 +4602,7 @@ impl<Signer: Sign> Channel<Signer> {
}

fn check_get_funding_locked(&mut self, height: u32) -> Option<msgs::FundingLocked> {
if self.funding_tx_confirmation_height == 0 {
if self.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) {
return None;
}

Expand DownExpand Up@@ -4636,12 +4657,11 @@ impl<Signer: Sign> Channel<Signer> {
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, logger: &L)
-> Result<(Option<msgs::FundingLocked>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
// whether they've sent a funding_locked or not), check if we should send one.
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
// Check if the transaction is the expected funding transaction, and if it is,
// check that it pays the right amount to the right script.
if self.funding_tx_confirmation_height == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this predated the PR, but it's unclear to me what funding_tx_confirmation_height of 0 means.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, it means "not yet confirmed", as certainly no channel is going to be confirmed at height 0 :) I kinda figured it was an obvious default but I can add docs if you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Meh, maybe an is_funding_confirmed function would be more readable, but no need to do anything here.

if tx.txid() == funding_txo.txid {
let txo_idx = funding_txo.index as usize;
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
Expand DownExpand Up@@ -4758,9 +4778,9 @@ impl<Signer: Sign> Channel<Signer> {
// close the channel and hope we can get the latest state on chain (because presumably
// the funding transaction is at least still in the mempool of most nodes).
//
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf channel,
// but not doing so may lead to the `ChannelManager::short_to_id` map being
// inconsistent, so we currently have to.
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf or
// 0-conf channel, but not doing so may lead to the `ChannelManager::short_to_id` map
// being inconsistent, so we currently have to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apart of more code complexity, I don't understand why we would force-close a 0-conf channel as we already assume it's safe without block inclusion ? And I'm not sure there is a real inconsistency risk with the short_to_id being inserted at funding_locked being a fake SCID ? Though one concern would be the funding inputs unconfirmed themselves.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

No, we should fix that eventually - #867

if funding_tx_confirmations == 0 && self.funding_tx_confirmed_in.is_some() {
let err_reason = format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.",
self.minimum_depth.unwrap(), funding_tx_confirmations);
Expand DownExpand Up@@ -4857,6 +4877,12 @@ impl<Signer: Sign> Channel<Signer> {
self.inbound_awaiting_accept
}

/// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
pub fn set_0conf(&mut self) {
assert!(self.inbound_awaiting_accept);
self.minimum_depth = Some(0);
}

/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
Expand DownExpand Up@@ -5619,7 +5645,7 @@ impl<Signer: Sign> Channel<Signer> {
}

const SERIALIZATION_VERSION: u8 = 2;
const MIN_SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 2;
Comment thread
TheBlueMatt marked this conversation as resolved.

impl_writeable_tlv_based_enum!(InboundHTLCRemovalReason,;
(0, FailRelay),
Expand DownExpand Up@@ -5684,12 +5710,10 @@ impl<Signer: Sign> Writeable for Channel<Signer> {

self.user_id.write(writer)?;

// Write out the old serialization for the config object. This is read by version-1
// deserializers, but we will read the version in the TLV at the end instead.
self.config.forwarding_fee_proportional_millionths.write(writer)?;
self.config.cltv_expiry_delta.write(writer)?;
self.config.announced_channel.write(writer)?;
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
// Version 1 deserializers expected to read parts of the config object here. Version 2
// deserializers (0.0.99) now read config through TLVs, and as we now require them for
// `minimum_depth` we simply write dummy values here.
writer.write_all(&[0; 8])?;

self.channel_id.write(writer)?;
(self.channel_state | ChannelState::PeerDisconnected as u32).write(writer)?;
Expand DownExpand Up@@ -6667,7 +6691,7 @@ mod tests {
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).unwrap();
let (funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();
let (funding_signed_msg, _, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: capacity,
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
Expand Down
96 changes: 60 additions & 36 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,6 +710,11 @@ pub(super) struct Channel<Signer: Sign> {
// Our counterparty can offer us SCID aliases which they will map to this channel when routing
// outbound payments. These can be used in invoice route hints to avoid explicitly revealing
// the channel's funding UTXO.
//
// We also use this when sending our peer a channel_update that isn't to be broadcasted
// publicly - allowing them to re-use their map of SCID -> channel for channel_update ->
// associated channel mapping.
//
// We only bother storing the most recent SCID alias at any time, though our counterparty has
// to store all of them.
latest_inbound_scid_alias: Option<u64>,
Expand DownExpand Up@@ -1307,7 +1312,7 @@ impl<Signer: Sign> Channel<Signer> {
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
holder_htlc_minimum_msat: if config.own_channel_config.our_htlc_minimum_msat == 0 { 1 } else { config.own_channel_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
minimum_depth: Some(config.own_channel_config.minimum_depth),
minimum_depth: Some(cmp::max(config.own_channel_config.minimum_depth, 1)),

counterparty_forwarding_info: None,

Expand DownExpand Up@@ -1987,12 +1992,6 @@ impl<Signer: Sign> Channel<Signer> {
if msg.minimum_depth > peer_limits.max_minimum_depth {
return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth)));
}
if msg.minimum_depth == 0 {
// Note that if this changes we should update the serialization minimum version to
// indicate to older clients that they don't understand some features of the current
// channel.
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}

if let Some(ty) = &msg.channel_type {
if *ty != self.channel_type {
Expand DownExpand Up@@ -2029,7 +2028,12 @@ impl<Signer: Sign> Channel<Signer> {
self.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
self.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat;
self.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs;
self.minimum_depth = Some(msg.minimum_depth);

if peer_limits.trust_own_funding_0conf {
self.minimum_depth = Some(msg.minimum_depth);
} else {
self.minimum_depth = Some(cmp::max(1, msg.minimum_depth));
Comment thread
valentinewallace marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would previously return an error when min_depth == 0, whereas now we'll enforce a minimum of one, so they may think we've agreed to proceed with zero confs once we send funding_created back.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Right, this is kinda a 0conf philosophy question. This PR doesn't actually support the 0conf channel type (as it was written before the 0conf channel type existed) which is the "I require 0conf" option, but we'll add that in a followup.

In general, if you're doing a 0conf channel, you've proably pre-negotiated it in some way - most likely the recipient of the channel is pre-configured to trust an LSP. There's arguably not a lot of value in negotiating 0conf in the open/accept channel flow - by the time you're there both sides of the channel should really already know if the channel should be 0conf or not.

More generally, nodes can always delay channel_ready/funding_locked as long as they want - the minimum_depth field is just a hint, and the protocol can't enforce it in any way. We're not strictly-speaking "out of spec" by delaying more than the channel recipient wanted.

}

let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.funding_pubkey,
Expand DownExpand Up@@ -2089,7 +2093,7 @@ impl<Signer: Sign> Channel<Signer> {
&self.get_counterparty_pubkeys().funding_pubkey
}

pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>), ChannelError> where L::Target: Logger {
pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if self.is_outbound() {
return Err(ChannelError::Close("Received funding_created for an outbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2164,12 +2168,12 @@ impl<Signer: Sign> Channel<Signer> {
Ok((msgs::FundingSigned {
channel_id: self.channel_id,
signature
}, channel_monitor))
}, channel_monitor, self.check_get_funding_locked(0)))
}

/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction), ChannelError> where L::Target: Logger {
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if !self.is_outbound() {
return Err(ChannelError::Close("Received funding_signed for an inbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2238,7 +2242,7 @@ impl<Signer: Sign> Channel<Signer> {

log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.channel_id()));

Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap()))
Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap(), self.check_get_funding_locked(0)))
}

/// Handles a funding_locked message from our peer. If we've already sent our funding_locked
Expand DownExpand Up@@ -3540,12 +3544,13 @@ impl<Signer: Sign> Channel<Signer> {
/// monitor update failure must *not* have been sent to the remote end, and must instead
/// have been dropped. They will be regenerated when monitor_updating_restored is called.
pub fn monitor_update_failed(&mut self, resend_raa: bool, resend_commitment: bool,
mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
resend_funding_locked: bool, mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
mut pending_fails: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>,
mut pending_finalized_claimed_htlcs: Vec<HTLCSource>
) {
self.monitor_pending_revoke_and_ack |= resend_raa;
self.monitor_pending_commitment_signed |= resend_commitment;
self.monitor_pending_funding_locked |= resend_funding_locked;
self.monitor_pending_forwards.append(&mut pending_forwards);
self.monitor_pending_failures.append(&mut pending_fails);
self.monitor_pending_finalized_fulfills.append(&mut pending_finalized_claimed_htlcs);
Expand All@@ -3559,17 +3564,28 @@ impl<Signer: Sign> Channel<Signer> {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateFailed as u32, ChannelState::MonitorUpdateFailed as u32);
self.channel_state &= !(ChannelState::MonitorUpdateFailed as u32);

let funding_broadcastable = if self.channel_state & (ChannelState::FundingSent as u32) != 0 && self.is_outbound() {
self.funding_transaction.take()
} else { None };
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
if self.is_outbound() && self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change almost seems like it belongs in a separate commit or be documented in the commit message, seemed a bit unrelated at first

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Updated the commit message.

self.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
if self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelFunded as u32 && self.minimum_depth != Some(0) {
funding_broadcastable = None;
}

// We will never broadcast the funding transaction when we're in MonitorUpdateFailed (and
// we assume the user never directly broadcasts the funding transaction and waits for us to
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're an inbound
// channel which failed to persist the monitor on funding_created, and we got the funding
// transaction confirmed before the monitor was persisted.
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're
// * an inbound channel that failed to persist the monitor on funding_created and we got
// the funding transaction confirmed before the monitor was persisted, or
// * a 0-conf channel and intended to send the funding_locked before any broadcast at all.
let funding_locked = if self.monitor_pending_funding_locked {
assert!(!self.is_outbound(), "Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
assert!(!self.is_outbound() || self.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.monitor_pending_funding_locked = false;
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
Some(msgs::FundingLocked {
Expand DownExpand Up@@ -4551,6 +4567,11 @@ impl<Signer: Sign> Channel<Signer> {
self.channel_state >= ChannelState::FundingSent as u32
}

/// Returns true if our funding_locked has been sent
pub fn is_our_funding_locked(&self) -> bool {
(self.channel_state & ChannelState::OurFundingLocked as u32) != 0 || self.channel_state >= ChannelState::ChannelFunded as u32
}

/// Returns true if our peer has either initiated or agreed to shut down the channel.
pub fn received_shutdown(&self) -> bool {
(self.channel_state & ChannelState::RemoteShutdownSent as u32) != 0
Expand DownExpand Up@@ -4581,7 +4602,7 @@ impl<Signer: Sign> Channel<Signer> {
}

fn check_get_funding_locked(&mut self, height: u32) -> Option<msgs::FundingLocked> {
if self.funding_tx_confirmation_height == 0 {
if self.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) {
return None;
}

Expand DownExpand Up@@ -4636,12 +4657,11 @@ impl<Signer: Sign> Channel<Signer> {
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, logger: &L)
-> Result<(Option<msgs::FundingLocked>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
// whether they've sent a funding_locked or not), check if we should send one.
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
// Check if the transaction is the expected funding transaction, and if it is,
// check that it pays the right amount to the right script.
if self.funding_tx_confirmation_height == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this predated the PR, but it's unclear to me what funding_tx_confirmation_height of 0 means.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, it means "not yet confirmed", as certainly no channel is going to be confirmed at height 0 :) I kinda figured it was an obvious default but I can add docs if you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Meh, maybe an is_funding_confirmed function would be more readable, but no need to do anything here.

if tx.txid() == funding_txo.txid {
let txo_idx = funding_txo.index as usize;
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
Expand DownExpand Up@@ -4758,9 +4778,9 @@ impl<Signer: Sign> Channel<Signer> {
// close the channel and hope we can get the latest state on chain (because presumably
// the funding transaction is at least still in the mempool of most nodes).
//
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf channel,
// but not doing so may lead to the `ChannelManager::short_to_id` map being
// inconsistent, so we currently have to.
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf or
// 0-conf channel, but not doing so may lead to the `ChannelManager::short_to_id` map
// being inconsistent, so we currently have to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apart of more code complexity, I don't understand why we would force-close a 0-conf channel as we already assume it's safe without block inclusion ? And I'm not sure there is a real inconsistency risk with the short_to_id being inserted at funding_locked being a fake SCID ? Though one concern would be the funding inputs unconfirmed themselves.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

No, we should fix that eventually - #867

if funding_tx_confirmations == 0 && self.funding_tx_confirmed_in.is_some() {
let err_reason = format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.",
self.minimum_depth.unwrap(), funding_tx_confirmations);
Expand DownExpand Up@@ -4857,6 +4877,12 @@ impl<Signer: Sign> Channel<Signer> {
self.inbound_awaiting_accept
}

/// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
pub fn set_0conf(&mut self) {
assert!(self.inbound_awaiting_accept);
self.minimum_depth = Some(0);
}

/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
Expand DownExpand Up@@ -5619,7 +5645,7 @@ impl<Signer: Sign> Channel<Signer> {
}

const SERIALIZATION_VERSION: u8 = 2;
const MIN_SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 2;
Comment thread
TheBlueMatt marked this conversation as resolved.

impl_writeable_tlv_based_enum!(InboundHTLCRemovalReason,;
(0, FailRelay),
Expand DownExpand Up@@ -5684,12 +5710,10 @@ impl<Signer: Sign> Writeable for Channel<Signer> {

self.user_id.write(writer)?;

// Write out the old serialization for the config object. This is read by version-1
// deserializers, but we will read the version in the TLV at the end instead.
self.config.forwarding_fee_proportional_millionths.write(writer)?;
self.config.cltv_expiry_delta.write(writer)?;
self.config.announced_channel.write(writer)?;
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
// Version 1 deserializers expected to read parts of the config object here. Version 2
// deserializers (0.0.99) now read config through TLVs, and as we now require them for
// `minimum_depth` we simply write dummy values here.
writer.write_all(&[0; 8])?;

self.channel_id.write(writer)?;
(self.channel_state | ChannelState::PeerDisconnected as u32).write(writer)?;
Expand DownExpand Up@@ -6667,7 +6691,7 @@ mod tests {
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).unwrap();
let (funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();
let (funding_signed_msg, _, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: capacity,
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
Expand Down
96 changes: 60 additions & 36 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,6 +710,11 @@ pub(super) struct Channel<Signer: Sign> {
// Our counterparty can offer us SCID aliases which they will map to this channel when routing
// outbound payments. These can be used in invoice route hints to avoid explicitly revealing
// the channel's funding UTXO.
//
// We also use this when sending our peer a channel_update that isn't to be broadcasted
// publicly - allowing them to re-use their map of SCID -> channel for channel_update ->
// associated channel mapping.
//
// We only bother storing the most recent SCID alias at any time, though our counterparty has
// to store all of them.
latest_inbound_scid_alias: Option<u64>,
Expand DownExpand Up@@ -1307,7 +1312,7 @@ impl<Signer: Sign> Channel<Signer> {
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
holder_htlc_minimum_msat: if config.own_channel_config.our_htlc_minimum_msat == 0 { 1 } else { config.own_channel_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
minimum_depth: Some(config.own_channel_config.minimum_depth),
minimum_depth: Some(cmp::max(config.own_channel_config.minimum_depth, 1)),

counterparty_forwarding_info: None,

Expand DownExpand Up@@ -1987,12 +1992,6 @@ impl<Signer: Sign> Channel<Signer> {
if msg.minimum_depth > peer_limits.max_minimum_depth {
return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth)));
}
if msg.minimum_depth == 0 {
// Note that if this changes we should update the serialization minimum version to
// indicate to older clients that they don't understand some features of the current
// channel.
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}

if let Some(ty) = &msg.channel_type {
if *ty != self.channel_type {
Expand DownExpand Up@@ -2029,7 +2028,12 @@ impl<Signer: Sign> Channel<Signer> {
self.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
self.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat;
self.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs;
self.minimum_depth = Some(msg.minimum_depth);

if peer_limits.trust_own_funding_0conf {
self.minimum_depth = Some(msg.minimum_depth);
} else {
self.minimum_depth = Some(cmp::max(1, msg.minimum_depth));
Comment thread
valentinewallace marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would previously return an error when min_depth == 0, whereas now we'll enforce a minimum of one, so they may think we've agreed to proceed with zero confs once we send funding_created back.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Right, this is kinda a 0conf philosophy question. This PR doesn't actually support the 0conf channel type (as it was written before the 0conf channel type existed) which is the "I require 0conf" option, but we'll add that in a followup.

In general, if you're doing a 0conf channel, you've proably pre-negotiated it in some way - most likely the recipient of the channel is pre-configured to trust an LSP. There's arguably not a lot of value in negotiating 0conf in the open/accept channel flow - by the time you're there both sides of the channel should really already know if the channel should be 0conf or not.

More generally, nodes can always delay channel_ready/funding_locked as long as they want - the minimum_depth field is just a hint, and the protocol can't enforce it in any way. We're not strictly-speaking "out of spec" by delaying more than the channel recipient wanted.

}

let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.funding_pubkey,
Expand DownExpand Up@@ -2089,7 +2093,7 @@ impl<Signer: Sign> Channel<Signer> {
&self.get_counterparty_pubkeys().funding_pubkey
}

pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>), ChannelError> where L::Target: Logger {
pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if self.is_outbound() {
return Err(ChannelError::Close("Received funding_created for an outbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2164,12 +2168,12 @@ impl<Signer: Sign> Channel<Signer> {
Ok((msgs::FundingSigned {
channel_id: self.channel_id,
signature
}, channel_monitor))
}, channel_monitor, self.check_get_funding_locked(0)))
}

/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction), ChannelError> where L::Target: Logger {
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if !self.is_outbound() {
return Err(ChannelError::Close("Received funding_signed for an inbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2238,7 +2242,7 @@ impl<Signer: Sign> Channel<Signer> {

log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.channel_id()));

Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap()))
Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap(), self.check_get_funding_locked(0)))
}

/// Handles a funding_locked message from our peer. If we've already sent our funding_locked
Expand DownExpand Up@@ -3540,12 +3544,13 @@ impl<Signer: Sign> Channel<Signer> {
/// monitor update failure must *not* have been sent to the remote end, and must instead
/// have been dropped. They will be regenerated when monitor_updating_restored is called.
pub fn monitor_update_failed(&mut self, resend_raa: bool, resend_commitment: bool,
mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
resend_funding_locked: bool, mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
mut pending_fails: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>,
mut pending_finalized_claimed_htlcs: Vec<HTLCSource>
) {
self.monitor_pending_revoke_and_ack |= resend_raa;
self.monitor_pending_commitment_signed |= resend_commitment;
self.monitor_pending_funding_locked |= resend_funding_locked;
self.monitor_pending_forwards.append(&mut pending_forwards);
self.monitor_pending_failures.append(&mut pending_fails);
self.monitor_pending_finalized_fulfills.append(&mut pending_finalized_claimed_htlcs);
Expand All@@ -3559,17 +3564,28 @@ impl<Signer: Sign> Channel<Signer> {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateFailed as u32, ChannelState::MonitorUpdateFailed as u32);
self.channel_state &= !(ChannelState::MonitorUpdateFailed as u32);

let funding_broadcastable = if self.channel_state & (ChannelState::FundingSent as u32) != 0 && self.is_outbound() {
self.funding_transaction.take()
} else { None };
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
if self.is_outbound() && self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change almost seems like it belongs in a separate commit or be documented in the commit message, seemed a bit unrelated at first

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Updated the commit message.

self.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
if self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelFunded as u32 && self.minimum_depth != Some(0) {
funding_broadcastable = None;
}

// We will never broadcast the funding transaction when we're in MonitorUpdateFailed (and
// we assume the user never directly broadcasts the funding transaction and waits for us to
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're an inbound
// channel which failed to persist the monitor on funding_created, and we got the funding
// transaction confirmed before the monitor was persisted.
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're
// * an inbound channel that failed to persist the monitor on funding_created and we got
// the funding transaction confirmed before the monitor was persisted, or
// * a 0-conf channel and intended to send the funding_locked before any broadcast at all.
let funding_locked = if self.monitor_pending_funding_locked {
assert!(!self.is_outbound(), "Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
assert!(!self.is_outbound() || self.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.monitor_pending_funding_locked = false;
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
Some(msgs::FundingLocked {
Expand DownExpand Up@@ -4551,6 +4567,11 @@ impl<Signer: Sign> Channel<Signer> {
self.channel_state >= ChannelState::FundingSent as u32
}

/// Returns true if our funding_locked has been sent
pub fn is_our_funding_locked(&self) -> bool {
(self.channel_state & ChannelState::OurFundingLocked as u32) != 0 || self.channel_state >= ChannelState::ChannelFunded as u32
}

/// Returns true if our peer has either initiated or agreed to shut down the channel.
pub fn received_shutdown(&self) -> bool {
(self.channel_state & ChannelState::RemoteShutdownSent as u32) != 0
Expand DownExpand Up@@ -4581,7 +4602,7 @@ impl<Signer: Sign> Channel<Signer> {
}

fn check_get_funding_locked(&mut self, height: u32) -> Option<msgs::FundingLocked> {
if self.funding_tx_confirmation_height == 0 {
if self.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) {
return None;
}

Expand DownExpand Up@@ -4636,12 +4657,11 @@ impl<Signer: Sign> Channel<Signer> {
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, logger: &L)
-> Result<(Option<msgs::FundingLocked>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
// whether they've sent a funding_locked or not), check if we should send one.
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
// Check if the transaction is the expected funding transaction, and if it is,
// check that it pays the right amount to the right script.
if self.funding_tx_confirmation_height == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this predated the PR, but it's unclear to me what funding_tx_confirmation_height of 0 means.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, it means "not yet confirmed", as certainly no channel is going to be confirmed at height 0 :) I kinda figured it was an obvious default but I can add docs if you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Meh, maybe an is_funding_confirmed function would be more readable, but no need to do anything here.

if tx.txid() == funding_txo.txid {
let txo_idx = funding_txo.index as usize;
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
Expand DownExpand Up@@ -4758,9 +4778,9 @@ impl<Signer: Sign> Channel<Signer> {
// close the channel and hope we can get the latest state on chain (because presumably
// the funding transaction is at least still in the mempool of most nodes).
//
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf channel,
// but not doing so may lead to the `ChannelManager::short_to_id` map being
// inconsistent, so we currently have to.
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf or
// 0-conf channel, but not doing so may lead to the `ChannelManager::short_to_id` map
// being inconsistent, so we currently have to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apart of more code complexity, I don't understand why we would force-close a 0-conf channel as we already assume it's safe without block inclusion ? And I'm not sure there is a real inconsistency risk with the short_to_id being inserted at funding_locked being a fake SCID ? Though one concern would be the funding inputs unconfirmed themselves.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

No, we should fix that eventually - #867

if funding_tx_confirmations == 0 && self.funding_tx_confirmed_in.is_some() {
let err_reason = format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.",
self.minimum_depth.unwrap(), funding_tx_confirmations);
Expand DownExpand Up@@ -4857,6 +4877,12 @@ impl<Signer: Sign> Channel<Signer> {
self.inbound_awaiting_accept
}

/// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
pub fn set_0conf(&mut self) {
assert!(self.inbound_awaiting_accept);
self.minimum_depth = Some(0);
}

/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
Expand DownExpand Up@@ -5619,7 +5645,7 @@ impl<Signer: Sign> Channel<Signer> {
}

const SERIALIZATION_VERSION: u8 = 2;
const MIN_SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 2;
Comment thread
TheBlueMatt marked this conversation as resolved.

impl_writeable_tlv_based_enum!(InboundHTLCRemovalReason,;
(0, FailRelay),
Expand DownExpand Up@@ -5684,12 +5710,10 @@ impl<Signer: Sign> Writeable for Channel<Signer> {

self.user_id.write(writer)?;

// Write out the old serialization for the config object. This is read by version-1
// deserializers, but we will read the version in the TLV at the end instead.
self.config.forwarding_fee_proportional_millionths.write(writer)?;
self.config.cltv_expiry_delta.write(writer)?;
self.config.announced_channel.write(writer)?;
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
// Version 1 deserializers expected to read parts of the config object here. Version 2
// deserializers (0.0.99) now read config through TLVs, and as we now require them for
// `minimum_depth` we simply write dummy values here.
writer.write_all(&[0; 8])?;

self.channel_id.write(writer)?;
(self.channel_state | ChannelState::PeerDisconnected as u32).write(writer)?;
Expand DownExpand Up@@ -6667,7 +6691,7 @@ mod tests {
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).unwrap();
let (funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();
let (funding_signed_msg, _, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: capacity,
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
Expand Down
96 changes: 60 additions & 36 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,6 +710,11 @@ pub(super) struct Channel<Signer: Sign> {
// Our counterparty can offer us SCID aliases which they will map to this channel when routing
// outbound payments. These can be used in invoice route hints to avoid explicitly revealing
// the channel's funding UTXO.
//
// We also use this when sending our peer a channel_update that isn't to be broadcasted
// publicly - allowing them to re-use their map of SCID -> channel for channel_update ->
// associated channel mapping.
//
// We only bother storing the most recent SCID alias at any time, though our counterparty has
// to store all of them.
latest_inbound_scid_alias: Option<u64>,
Expand DownExpand Up@@ -1307,7 +1312,7 @@ impl<Signer: Sign> Channel<Signer> {
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
holder_htlc_minimum_msat: if config.own_channel_config.our_htlc_minimum_msat == 0 { 1 } else { config.own_channel_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
minimum_depth: Some(config.own_channel_config.minimum_depth),
minimum_depth: Some(cmp::max(config.own_channel_config.minimum_depth, 1)),

counterparty_forwarding_info: None,

Expand DownExpand Up@@ -1987,12 +1992,6 @@ impl<Signer: Sign> Channel<Signer> {
if msg.minimum_depth > peer_limits.max_minimum_depth {
return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth)));
}
if msg.minimum_depth == 0 {
// Note that if this changes we should update the serialization minimum version to
// indicate to older clients that they don't understand some features of the current
// channel.
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}

if let Some(ty) = &msg.channel_type {
if *ty != self.channel_type {
Expand DownExpand Up@@ -2029,7 +2028,12 @@ impl<Signer: Sign> Channel<Signer> {
self.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
self.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat;
self.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs;
self.minimum_depth = Some(msg.minimum_depth);

if peer_limits.trust_own_funding_0conf {
self.minimum_depth = Some(msg.minimum_depth);
} else {
self.minimum_depth = Some(cmp::max(1, msg.minimum_depth));
Comment thread
valentinewallace marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would previously return an error when min_depth == 0, whereas now we'll enforce a minimum of one, so they may think we've agreed to proceed with zero confs once we send funding_created back.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Right, this is kinda a 0conf philosophy question. This PR doesn't actually support the 0conf channel type (as it was written before the 0conf channel type existed) which is the "I require 0conf" option, but we'll add that in a followup.

In general, if you're doing a 0conf channel, you've proably pre-negotiated it in some way - most likely the recipient of the channel is pre-configured to trust an LSP. There's arguably not a lot of value in negotiating 0conf in the open/accept channel flow - by the time you're there both sides of the channel should really already know if the channel should be 0conf or not.

More generally, nodes can always delay channel_ready/funding_locked as long as they want - the minimum_depth field is just a hint, and the protocol can't enforce it in any way. We're not strictly-speaking "out of spec" by delaying more than the channel recipient wanted.

}

let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.funding_pubkey,
Expand DownExpand Up@@ -2089,7 +2093,7 @@ impl<Signer: Sign> Channel<Signer> {
&self.get_counterparty_pubkeys().funding_pubkey
}

pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>), ChannelError> where L::Target: Logger {
pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if self.is_outbound() {
return Err(ChannelError::Close("Received funding_created for an outbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2164,12 +2168,12 @@ impl<Signer: Sign> Channel<Signer> {
Ok((msgs::FundingSigned {
channel_id: self.channel_id,
signature
}, channel_monitor))
}, channel_monitor, self.check_get_funding_locked(0)))
}

/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction), ChannelError> where L::Target: Logger {
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if !self.is_outbound() {
return Err(ChannelError::Close("Received funding_signed for an inbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2238,7 +2242,7 @@ impl<Signer: Sign> Channel<Signer> {

log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.channel_id()));

Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap()))
Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap(), self.check_get_funding_locked(0)))
}

/// Handles a funding_locked message from our peer. If we've already sent our funding_locked
Expand DownExpand Up@@ -3540,12 +3544,13 @@ impl<Signer: Sign> Channel<Signer> {
/// monitor update failure must *not* have been sent to the remote end, and must instead
/// have been dropped. They will be regenerated when monitor_updating_restored is called.
pub fn monitor_update_failed(&mut self, resend_raa: bool, resend_commitment: bool,
mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
resend_funding_locked: bool, mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
mut pending_fails: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>,
mut pending_finalized_claimed_htlcs: Vec<HTLCSource>
) {
self.monitor_pending_revoke_and_ack |= resend_raa;
self.monitor_pending_commitment_signed |= resend_commitment;
self.monitor_pending_funding_locked |= resend_funding_locked;
self.monitor_pending_forwards.append(&mut pending_forwards);
self.monitor_pending_failures.append(&mut pending_fails);
self.monitor_pending_finalized_fulfills.append(&mut pending_finalized_claimed_htlcs);
Expand All@@ -3559,17 +3564,28 @@ impl<Signer: Sign> Channel<Signer> {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateFailed as u32, ChannelState::MonitorUpdateFailed as u32);
self.channel_state &= !(ChannelState::MonitorUpdateFailed as u32);

let funding_broadcastable = if self.channel_state & (ChannelState::FundingSent as u32) != 0 && self.is_outbound() {
self.funding_transaction.take()
} else { None };
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
if self.is_outbound() && self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change almost seems like it belongs in a separate commit or be documented in the commit message, seemed a bit unrelated at first

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Updated the commit message.

self.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
if self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelFunded as u32 && self.minimum_depth != Some(0) {
funding_broadcastable = None;
}

// We will never broadcast the funding transaction when we're in MonitorUpdateFailed (and
// we assume the user never directly broadcasts the funding transaction and waits for us to
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're an inbound
// channel which failed to persist the monitor on funding_created, and we got the funding
// transaction confirmed before the monitor was persisted.
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're
// * an inbound channel that failed to persist the monitor on funding_created and we got
// the funding transaction confirmed before the monitor was persisted, or
// * a 0-conf channel and intended to send the funding_locked before any broadcast at all.
let funding_locked = if self.monitor_pending_funding_locked {
assert!(!self.is_outbound(), "Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
assert!(!self.is_outbound() || self.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.monitor_pending_funding_locked = false;
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
Some(msgs::FundingLocked {
Expand DownExpand Up@@ -4551,6 +4567,11 @@ impl<Signer: Sign> Channel<Signer> {
self.channel_state >= ChannelState::FundingSent as u32
}

/// Returns true if our funding_locked has been sent
pub fn is_our_funding_locked(&self) -> bool {
(self.channel_state & ChannelState::OurFundingLocked as u32) != 0 || self.channel_state >= ChannelState::ChannelFunded as u32
}

/// Returns true if our peer has either initiated or agreed to shut down the channel.
pub fn received_shutdown(&self) -> bool {
(self.channel_state & ChannelState::RemoteShutdownSent as u32) != 0
Expand DownExpand Up@@ -4581,7 +4602,7 @@ impl<Signer: Sign> Channel<Signer> {
}

fn check_get_funding_locked(&mut self, height: u32) -> Option<msgs::FundingLocked> {
if self.funding_tx_confirmation_height == 0 {
if self.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) {
return None;
}

Expand DownExpand Up@@ -4636,12 +4657,11 @@ impl<Signer: Sign> Channel<Signer> {
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, logger: &L)
-> Result<(Option<msgs::FundingLocked>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
// whether they've sent a funding_locked or not), check if we should send one.
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
// Check if the transaction is the expected funding transaction, and if it is,
// check that it pays the right amount to the right script.
if self.funding_tx_confirmation_height == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this predated the PR, but it's unclear to me what funding_tx_confirmation_height of 0 means.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, it means "not yet confirmed", as certainly no channel is going to be confirmed at height 0 :) I kinda figured it was an obvious default but I can add docs if you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Meh, maybe an is_funding_confirmed function would be more readable, but no need to do anything here.

if tx.txid() == funding_txo.txid {
let txo_idx = funding_txo.index as usize;
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
Expand DownExpand Up@@ -4758,9 +4778,9 @@ impl<Signer: Sign> Channel<Signer> {
// close the channel and hope we can get the latest state on chain (because presumably
// the funding transaction is at least still in the mempool of most nodes).
//
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf channel,
// but not doing so may lead to the `ChannelManager::short_to_id` map being
// inconsistent, so we currently have to.
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf or
// 0-conf channel, but not doing so may lead to the `ChannelManager::short_to_id` map
// being inconsistent, so we currently have to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apart of more code complexity, I don't understand why we would force-close a 0-conf channel as we already assume it's safe without block inclusion ? And I'm not sure there is a real inconsistency risk with the short_to_id being inserted at funding_locked being a fake SCID ? Though one concern would be the funding inputs unconfirmed themselves.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

No, we should fix that eventually - #867

if funding_tx_confirmations == 0 && self.funding_tx_confirmed_in.is_some() {
let err_reason = format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.",
self.minimum_depth.unwrap(), funding_tx_confirmations);
Expand DownExpand Up@@ -4857,6 +4877,12 @@ impl<Signer: Sign> Channel<Signer> {
self.inbound_awaiting_accept
}

/// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
pub fn set_0conf(&mut self) {
assert!(self.inbound_awaiting_accept);
self.minimum_depth = Some(0);
}

/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
Expand DownExpand Up@@ -5619,7 +5645,7 @@ impl<Signer: Sign> Channel<Signer> {
}

const SERIALIZATION_VERSION: u8 = 2;
const MIN_SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 2;
Comment thread
TheBlueMatt marked this conversation as resolved.

impl_writeable_tlv_based_enum!(InboundHTLCRemovalReason,;
(0, FailRelay),
Expand DownExpand Up@@ -5684,12 +5710,10 @@ impl<Signer: Sign> Writeable for Channel<Signer> {

self.user_id.write(writer)?;

// Write out the old serialization for the config object. This is read by version-1
// deserializers, but we will read the version in the TLV at the end instead.
self.config.forwarding_fee_proportional_millionths.write(writer)?;
self.config.cltv_expiry_delta.write(writer)?;
self.config.announced_channel.write(writer)?;
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
// Version 1 deserializers expected to read parts of the config object here. Version 2
// deserializers (0.0.99) now read config through TLVs, and as we now require them for
// `minimum_depth` we simply write dummy values here.
writer.write_all(&[0; 8])?;

self.channel_id.write(writer)?;
(self.channel_state | ChannelState::PeerDisconnected as u32).write(writer)?;
Expand DownExpand Up@@ -6667,7 +6691,7 @@ mod tests {
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).unwrap();
let (funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();
let (funding_signed_msg, _, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: capacity,
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
Expand Down
96 changes: 60 additions & 36 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,6 +710,11 @@ pub(super) struct Channel<Signer: Sign> {
// Our counterparty can offer us SCID aliases which they will map to this channel when routing
// outbound payments. These can be used in invoice route hints to avoid explicitly revealing
// the channel's funding UTXO.
//
// We also use this when sending our peer a channel_update that isn't to be broadcasted
// publicly - allowing them to re-use their map of SCID -> channel for channel_update ->
// associated channel mapping.
//
// We only bother storing the most recent SCID alias at any time, though our counterparty has
// to store all of them.
latest_inbound_scid_alias: Option<u64>,
Expand DownExpand Up@@ -1307,7 +1312,7 @@ impl<Signer: Sign> Channel<Signer> {
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
holder_htlc_minimum_msat: if config.own_channel_config.our_htlc_minimum_msat == 0 { 1 } else { config.own_channel_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
minimum_depth: Some(config.own_channel_config.minimum_depth),
minimum_depth: Some(cmp::max(config.own_channel_config.minimum_depth, 1)),

counterparty_forwarding_info: None,

Expand DownExpand Up@@ -1987,12 +1992,6 @@ impl<Signer: Sign> Channel<Signer> {
if msg.minimum_depth > peer_limits.max_minimum_depth {
return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth)));
}
if msg.minimum_depth == 0 {
// Note that if this changes we should update the serialization minimum version to
// indicate to older clients that they don't understand some features of the current
// channel.
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}

if let Some(ty) = &msg.channel_type {
if *ty != self.channel_type {
Expand DownExpand Up@@ -2029,7 +2028,12 @@ impl<Signer: Sign> Channel<Signer> {
self.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
self.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat;
self.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs;
self.minimum_depth = Some(msg.minimum_depth);

if peer_limits.trust_own_funding_0conf {
self.minimum_depth = Some(msg.minimum_depth);
} else {
self.minimum_depth = Some(cmp::max(1, msg.minimum_depth));
Comment thread
valentinewallace marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would previously return an error when min_depth == 0, whereas now we'll enforce a minimum of one, so they may think we've agreed to proceed with zero confs once we send funding_created back.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Right, this is kinda a 0conf philosophy question. This PR doesn't actually support the 0conf channel type (as it was written before the 0conf channel type existed) which is the "I require 0conf" option, but we'll add that in a followup.

In general, if you're doing a 0conf channel, you've proably pre-negotiated it in some way - most likely the recipient of the channel is pre-configured to trust an LSP. There's arguably not a lot of value in negotiating 0conf in the open/accept channel flow - by the time you're there both sides of the channel should really already know if the channel should be 0conf or not.

More generally, nodes can always delay channel_ready/funding_locked as long as they want - the minimum_depth field is just a hint, and the protocol can't enforce it in any way. We're not strictly-speaking "out of spec" by delaying more than the channel recipient wanted.

}

let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.funding_pubkey,
Expand DownExpand Up@@ -2089,7 +2093,7 @@ impl<Signer: Sign> Channel<Signer> {
&self.get_counterparty_pubkeys().funding_pubkey
}

pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>), ChannelError> where L::Target: Logger {
pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if self.is_outbound() {
return Err(ChannelError::Close("Received funding_created for an outbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2164,12 +2168,12 @@ impl<Signer: Sign> Channel<Signer> {
Ok((msgs::FundingSigned {
channel_id: self.channel_id,
signature
}, channel_monitor))
}, channel_monitor, self.check_get_funding_locked(0)))
}

/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction), ChannelError> where L::Target: Logger {
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if !self.is_outbound() {
return Err(ChannelError::Close("Received funding_signed for an inbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2238,7 +2242,7 @@ impl<Signer: Sign> Channel<Signer> {

log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.channel_id()));

Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap()))
Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap(), self.check_get_funding_locked(0)))
}

/// Handles a funding_locked message from our peer. If we've already sent our funding_locked
Expand DownExpand Up@@ -3540,12 +3544,13 @@ impl<Signer: Sign> Channel<Signer> {
/// monitor update failure must *not* have been sent to the remote end, and must instead
/// have been dropped. They will be regenerated when monitor_updating_restored is called.
pub fn monitor_update_failed(&mut self, resend_raa: bool, resend_commitment: bool,
mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
resend_funding_locked: bool, mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
mut pending_fails: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>,
mut pending_finalized_claimed_htlcs: Vec<HTLCSource>
) {
self.monitor_pending_revoke_and_ack |= resend_raa;
self.monitor_pending_commitment_signed |= resend_commitment;
self.monitor_pending_funding_locked |= resend_funding_locked;
self.monitor_pending_forwards.append(&mut pending_forwards);
self.monitor_pending_failures.append(&mut pending_fails);
self.monitor_pending_finalized_fulfills.append(&mut pending_finalized_claimed_htlcs);
Expand All@@ -3559,17 +3564,28 @@ impl<Signer: Sign> Channel<Signer> {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateFailed as u32, ChannelState::MonitorUpdateFailed as u32);
self.channel_state &= !(ChannelState::MonitorUpdateFailed as u32);

let funding_broadcastable = if self.channel_state & (ChannelState::FundingSent as u32) != 0 && self.is_outbound() {
self.funding_transaction.take()
} else { None };
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
if self.is_outbound() && self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change almost seems like it belongs in a separate commit or be documented in the commit message, seemed a bit unrelated at first

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Updated the commit message.

self.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
if self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelFunded as u32 && self.minimum_depth != Some(0) {
funding_broadcastable = None;
}

// We will never broadcast the funding transaction when we're in MonitorUpdateFailed (and
// we assume the user never directly broadcasts the funding transaction and waits for us to
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're an inbound
// channel which failed to persist the monitor on funding_created, and we got the funding
// transaction confirmed before the monitor was persisted.
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're
// * an inbound channel that failed to persist the monitor on funding_created and we got
// the funding transaction confirmed before the monitor was persisted, or
// * a 0-conf channel and intended to send the funding_locked before any broadcast at all.
let funding_locked = if self.monitor_pending_funding_locked {
assert!(!self.is_outbound(), "Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
assert!(!self.is_outbound() || self.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.monitor_pending_funding_locked = false;
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
Some(msgs::FundingLocked {
Expand DownExpand Up@@ -4551,6 +4567,11 @@ impl<Signer: Sign> Channel<Signer> {
self.channel_state >= ChannelState::FundingSent as u32
}

/// Returns true if our funding_locked has been sent
pub fn is_our_funding_locked(&self) -> bool {
(self.channel_state & ChannelState::OurFundingLocked as u32) != 0 || self.channel_state >= ChannelState::ChannelFunded as u32
}

/// Returns true if our peer has either initiated or agreed to shut down the channel.
pub fn received_shutdown(&self) -> bool {
(self.channel_state & ChannelState::RemoteShutdownSent as u32) != 0
Expand DownExpand Up@@ -4581,7 +4602,7 @@ impl<Signer: Sign> Channel<Signer> {
}

fn check_get_funding_locked(&mut self, height: u32) -> Option<msgs::FundingLocked> {
if self.funding_tx_confirmation_height == 0 {
if self.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) {
return None;
}

Expand DownExpand Up@@ -4636,12 +4657,11 @@ impl<Signer: Sign> Channel<Signer> {
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, logger: &L)
-> Result<(Option<msgs::FundingLocked>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
// whether they've sent a funding_locked or not), check if we should send one.
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
// Check if the transaction is the expected funding transaction, and if it is,
// check that it pays the right amount to the right script.
if self.funding_tx_confirmation_height == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this predated the PR, but it's unclear to me what funding_tx_confirmation_height of 0 means.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, it means "not yet confirmed", as certainly no channel is going to be confirmed at height 0 :) I kinda figured it was an obvious default but I can add docs if you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Meh, maybe an is_funding_confirmed function would be more readable, but no need to do anything here.

if tx.txid() == funding_txo.txid {
let txo_idx = funding_txo.index as usize;
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
Expand DownExpand Up@@ -4758,9 +4778,9 @@ impl<Signer: Sign> Channel<Signer> {
// close the channel and hope we can get the latest state on chain (because presumably
// the funding transaction is at least still in the mempool of most nodes).
//
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf channel,
// but not doing so may lead to the `ChannelManager::short_to_id` map being
// inconsistent, so we currently have to.
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf or
// 0-conf channel, but not doing so may lead to the `ChannelManager::short_to_id` map
// being inconsistent, so we currently have to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apart of more code complexity, I don't understand why we would force-close a 0-conf channel as we already assume it's safe without block inclusion ? And I'm not sure there is a real inconsistency risk with the short_to_id being inserted at funding_locked being a fake SCID ? Though one concern would be the funding inputs unconfirmed themselves.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

No, we should fix that eventually - #867

if funding_tx_confirmations == 0 && self.funding_tx_confirmed_in.is_some() {
let err_reason = format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.",
self.minimum_depth.unwrap(), funding_tx_confirmations);
Expand DownExpand Up@@ -4857,6 +4877,12 @@ impl<Signer: Sign> Channel<Signer> {
self.inbound_awaiting_accept
}

/// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
pub fn set_0conf(&mut self) {
assert!(self.inbound_awaiting_accept);
self.minimum_depth = Some(0);
}

/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
Expand DownExpand Up@@ -5619,7 +5645,7 @@ impl<Signer: Sign> Channel<Signer> {
}

const SERIALIZATION_VERSION: u8 = 2;
const MIN_SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 2;
Comment thread
TheBlueMatt marked this conversation as resolved.

impl_writeable_tlv_based_enum!(InboundHTLCRemovalReason,;
(0, FailRelay),
Expand DownExpand Up@@ -5684,12 +5710,10 @@ impl<Signer: Sign> Writeable for Channel<Signer> {

self.user_id.write(writer)?;

// Write out the old serialization for the config object. This is read by version-1
// deserializers, but we will read the version in the TLV at the end instead.
self.config.forwarding_fee_proportional_millionths.write(writer)?;
self.config.cltv_expiry_delta.write(writer)?;
self.config.announced_channel.write(writer)?;
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
// Version 1 deserializers expected to read parts of the config object here. Version 2
// deserializers (0.0.99) now read config through TLVs, and as we now require them for
// `minimum_depth` we simply write dummy values here.
writer.write_all(&[0; 8])?;

self.channel_id.write(writer)?;
(self.channel_state | ChannelState::PeerDisconnected as u32).write(writer)?;
Expand DownExpand Up@@ -6667,7 +6691,7 @@ mod tests {
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).unwrap();
let (funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();
let (funding_signed_msg, _, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: capacity,
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
Expand Down
96 changes: 60 additions & 36 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,6 +710,11 @@ pub(super) struct Channel<Signer: Sign> {
// Our counterparty can offer us SCID aliases which they will map to this channel when routing
// outbound payments. These can be used in invoice route hints to avoid explicitly revealing
// the channel's funding UTXO.
//
// We also use this when sending our peer a channel_update that isn't to be broadcasted
// publicly - allowing them to re-use their map of SCID -> channel for channel_update ->
// associated channel mapping.
//
// We only bother storing the most recent SCID alias at any time, though our counterparty has
// to store all of them.
latest_inbound_scid_alias: Option<u64>,
Expand DownExpand Up@@ -1307,7 +1312,7 @@ impl<Signer: Sign> Channel<Signer> {
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
holder_htlc_minimum_msat: if config.own_channel_config.our_htlc_minimum_msat == 0 { 1 } else { config.own_channel_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
minimum_depth: Some(config.own_channel_config.minimum_depth),
minimum_depth: Some(cmp::max(config.own_channel_config.minimum_depth, 1)),

counterparty_forwarding_info: None,

Expand DownExpand Up@@ -1987,12 +1992,6 @@ impl<Signer: Sign> Channel<Signer> {
if msg.minimum_depth > peer_limits.max_minimum_depth {
return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth)));
}
if msg.minimum_depth == 0 {
// Note that if this changes we should update the serialization minimum version to
// indicate to older clients that they don't understand some features of the current
// channel.
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}

if let Some(ty) = &msg.channel_type {
if *ty != self.channel_type {
Expand DownExpand Up@@ -2029,7 +2028,12 @@ impl<Signer: Sign> Channel<Signer> {
self.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
self.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat;
self.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs;
self.minimum_depth = Some(msg.minimum_depth);

if peer_limits.trust_own_funding_0conf {
self.minimum_depth = Some(msg.minimum_depth);
} else {
self.minimum_depth = Some(cmp::max(1, msg.minimum_depth));
Comment thread
valentinewallace marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would previously return an error when min_depth == 0, whereas now we'll enforce a minimum of one, so they may think we've agreed to proceed with zero confs once we send funding_created back.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Right, this is kinda a 0conf philosophy question. This PR doesn't actually support the 0conf channel type (as it was written before the 0conf channel type existed) which is the "I require 0conf" option, but we'll add that in a followup.

In general, if you're doing a 0conf channel, you've proably pre-negotiated it in some way - most likely the recipient of the channel is pre-configured to trust an LSP. There's arguably not a lot of value in negotiating 0conf in the open/accept channel flow - by the time you're there both sides of the channel should really already know if the channel should be 0conf or not.

More generally, nodes can always delay channel_ready/funding_locked as long as they want - the minimum_depth field is just a hint, and the protocol can't enforce it in any way. We're not strictly-speaking "out of spec" by delaying more than the channel recipient wanted.

}

let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.funding_pubkey,
Expand DownExpand Up@@ -2089,7 +2093,7 @@ impl<Signer: Sign> Channel<Signer> {
&self.get_counterparty_pubkeys().funding_pubkey
}

pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>), ChannelError> where L::Target: Logger {
pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if self.is_outbound() {
return Err(ChannelError::Close("Received funding_created for an outbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2164,12 +2168,12 @@ impl<Signer: Sign> Channel<Signer> {
Ok((msgs::FundingSigned {
channel_id: self.channel_id,
signature
}, channel_monitor))
}, channel_monitor, self.check_get_funding_locked(0)))
}

/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction), ChannelError> where L::Target: Logger {
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if !self.is_outbound() {
return Err(ChannelError::Close("Received funding_signed for an inbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2238,7 +2242,7 @@ impl<Signer: Sign> Channel<Signer> {

log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.channel_id()));

Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap()))
Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap(), self.check_get_funding_locked(0)))
}

/// Handles a funding_locked message from our peer. If we've already sent our funding_locked
Expand DownExpand Up@@ -3540,12 +3544,13 @@ impl<Signer: Sign> Channel<Signer> {
/// monitor update failure must *not* have been sent to the remote end, and must instead
/// have been dropped. They will be regenerated when monitor_updating_restored is called.
pub fn monitor_update_failed(&mut self, resend_raa: bool, resend_commitment: bool,
mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
resend_funding_locked: bool, mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
mut pending_fails: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>,
mut pending_finalized_claimed_htlcs: Vec<HTLCSource>
) {
self.monitor_pending_revoke_and_ack |= resend_raa;
self.monitor_pending_commitment_signed |= resend_commitment;
self.monitor_pending_funding_locked |= resend_funding_locked;
self.monitor_pending_forwards.append(&mut pending_forwards);
self.monitor_pending_failures.append(&mut pending_fails);
self.monitor_pending_finalized_fulfills.append(&mut pending_finalized_claimed_htlcs);
Expand All@@ -3559,17 +3564,28 @@ impl<Signer: Sign> Channel<Signer> {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateFailed as u32, ChannelState::MonitorUpdateFailed as u32);
self.channel_state &= !(ChannelState::MonitorUpdateFailed as u32);

let funding_broadcastable = if self.channel_state & (ChannelState::FundingSent as u32) != 0 && self.is_outbound() {
self.funding_transaction.take()
} else { None };
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
if self.is_outbound() && self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change almost seems like it belongs in a separate commit or be documented in the commit message, seemed a bit unrelated at first

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Updated the commit message.

self.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
if self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelFunded as u32 && self.minimum_depth != Some(0) {
funding_broadcastable = None;
}

// We will never broadcast the funding transaction when we're in MonitorUpdateFailed (and
// we assume the user never directly broadcasts the funding transaction and waits for us to
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're an inbound
// channel which failed to persist the monitor on funding_created, and we got the funding
// transaction confirmed before the monitor was persisted.
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're
// * an inbound channel that failed to persist the monitor on funding_created and we got
// the funding transaction confirmed before the monitor was persisted, or
// * a 0-conf channel and intended to send the funding_locked before any broadcast at all.
let funding_locked = if self.monitor_pending_funding_locked {
assert!(!self.is_outbound(), "Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
assert!(!self.is_outbound() || self.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.monitor_pending_funding_locked = false;
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
Some(msgs::FundingLocked {
Expand DownExpand Up@@ -4551,6 +4567,11 @@ impl<Signer: Sign> Channel<Signer> {
self.channel_state >= ChannelState::FundingSent as u32
}

/// Returns true if our funding_locked has been sent
pub fn is_our_funding_locked(&self) -> bool {
(self.channel_state & ChannelState::OurFundingLocked as u32) != 0 || self.channel_state >= ChannelState::ChannelFunded as u32
}

/// Returns true if our peer has either initiated or agreed to shut down the channel.
pub fn received_shutdown(&self) -> bool {
(self.channel_state & ChannelState::RemoteShutdownSent as u32) != 0
Expand DownExpand Up@@ -4581,7 +4602,7 @@ impl<Signer: Sign> Channel<Signer> {
}

fn check_get_funding_locked(&mut self, height: u32) -> Option<msgs::FundingLocked> {
if self.funding_tx_confirmation_height == 0 {
if self.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) {
return None;
}

Expand DownExpand Up@@ -4636,12 +4657,11 @@ impl<Signer: Sign> Channel<Signer> {
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, logger: &L)
-> Result<(Option<msgs::FundingLocked>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
// whether they've sent a funding_locked or not), check if we should send one.
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
// Check if the transaction is the expected funding transaction, and if it is,
// check that it pays the right amount to the right script.
if self.funding_tx_confirmation_height == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this predated the PR, but it's unclear to me what funding_tx_confirmation_height of 0 means.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, it means "not yet confirmed", as certainly no channel is going to be confirmed at height 0 :) I kinda figured it was an obvious default but I can add docs if you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Meh, maybe an is_funding_confirmed function would be more readable, but no need to do anything here.

if tx.txid() == funding_txo.txid {
let txo_idx = funding_txo.index as usize;
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
Expand DownExpand Up@@ -4758,9 +4778,9 @@ impl<Signer: Sign> Channel<Signer> {
// close the channel and hope we can get the latest state on chain (because presumably
// the funding transaction is at least still in the mempool of most nodes).
//
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf channel,
// but not doing so may lead to the `ChannelManager::short_to_id` map being
// inconsistent, so we currently have to.
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf or
// 0-conf channel, but not doing so may lead to the `ChannelManager::short_to_id` map
// being inconsistent, so we currently have to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apart of more code complexity, I don't understand why we would force-close a 0-conf channel as we already assume it's safe without block inclusion ? And I'm not sure there is a real inconsistency risk with the short_to_id being inserted at funding_locked being a fake SCID ? Though one concern would be the funding inputs unconfirmed themselves.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

No, we should fix that eventually - #867

if funding_tx_confirmations == 0 && self.funding_tx_confirmed_in.is_some() {
let err_reason = format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.",
self.minimum_depth.unwrap(), funding_tx_confirmations);
Expand DownExpand Up@@ -4857,6 +4877,12 @@ impl<Signer: Sign> Channel<Signer> {
self.inbound_awaiting_accept
}

/// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
pub fn set_0conf(&mut self) {
assert!(self.inbound_awaiting_accept);
self.minimum_depth = Some(0);
}

/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
Expand DownExpand Up@@ -5619,7 +5645,7 @@ impl<Signer: Sign> Channel<Signer> {
}

const SERIALIZATION_VERSION: u8 = 2;
const MIN_SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 2;
Comment thread
TheBlueMatt marked this conversation as resolved.

impl_writeable_tlv_based_enum!(InboundHTLCRemovalReason,;
(0, FailRelay),
Expand DownExpand Up@@ -5684,12 +5710,10 @@ impl<Signer: Sign> Writeable for Channel<Signer> {

self.user_id.write(writer)?;

// Write out the old serialization for the config object. This is read by version-1
// deserializers, but we will read the version in the TLV at the end instead.
self.config.forwarding_fee_proportional_millionths.write(writer)?;
self.config.cltv_expiry_delta.write(writer)?;
self.config.announced_channel.write(writer)?;
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
// Version 1 deserializers expected to read parts of the config object here. Version 2
// deserializers (0.0.99) now read config through TLVs, and as we now require them for
// `minimum_depth` we simply write dummy values here.
writer.write_all(&[0; 8])?;

self.channel_id.write(writer)?;
(self.channel_state | ChannelState::PeerDisconnected as u32).write(writer)?;
Expand DownExpand Up@@ -6667,7 +6691,7 @@ mod tests {
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).unwrap();
let (funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();
let (funding_signed_msg, _, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: capacity,
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
Expand Down
96 changes: 60 additions & 36 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,6 +710,11 @@ pub(super) struct Channel<Signer: Sign> {
// Our counterparty can offer us SCID aliases which they will map to this channel when routing
// outbound payments. These can be used in invoice route hints to avoid explicitly revealing
// the channel's funding UTXO.
//
// We also use this when sending our peer a channel_update that isn't to be broadcasted
// publicly - allowing them to re-use their map of SCID -> channel for channel_update ->
// associated channel mapping.
//
// We only bother storing the most recent SCID alias at any time, though our counterparty has
// to store all of them.
latest_inbound_scid_alias: Option<u64>,
Expand DownExpand Up@@ -1307,7 +1312,7 @@ impl<Signer: Sign> Channel<Signer> {
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
holder_htlc_minimum_msat: if config.own_channel_config.our_htlc_minimum_msat == 0 { 1 } else { config.own_channel_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
minimum_depth: Some(config.own_channel_config.minimum_depth),
minimum_depth: Some(cmp::max(config.own_channel_config.minimum_depth, 1)),

counterparty_forwarding_info: None,

Expand DownExpand Up@@ -1987,12 +1992,6 @@ impl<Signer: Sign> Channel<Signer> {
if msg.minimum_depth > peer_limits.max_minimum_depth {
return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth)));
}
if msg.minimum_depth == 0 {
// Note that if this changes we should update the serialization minimum version to
// indicate to older clients that they don't understand some features of the current
// channel.
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}

if let Some(ty) = &msg.channel_type {
if *ty != self.channel_type {
Expand DownExpand Up@@ -2029,7 +2028,12 @@ impl<Signer: Sign> Channel<Signer> {
self.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
self.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat;
self.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs;
self.minimum_depth = Some(msg.minimum_depth);

if peer_limits.trust_own_funding_0conf {
self.minimum_depth = Some(msg.minimum_depth);
} else {
self.minimum_depth = Some(cmp::max(1, msg.minimum_depth));
Comment thread
valentinewallace marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would previously return an error when min_depth == 0, whereas now we'll enforce a minimum of one, so they may think we've agreed to proceed with zero confs once we send funding_created back.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Right, this is kinda a 0conf philosophy question. This PR doesn't actually support the 0conf channel type (as it was written before the 0conf channel type existed) which is the "I require 0conf" option, but we'll add that in a followup.

In general, if you're doing a 0conf channel, you've proably pre-negotiated it in some way - most likely the recipient of the channel is pre-configured to trust an LSP. There's arguably not a lot of value in negotiating 0conf in the open/accept channel flow - by the time you're there both sides of the channel should really already know if the channel should be 0conf or not.

More generally, nodes can always delay channel_ready/funding_locked as long as they want - the minimum_depth field is just a hint, and the protocol can't enforce it in any way. We're not strictly-speaking "out of spec" by delaying more than the channel recipient wanted.

}

let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.funding_pubkey,
Expand DownExpand Up@@ -2089,7 +2093,7 @@ impl<Signer: Sign> Channel<Signer> {
&self.get_counterparty_pubkeys().funding_pubkey
}

pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>), ChannelError> where L::Target: Logger {
pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if self.is_outbound() {
return Err(ChannelError::Close("Received funding_created for an outbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2164,12 +2168,12 @@ impl<Signer: Sign> Channel<Signer> {
Ok((msgs::FundingSigned {
channel_id: self.channel_id,
signature
}, channel_monitor))
}, channel_monitor, self.check_get_funding_locked(0)))
}

/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction), ChannelError> where L::Target: Logger {
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if !self.is_outbound() {
return Err(ChannelError::Close("Received funding_signed for an inbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2238,7 +2242,7 @@ impl<Signer: Sign> Channel<Signer> {

log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.channel_id()));

Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap()))
Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap(), self.check_get_funding_locked(0)))
}

/// Handles a funding_locked message from our peer. If we've already sent our funding_locked
Expand DownExpand Up@@ -3540,12 +3544,13 @@ impl<Signer: Sign> Channel<Signer> {
/// monitor update failure must *not* have been sent to the remote end, and must instead
/// have been dropped. They will be regenerated when monitor_updating_restored is called.
pub fn monitor_update_failed(&mut self, resend_raa: bool, resend_commitment: bool,
mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
resend_funding_locked: bool, mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
mut pending_fails: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>,
mut pending_finalized_claimed_htlcs: Vec<HTLCSource>
) {
self.monitor_pending_revoke_and_ack |= resend_raa;
self.monitor_pending_commitment_signed |= resend_commitment;
self.monitor_pending_funding_locked |= resend_funding_locked;
self.monitor_pending_forwards.append(&mut pending_forwards);
self.monitor_pending_failures.append(&mut pending_fails);
self.monitor_pending_finalized_fulfills.append(&mut pending_finalized_claimed_htlcs);
Expand All@@ -3559,17 +3564,28 @@ impl<Signer: Sign> Channel<Signer> {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateFailed as u32, ChannelState::MonitorUpdateFailed as u32);
self.channel_state &= !(ChannelState::MonitorUpdateFailed as u32);

let funding_broadcastable = if self.channel_state & (ChannelState::FundingSent as u32) != 0 && self.is_outbound() {
self.funding_transaction.take()
} else { None };
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
if self.is_outbound() && self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change almost seems like it belongs in a separate commit or be documented in the commit message, seemed a bit unrelated at first

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Updated the commit message.

self.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
if self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelFunded as u32 && self.minimum_depth != Some(0) {
funding_broadcastable = None;
}

// We will never broadcast the funding transaction when we're in MonitorUpdateFailed (and
// we assume the user never directly broadcasts the funding transaction and waits for us to
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're an inbound
// channel which failed to persist the monitor on funding_created, and we got the funding
// transaction confirmed before the monitor was persisted.
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're
// * an inbound channel that failed to persist the monitor on funding_created and we got
// the funding transaction confirmed before the monitor was persisted, or
// * a 0-conf channel and intended to send the funding_locked before any broadcast at all.
let funding_locked = if self.monitor_pending_funding_locked {
assert!(!self.is_outbound(), "Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
assert!(!self.is_outbound() || self.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.monitor_pending_funding_locked = false;
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
Some(msgs::FundingLocked {
Expand DownExpand Up@@ -4551,6 +4567,11 @@ impl<Signer: Sign> Channel<Signer> {
self.channel_state >= ChannelState::FundingSent as u32
}

/// Returns true if our funding_locked has been sent
pub fn is_our_funding_locked(&self) -> bool {
(self.channel_state & ChannelState::OurFundingLocked as u32) != 0 || self.channel_state >= ChannelState::ChannelFunded as u32
}

/// Returns true if our peer has either initiated or agreed to shut down the channel.
pub fn received_shutdown(&self) -> bool {
(self.channel_state & ChannelState::RemoteShutdownSent as u32) != 0
Expand DownExpand Up@@ -4581,7 +4602,7 @@ impl<Signer: Sign> Channel<Signer> {
}

fn check_get_funding_locked(&mut self, height: u32) -> Option<msgs::FundingLocked> {
if self.funding_tx_confirmation_height == 0 {
if self.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) {
return None;
}

Expand DownExpand Up@@ -4636,12 +4657,11 @@ impl<Signer: Sign> Channel<Signer> {
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, logger: &L)
-> Result<(Option<msgs::FundingLocked>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
// whether they've sent a funding_locked or not), check if we should send one.
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
// Check if the transaction is the expected funding transaction, and if it is,
// check that it pays the right amount to the right script.
if self.funding_tx_confirmation_height == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this predated the PR, but it's unclear to me what funding_tx_confirmation_height of 0 means.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, it means "not yet confirmed", as certainly no channel is going to be confirmed at height 0 :) I kinda figured it was an obvious default but I can add docs if you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Meh, maybe an is_funding_confirmed function would be more readable, but no need to do anything here.

if tx.txid() == funding_txo.txid {
let txo_idx = funding_txo.index as usize;
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
Expand DownExpand Up@@ -4758,9 +4778,9 @@ impl<Signer: Sign> Channel<Signer> {
// close the channel and hope we can get the latest state on chain (because presumably
// the funding transaction is at least still in the mempool of most nodes).
//
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf channel,
// but not doing so may lead to the `ChannelManager::short_to_id` map being
// inconsistent, so we currently have to.
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf or
// 0-conf channel, but not doing so may lead to the `ChannelManager::short_to_id` map
// being inconsistent, so we currently have to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apart of more code complexity, I don't understand why we would force-close a 0-conf channel as we already assume it's safe without block inclusion ? And I'm not sure there is a real inconsistency risk with the short_to_id being inserted at funding_locked being a fake SCID ? Though one concern would be the funding inputs unconfirmed themselves.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

No, we should fix that eventually - #867

if funding_tx_confirmations == 0 && self.funding_tx_confirmed_in.is_some() {
let err_reason = format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.",
self.minimum_depth.unwrap(), funding_tx_confirmations);
Expand DownExpand Up@@ -4857,6 +4877,12 @@ impl<Signer: Sign> Channel<Signer> {
self.inbound_awaiting_accept
}

/// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
pub fn set_0conf(&mut self) {
assert!(self.inbound_awaiting_accept);
self.minimum_depth = Some(0);
}

/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
Expand DownExpand Up@@ -5619,7 +5645,7 @@ impl<Signer: Sign> Channel<Signer> {
}

const SERIALIZATION_VERSION: u8 = 2;
const MIN_SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 2;
Comment thread
TheBlueMatt marked this conversation as resolved.

impl_writeable_tlv_based_enum!(InboundHTLCRemovalReason,;
(0, FailRelay),
Expand DownExpand Up@@ -5684,12 +5710,10 @@ impl<Signer: Sign> Writeable for Channel<Signer> {

self.user_id.write(writer)?;

// Write out the old serialization for the config object. This is read by version-1
// deserializers, but we will read the version in the TLV at the end instead.
self.config.forwarding_fee_proportional_millionths.write(writer)?;
self.config.cltv_expiry_delta.write(writer)?;
self.config.announced_channel.write(writer)?;
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
// Version 1 deserializers expected to read parts of the config object here. Version 2
// deserializers (0.0.99) now read config through TLVs, and as we now require them for
// `minimum_depth` we simply write dummy values here.
writer.write_all(&[0; 8])?;

self.channel_id.write(writer)?;
(self.channel_state | ChannelState::PeerDisconnected as u32).write(writer)?;
Expand DownExpand Up@@ -6667,7 +6691,7 @@ mod tests {
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).unwrap();
let (funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();
let (funding_signed_msg, _, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fuzz/src/router.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -222,6 +222,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
outbound_scid_alias: None,
channel_value_satoshis: capacity,
user_channel_id: 0, inbound_capacity_msat: 0,
unspendable_punishment_reserve: None,
Expand Down
96 changes: 60 additions & 36 deletions lightning/src/ln/channel.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -710,6 +710,11 @@ pub(super) struct Channel<Signer: Sign> {
// Our counterparty can offer us SCID aliases which they will map to this channel when routing
// outbound payments. These can be used in invoice route hints to avoid explicitly revealing
// the channel's funding UTXO.
//
// We also use this when sending our peer a channel_update that isn't to be broadcasted
// publicly - allowing them to re-use their map of SCID -> channel for channel_update ->
// associated channel mapping.
//
// We only bother storing the most recent SCID alias at any time, though our counterparty has
// to store all of them.
latest_inbound_scid_alias: Option<u64>,
Expand DownExpand Up@@ -1307,7 +1312,7 @@ impl<Signer: Sign> Channel<Signer> {
counterparty_htlc_minimum_msat: msg.htlc_minimum_msat,
holder_htlc_minimum_msat: if config.own_channel_config.our_htlc_minimum_msat == 0 { 1 } else { config.own_channel_config.our_htlc_minimum_msat },
counterparty_max_accepted_htlcs: msg.max_accepted_htlcs,
minimum_depth: Some(config.own_channel_config.minimum_depth),
minimum_depth: Some(cmp::max(config.own_channel_config.minimum_depth, 1)),

counterparty_forwarding_info: None,

Expand DownExpand Up@@ -1987,12 +1992,6 @@ impl<Signer: Sign> Channel<Signer> {
if msg.minimum_depth > peer_limits.max_minimum_depth {
return Err(ChannelError::Close(format!("We consider the minimum depth to be unreasonably large. Expected minimum: ({}). Actual: ({})", peer_limits.max_minimum_depth, msg.minimum_depth)));
}
if msg.minimum_depth == 0 {
// Note that if this changes we should update the serialization minimum version to
// indicate to older clients that they don't understand some features of the current
// channel.
return Err(ChannelError::Close("Minimum confirmation depth must be at least 1".to_owned()));
}

if let Some(ty) = &msg.channel_type {
if *ty != self.channel_type {
Expand DownExpand Up@@ -2029,7 +2028,12 @@ impl<Signer: Sign> Channel<Signer> {
self.counterparty_selected_channel_reserve_satoshis = Some(msg.channel_reserve_satoshis);
self.counterparty_htlc_minimum_msat = msg.htlc_minimum_msat;
self.counterparty_max_accepted_htlcs = msg.max_accepted_htlcs;
self.minimum_depth = Some(msg.minimum_depth);

if peer_limits.trust_own_funding_0conf {
self.minimum_depth = Some(msg.minimum_depth);
} else {
self.minimum_depth = Some(cmp::max(1, msg.minimum_depth));
Comment thread
valentinewallace marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would previously return an error when min_depth == 0, whereas now we'll enforce a minimum of one, so they may think we've agreed to proceed with zero confs once we send funding_created back.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Right, this is kinda a 0conf philosophy question. This PR doesn't actually support the 0conf channel type (as it was written before the 0conf channel type existed) which is the "I require 0conf" option, but we'll add that in a followup.

In general, if you're doing a 0conf channel, you've proably pre-negotiated it in some way - most likely the recipient of the channel is pre-configured to trust an LSP. There's arguably not a lot of value in negotiating 0conf in the open/accept channel flow - by the time you're there both sides of the channel should really already know if the channel should be 0conf or not.

More generally, nodes can always delay channel_ready/funding_locked as long as they want - the minimum_depth field is just a hint, and the protocol can't enforce it in any way. We're not strictly-speaking "out of spec" by delaying more than the channel recipient wanted.

}

let counterparty_pubkeys = ChannelPublicKeys {
funding_pubkey: msg.funding_pubkey,
Expand DownExpand Up@@ -2089,7 +2093,7 @@ impl<Signer: Sign> Channel<Signer> {
&self.get_counterparty_pubkeys().funding_pubkey
}

pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>), ChannelError> where L::Target: Logger {
pub fn funding_created<L: Deref>(&mut self, msg: &msgs::FundingCreated, best_block: BestBlock, logger: &L) -> Result<(msgs::FundingSigned, ChannelMonitor<Signer>, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if self.is_outbound() {
return Err(ChannelError::Close("Received funding_created for an outbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2164,12 +2168,12 @@ impl<Signer: Sign> Channel<Signer> {
Ok((msgs::FundingSigned {
channel_id: self.channel_id,
signature
}, channel_monitor))
}, channel_monitor, self.check_get_funding_locked(0)))
}

/// Handles a funding_signed message from the remote end.
/// If this call is successful, broadcast the funding transaction (and not before!)
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction), ChannelError> where L::Target: Logger {
pub fn funding_signed<L: Deref>(&mut self, msg: &msgs::FundingSigned, best_block: BestBlock, logger: &L) -> Result<(ChannelMonitor<Signer>, Transaction, Option<msgs::FundingLocked>), ChannelError> where L::Target: Logger {
if !self.is_outbound() {
return Err(ChannelError::Close("Received funding_signed for an inbound channel?".to_owned()));
}
Expand DownExpand Up@@ -2238,7 +2242,7 @@ impl<Signer: Sign> Channel<Signer> {

log_info!(logger, "Received funding_signed from peer for channel {}", log_bytes!(self.channel_id()));

Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap()))
Ok((channel_monitor, self.funding_transaction.as_ref().cloned().unwrap(), self.check_get_funding_locked(0)))
}

/// Handles a funding_locked message from our peer. If we've already sent our funding_locked
Expand DownExpand Up@@ -3540,12 +3544,13 @@ impl<Signer: Sign> Channel<Signer> {
/// monitor update failure must *not* have been sent to the remote end, and must instead
/// have been dropped. They will be regenerated when monitor_updating_restored is called.
pub fn monitor_update_failed(&mut self, resend_raa: bool, resend_commitment: bool,
mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
resend_funding_locked: bool, mut pending_forwards: Vec<(PendingHTLCInfo, u64)>,
mut pending_fails: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>,
mut pending_finalized_claimed_htlcs: Vec<HTLCSource>
) {
self.monitor_pending_revoke_and_ack |= resend_raa;
self.monitor_pending_commitment_signed |= resend_commitment;
self.monitor_pending_funding_locked |= resend_funding_locked;
self.monitor_pending_forwards.append(&mut pending_forwards);
self.monitor_pending_failures.append(&mut pending_fails);
self.monitor_pending_finalized_fulfills.append(&mut pending_finalized_claimed_htlcs);
Expand All@@ -3559,17 +3564,28 @@ impl<Signer: Sign> Channel<Signer> {
assert_eq!(self.channel_state & ChannelState::MonitorUpdateFailed as u32, ChannelState::MonitorUpdateFailed as u32);
self.channel_state &= !(ChannelState::MonitorUpdateFailed as u32);

let funding_broadcastable = if self.channel_state & (ChannelState::FundingSent as u32) != 0 && self.is_outbound() {
self.funding_transaction.take()
} else { None };
// If we're past (or at) the FundingSent stage on an outbound channel, try to
// (re-)broadcast the funding transaction as we may have declined to broadcast it when we
// first received the funding_signed.
let mut funding_broadcastable =
if self.is_outbound() && self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::FundingSent as u32 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change almost seems like it belongs in a separate commit or be documented in the commit message, seemed a bit unrelated at first

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Updated the commit message.

self.funding_transaction.take()
} else { None };
// That said, if the funding transaction is already confirmed (ie we're active with a
// minimum_depth over 0) don't bother re-broadcasting the confirmed funding tx.
if self.channel_state & !MULTI_STATE_FLAGS >= ChannelState::ChannelFunded as u32 && self.minimum_depth != Some(0) {
funding_broadcastable = None;
}

// We will never broadcast the funding transaction when we're in MonitorUpdateFailed (and
// we assume the user never directly broadcasts the funding transaction and waits for us to
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're an inbound
// channel which failed to persist the monitor on funding_created, and we got the funding
// transaction confirmed before the monitor was persisted.
// do it). Thus, we can only ever hit monitor_pending_funding_locked when we're
// * an inbound channel that failed to persist the monitor on funding_created and we got
// the funding transaction confirmed before the monitor was persisted, or
// * a 0-conf channel and intended to send the funding_locked before any broadcast at all.
let funding_locked = if self.monitor_pending_funding_locked {
assert!(!self.is_outbound(), "Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
assert!(!self.is_outbound() || self.minimum_depth == Some(0),
"Funding transaction broadcast by the local client before it should have - LDK didn't do it!");
self.monitor_pending_funding_locked = false;
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number, &self.secp_ctx);
Some(msgs::FundingLocked {
Expand DownExpand Up@@ -4551,6 +4567,11 @@ impl<Signer: Sign> Channel<Signer> {
self.channel_state >= ChannelState::FundingSent as u32
}

/// Returns true if our funding_locked has been sent
pub fn is_our_funding_locked(&self) -> bool {
(self.channel_state & ChannelState::OurFundingLocked as u32) != 0 || self.channel_state >= ChannelState::ChannelFunded as u32
}

/// Returns true if our peer has either initiated or agreed to shut down the channel.
pub fn received_shutdown(&self) -> bool {
(self.channel_state & ChannelState::RemoteShutdownSent as u32) != 0
Expand DownExpand Up@@ -4581,7 +4602,7 @@ impl<Signer: Sign> Channel<Signer> {
}

fn check_get_funding_locked(&mut self, height: u32) -> Option<msgs::FundingLocked> {
if self.funding_tx_confirmation_height == 0 {
if self.funding_tx_confirmation_height == 0 && self.minimum_depth != Some(0) {
return None;
}

Expand DownExpand Up@@ -4636,12 +4657,11 @@ impl<Signer: Sign> Channel<Signer> {
pub fn transactions_confirmed<L: Deref>(&mut self, block_hash: &BlockHash, height: u32,
txdata: &TransactionData, genesis_block_hash: BlockHash, node_pk: PublicKey, logger: &L)
-> Result<(Option<msgs::FundingLocked>, Option<msgs::AnnouncementSignatures>), ClosureReason> where L::Target: Logger {
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
if let Some(funding_txo) = self.get_funding_txo() {
for &(index_in_block, tx) in txdata.iter() {
// If we haven't yet sent a funding_locked, but are in FundingSent (ignoring
// whether they've sent a funding_locked or not), check if we should send one.
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
// Check if the transaction is the expected funding transaction, and if it is,
// check that it pays the right amount to the right script.
if self.funding_tx_confirmation_height == 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this predated the PR, but it's unclear to me what funding_tx_confirmation_height of 0 means.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah, it means "not yet confirmed", as certainly no channel is going to be confirmed at height 0 :) I kinda figured it was an obvious default but I can add docs if you prefer?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Meh, maybe an is_funding_confirmed function would be more readable, but no need to do anything here.

if tx.txid() == funding_txo.txid {
let txo_idx = funding_txo.index as usize;
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.get_funding_redeemscript().to_v0_p2wsh() ||
Expand DownExpand Up@@ -4758,9 +4778,9 @@ impl<Signer: Sign> Channel<Signer> {
// close the channel and hope we can get the latest state on chain (because presumably
// the funding transaction is at least still in the mempool of most nodes).
//
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf channel,
// but not doing so may lead to the `ChannelManager::short_to_id` map being
// inconsistent, so we currently have to.
// Note that ideally we wouldn't force-close if we see *any* reorg on a 1-conf or
// 0-conf channel, but not doing so may lead to the `ChannelManager::short_to_id` map
// being inconsistent, so we currently have to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Apart of more code complexity, I don't understand why we would force-close a 0-conf channel as we already assume it's safe without block inclusion ? And I'm not sure there is a real inconsistency risk with the short_to_id being inserted at funding_locked being a fake SCID ? Though one concern would be the funding inputs unconfirmed themselves.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

No, we should fix that eventually - #867

if funding_tx_confirmations == 0 && self.funding_tx_confirmed_in.is_some() {
let err_reason = format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.",
self.minimum_depth.unwrap(), funding_tx_confirmations);
Expand DownExpand Up@@ -4857,6 +4877,12 @@ impl<Signer: Sign> Channel<Signer> {
self.inbound_awaiting_accept
}

/// Sets this channel to accepting 0conf, must be done before `get_accept_channel`
pub fn set_0conf(&mut self) {
assert!(self.inbound_awaiting_accept);
self.minimum_depth = Some(0);
}

/// Marks an inbound channel as accepted and generates a [`msgs::AcceptChannel`] message which
/// should be sent back to the counterparty node.
///
Expand DownExpand Up@@ -5619,7 +5645,7 @@ impl<Signer: Sign> Channel<Signer> {
}

const SERIALIZATION_VERSION: u8 = 2;
const MIN_SERIALIZATION_VERSION: u8 = 1;
const MIN_SERIALIZATION_VERSION: u8 = 2;
Comment thread
TheBlueMatt marked this conversation as resolved.

impl_writeable_tlv_based_enum!(InboundHTLCRemovalReason,;
(0, FailRelay),
Expand DownExpand Up@@ -5684,12 +5710,10 @@ impl<Signer: Sign> Writeable for Channel<Signer> {

self.user_id.write(writer)?;

// Write out the old serialization for the config object. This is read by version-1
// deserializers, but we will read the version in the TLV at the end instead.
self.config.forwarding_fee_proportional_millionths.write(writer)?;
self.config.cltv_expiry_delta.write(writer)?;
self.config.announced_channel.write(writer)?;
self.config.commit_upfront_shutdown_pubkey.write(writer)?;
// Version 1 deserializers expected to read parts of the config object here. Version 2
// deserializers (0.0.99) now read config through TLVs, and as we now require them for
// `minimum_depth` we simply write dummy values here.
writer.write_all(&[0; 8])?;

self.channel_id.write(writer)?;
(self.channel_state | ChannelState::PeerDisconnected as u32).write(writer)?;
Expand DownExpand Up@@ -6667,7 +6691,7 @@ mod tests {
}]};
let funding_outpoint = OutPoint{ txid: tx.txid(), index: 0 };
let funding_created_msg = node_a_chan.get_outbound_funding_created(tx.clone(), funding_outpoint, &&logger).unwrap();
let (funding_signed_msg, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();
let (funding_signed_msg, _, _) = node_b_chan.funding_created(&funding_created_msg, best_block, &&logger).unwrap();

// Node B --> Node A: funding signed
let _ = node_a_chan.funding_signed(&funding_signed_msg, best_block, &&logger);
Expand Down
Loading