Create SpendableOutputs events no matter the chain::Confirm order - #970

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay
Jul 2, 2021
Merged

Create SpendableOutputs events no matter the chain::Confirm order#970
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jun 25, 2021

Copy link
Copy Markdown
Collaborator

We had a user who pointed out that we weren't creating
SpendableOutputs events when we should have been after they
called ChannelMonitor::best_block_updated with a block well
after a CSV locktime and then called
ChannelMonitor::transactions_confirmed with the transaction which
we should have been spending (with a block height/hash a ways in
the past).

This was due to ChannelMonitor::transactions_confirmed only
calling ChannelMonitor::block_confirmed with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.

Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.

To address this, we use the best block height for confirmation
threshold checks in ChannelMonitor::block_confirmed and pass both
the confirmation and current heights through to
OnchainTx::update_claims_view, using each as appropriate.

Note to reviewers: please carefully check the update_claims_view part of the changeset here, there may very well be some unintended double-use of the height which wasn't properly separated (though I don't see any immediately).

Fixes#962.

@TheBlueMattTheBlueMatt added this to the 0.0.99 milestone Jun 25, 2021
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 7206c03 to 8216af0CompareJune 25, 2021 17:43
@codecov

codecovBot commented Jun 25, 2021

Copy link
Copy Markdown

Codecov Report

Merging #970 (5e51f84) into main (f472907) will increase coverage by 0.30%.
The diff coverage is 98.27%.

❗ Current head 5e51f84 differs from pull request most recent head 412cc9f. Consider uploading reports for the commit 412cc9f to get more accurate results
Impacted file tree graph

@@ Coverage Diff @@## main #970 +/- ##
==========================================
+ Coverage 90.63% 90.93% +0.30% 
==========================================
Files 60 60 Lines 30538 31896 +1358 ==========================================
+ Hits 27678 29005 +1327 - Misses 2860 2891 +31 
Impacted FilesCoverage Δ
lightning/src/ln/channel.rs90.43% <ø> (+1.92%)⬆️
lightning/src/ln/channelmanager.rs86.76% <ø> (+2.86%)⬆️
lightning/src/chain/onchaintx.rs94.19% <93.75%> (ø)
lightning/src/chain/channelmonitor.rs90.80% <95.65%> (+0.04%)⬆️
lightning/src/ln/functional_test_utils.rs94.83% <100.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.34% <100.00%> (+0.17%)⬆️
lightning/src/util/events.rs15.15% <0.00%> (-2.15%)⬇️
lightning/src/ln/chanmon_update_fail_tests.rs98.55% <0.00%> (+0.77%)⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f472907...412cc9f. Read the comment docs.

Comment threadlightning/src/ln/functional_test_utils.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Might make sense to update our interfaces (here and elsewhere) to take &BestBlock where that is expected to avoid accidentally passing a confirmation height. Or even directly use it rather than passing it at all where that is possible.

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.

Hmm, suppose we could, I thought renaming height to conf_height was pretty clear (though I could spell it out). For OnchainTx I could tweak it if you think it makes more sense (given it doesn't have its own best_block field)?

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.

Seems like there are a few scenarios:

  • it doesn't need to be passed at all because the method already can access it through self, so why bother passing it? (e.g., remove would_broadcast_at_height can be named simply should_broadcast and doesn't need to take a height, nor does get_broadcasted_holder_claims
  • only the current height is needed, so passing &BestBlock makes it explicit and prevents passing the wrong height (e.g., has_reached_confirmation_threshold)
  • both heights are needed in OnchainTx (maybe leave as is since there is a calculation involved at the call site)

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.

I believe get_broadcasted_holder_claims needs the confirmation height, or at least appears to given it passes it to PackageTx::build_package as height_original, though its API seems like a good opportunity for cleanup. Did the rest.

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

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, in that case its being called with reference to a new commitment transaction being broadcast, not one which is confirmed (new_holder_commitment_transaction), so I think that's correct. Somewhat confusingly, its also called with a 0-height in provide_payment_preimage in a similar context, which is "wrong", but AFAICT harmless.

Ultimately it doesn't really matter much for this case, I think, cause its passed through as height_original in PackageTemplate which is only used to figure out when we can merge packages (which we can never do for the HolderHTLCOutputs that are used here), and when we can broadcast (which we just use the CLTV which is fine).

I'll add some comments and fix the 0 value version.

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 8216af0 to 642f5afCompareJune 25, 2021 19:29
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

Comment on lines +2014 to +2015
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller *must* update
/// `self.best_block` before calling, and set `conf_height` to the height at which any new

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.

Is this right? It isn't the case when called by transactions_confirmed as of now.

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.

I don't think it matters, but its probably simpler/nicer for users to actually set it there, so I did that.

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.

Is this behavior required to fix the bug? Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block? Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

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.

Is this behavior required to fix the bug?

No.

Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block?

Yes, I believe so, at least in ChannelMonitor, dunno about ChannelManager.

Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

I don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

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 don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

It seems like surprising behavior. I called X expecting Y but instead Y + Z happened. I guess I don't see why not just drop the comment rather than altering the behavior if it isn't required to fix the bug. At very least it should be a separate commit stating the reason for the change, IMHO.

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.

It definitely feels "more correct" and should happen, IMO. I moved it to a new commit, let me know what you think of the commit docs and if it sufficiently justifies it.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/onchaintx.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from eaff313 to 9fcc9d2CompareJune 27, 2021 22:18
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Updated to address feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9fcc9d2 to 540bcdeCompareJune 28, 2021 16:38

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A lot of interactions to think about after a first parse.

self.is_paying_spendable_output(&tx, height, &logger);
}

if height > self.best_block.height() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe you can add message commit here : "This avoids complexity in block_confirmed by never having a tracked height set which is higher than our current best confirmed chain, potentially avoiding some bugs in the rather-complicated code" ?

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.

I put it in the doc comment of block_confirmed instead.

if node_txn.len() == 1 {
check_spends!(node_txn[0], chan_2.3);
} else {
assert_eq!(node_txn.len(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Honestly this test path which covers TransactionsFirstSkippingBlocks, the connection style updated by this comment, doesn't make sense to me.

From what I remember, I don't understand why updating best_block in transactions_confirmed instead of best_block_updated only doesn't generate a B's commitment transaction broadcast, they both should fulfill should_broadcast.

I need to look deeper on this...

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.

In the old case, a few blocks before we get here we remove the funding tx output from OnchainTx::claimable_outpoints (which was originally tracked via commitment_tx[0]'s confirmation). Because its not there we can re-add it and re-broadcast our own commitment transaction. In the new case, we skip ahead a few blocks and just call OnchainTx::update_claims_view once, which means only at the very end do we remove the funding tx output, after we've already given up broadcasting the commitment tx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because its not there we can re-add it and re-broadcast our own commitment transaction.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Adding the following diff

diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 7e616ef2..4e1c1c5d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -536,6 +536,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
// been aggregated in a single tx and claimed so atomically
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
for outpoint in request.outpoints() {
+ log_trace!(logger, "Removing claiming outpoint for {}", outpoint.txid);
self.claimable_outpoints.remove(&outpoint);
}
}

And obtaining the following log, confirm that we might remove a tracked output to re-add just after as a new request ? (see 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e)


TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor DynamicOutputP2WPKH 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4:2 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2085] HTLC 75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a failure update has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor StaticOutput 4ee8b85ba72a1a1beb201438e8ec8903ce0acf2e48161e5e61c9e331d048fa70:0 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 0 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1963] Block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 connected with 0 txn matched INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 413] Test if outpoint can be aggregated with expiration 206 against 218 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 615] Adding claiming input for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 616] Finalized transaction b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 ready to broadcast TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 444] Registering claiming request for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 448] Broadcasting onchain commitment tx with txid b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 

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.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Its not height, its the previous outpoint itself. The claimable_outpoints map is indexed by previous outpoint, and in this case we've already registered that we're tracking a spend of the same outpoint, so we don't add a new one. This seems correct to me, if race-y.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmmmm i still think it's a weird height-race because note in the log channelmonitor is processing twice height 206 ? First time we remove the commitment's outpoint from OnchainTxHandler.claimable_outpoints then at second processing we add a new request for the already-confirmed commitment transaction in OnchainTxHandler.claimable_outpoints as the redundant-claim replay protection L393 doesn't play out ?

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, I totally agree that is a (very minor) bug, but this PR just removes that bug depending on the connection style, so I don't think it needs to get fixed here :).

let mut onchain_events_reaching_threshold_conf = Vec::new();
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(height) {
if entry.has_reached_confirmation_threshold(self.best_block.height()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is the updated code path solving the issue describing in the commit. Maybe add PR number and one-line mentioning we had a bug here.

@TheBlueMattTheBlueMattJun 29, 2021

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.

There were two issues, this is one, tbh I don't recall exactly which line in update_claims_view solved the other one. In general we haven't historically updated our code with comments where there was a bug, at least as long as we have a test which will fail if we ever regress (which we do here). It feels weird to start now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right, I don't seep mentions of former bugs over a quick browse of channel.rs/channelmanager.rs though for QA purposes what do you think of tracking PR in do_test_htlc_on_chain_timeout's updated comment about connect style scenarios ?

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.

Hmm, I generally just git blame and then look at the commit(s)/check github to see what PR it came from. We could add PR #s too, but I'm a bit hesitant to have the code explicitly reference the PR it was in. But if you feel strongly I can add it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well i'm a huge fan of good post-mortem tracking, kind of in the long-term of #573 :) Making a mental note to add to next agenda meeting as we have all differing opinions there.

Comment threadlightning/src/chain/onchaintx.rs
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
if let Some(h) = request.timer() {
if height >= h {
if cur_height >= h {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this change opening to the possibility of a duplicated broadcast ?

If you call first best_block_updated and bump best_block to N.
Then you call transaction_confirmed and register a request at block N - 3, you generate a first claim transaction.
Then you hit timer returning T >= H, you generate a second claim transaction.

I don't think because we call get_height_timer with cur_height in generate_claim_tx though I would be glad to have this potential interaction more highlighted.

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.

Hmmmm, should we swap the ordering of things in update_claims_view and first bump RBF transactions and only process the requests after that? I'm a bit hesitant to given it will probably mean a ton of test changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See above comment about height collision, I would favor this ordering swap to avoid re-broadcasting. I believe, post-anchor that kind of defaulting internal API issue might force us to reserve fee-bumping reserve for nothing ?

Though it's not related to this PR, and we can keep thinking about it.

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.

I'm a bit confused about that comment, but I'd think the solution is to track outputs which were on chain and spent and never re-spend them, instead of re-spending them after ANTI_REORG_DELAY.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah that would works too to blacklist confirmed utxos so i think it's more an issue on how duplicate block processing is interfering with update_claim_view() list of calls.

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.

Oh, hmm, I guess I just misread your above comment. Let me try the ordering swap in a new PR? I don't really want to hold this/0.0.99 up to redo a ton of tests.

Comment threadlightning/src/ln/channelmanager.rs Outdated
if self.broadcasted_holder_revokable_script.is_some() {
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this comment is a bit confusing, bumping timer is only function of current height and expiration timelock (see get_height_timer in package.rs, not of parent transaction conf. So leveraging the best known height as comparison point to refresh timers is the best we can do ?

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.

get_broadcasted_holder_claims sets the PackageTemplate::soonest_conf_deadline to the height given here, so it is the "expiration timelock".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes in this case parent conf height is equal to PackageTemplate::soonest_conf_deadline which is equal to "expiration timelock" but my point rather than relying on parent conf height, which have the side-effect of artificially increasing the fee-bump frequency and thus bleed feerate a bit, we should rely on the per-holder-claims timelocks themselves to fulfill soonest_conf_deadline.

Though I don't think this comment needs further action for now and sounds to be related just after.

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.

I'm still a bit confused here, but I'll leave it for a followup.

HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), height, false, height);
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is buggy, why we call build_package and initialize soonest_conf_deadline (i.e the timelock expiration height) to the confirmation or current height ? At least it's already what we do on master (c05347f), this PR doesn't seem to change anything...

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.

Yea, its "wrong", but only in the sense that there is no "right" answer. We can't RBF-bump the commitment transactions anyway, so its not broken, i don't think, and indeed its the same on master. I'd ideally prefer to clean it up in a separate PR, though I agree its a really poor API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well IMHO the change is as simple as this :

diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index b7f87883..9e6e64e4 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -1746,7 +1746,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
};
HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
- let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);
+ let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height);
claim_requests.push(htlc_package);
}
}

Though doesn't break any test on top of this branch, so likely need a test to demonstrate the fee-bumping policy change. Tracked as #982 and self-assigned. Note, it might duplicate htlc.cltv_expiry usage both at the package-level and at the output-level but i don't think that's a redundance as in the first case we would use for fee-bumping scheduling and in the second to generate the correct witnessScript ?

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! Duh, indeed, I was somehow confusing the htlc package for the commitment tx package. I updated it but agree we need more tests here.

self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're building a new commitment transaction, we should construct the package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing too, we never "build" new commitment transaction in the backend, at least we might regenerate one to ensure sanity but never build-and-sign a commitment transaction from scratch ?

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.

Hmm, semantics, but OK, I tweaked it.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch 2 times, most recently from 6a0dcd8 to 9abba15CompareJune 29, 2021 15:54
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9abba15 to 419e9faCompareJune 29, 2021 20:14
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of the logging dependency. No changes/squashes were done.

@jkczyzjkczyz left a comment

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.

Largely looks good though my comprehension of onchaintx.rs changes is lacking compared to @ariard.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
}

if height > self.best_block.height() {
self.best_block = BestBlock::new(block_hash, height);

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.

Should we log_trace here as we do in block_connected and best_block_updated? Regardless of which methods, should we log only when best_block is updated or (in particularly for the other two) unconditionally when the methods are called?

Taking a step back, it feels like some of this logging me be redundant across other monitors and may not provide much value. Maybe the logging should be placed in ChainMonitor instead? Then again, logging when the best block is actually updated could be valuable.

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.

Yea, I think there's a lot of room for improvement here, lets do it in a followup?

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.

SGTM

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notwithstanding already-existent issues mentioned in comment and we should address latter, this PR sounds good to me.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from e271d98 to 5e51f84CompareJuly 1, 2021 14:47
@ariard

ariard commented Jul 1, 2021

Copy link
Copy Markdown

Code Review ACK 5e51f84

Thanks for taking the latest suggestions!

@jkczyzjkczyz left a comment

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 further DRYs up some functional_test code and increases
coverage.
There are no visible effects of this, but it seems like good code
hygiene to not call a disconnect function in a different file if no
disconnect happened.
No matter the context, if we're told about a block which is
guaranteed by our API semantics to be on the best chain, and it has
a higher height than our current understanding of the best chain,
we should update our understanding. This avoids complexity
in `block_confirmed` by never having a height set which is *higher*
than our current best chain, potentially avoiding some bugs in the
rather-complicated code.
It also requires a minor test tweak as we in some cases now no
longer broadcast a conflicting transaction after the original has
reached the ANTI_REORG_DELAY.
We had a user who pointed out that we weren't creating
`SpendableOutputs` events when we should have been after they
called `ChannelMonitor::best_block_updated` with a block well
after a CSV locktime and then called
`ChannelMonitor::transactions_confirmed` with the transaction which
we should have been spending (with a block height/hash a ways in
the past).
This was due to `ChannelMonitor::transactions_confirmed` only
calling `ChannelMonitor::block_confirmed` with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.
Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.
To address this, we use the best block height for confirmation
threshold checks in `ChannelMonitor::block_confirmed` and pass both
the confirmation and current heights through to
`OnchainTx::update_claims_view`, using each as appropriate.
Fixeslightningdevkit#962.
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 5e51f84 to 412cc9fCompareJuly 2, 2021 17:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed with no diff, will take after CI:

$ git diff-tree -U1 5e51f84b 412cc9f0
$

@TheBlueMatt
TheBlueMatt merged commit 0c57018 into lightningdevkit:mainJul 2, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChannelMonitor::best_block_updated before ChannelMonitor::transactions_confirmed may needlessly delay broadcasting by a block

3 participants

@TheBlueMatt@ariard@jkczyz
, '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

Create SpendableOutputs events no matter the chain::Confirm order - #970

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay
Jul 2, 2021
Merged

Create SpendableOutputs events no matter the chain::Confirm order#970
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jun 25, 2021

Copy link
Copy Markdown
Collaborator

We had a user who pointed out that we weren't creating
SpendableOutputs events when we should have been after they
called ChannelMonitor::best_block_updated with a block well
after a CSV locktime and then called
ChannelMonitor::transactions_confirmed with the transaction which
we should have been spending (with a block height/hash a ways in
the past).

This was due to ChannelMonitor::transactions_confirmed only
calling ChannelMonitor::block_confirmed with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.

Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.

To address this, we use the best block height for confirmation
threshold checks in ChannelMonitor::block_confirmed and pass both
the confirmation and current heights through to
OnchainTx::update_claims_view, using each as appropriate.

Note to reviewers: please carefully check the update_claims_view part of the changeset here, there may very well be some unintended double-use of the height which wasn't properly separated (though I don't see any immediately).

Fixes#962.

@TheBlueMattTheBlueMatt added this to the 0.0.99 milestone Jun 25, 2021
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 7206c03 to 8216af0CompareJune 25, 2021 17:43
@codecov

codecovBot commented Jun 25, 2021

Copy link
Copy Markdown

Codecov Report

Merging #970 (5e51f84) into main (f472907) will increase coverage by 0.30%.
The diff coverage is 98.27%.

❗ Current head 5e51f84 differs from pull request most recent head 412cc9f. Consider uploading reports for the commit 412cc9f to get more accurate results
Impacted file tree graph

@@ Coverage Diff @@## main #970 +/- ##
==========================================
+ Coverage 90.63% 90.93% +0.30% 
==========================================
Files 60 60 Lines 30538 31896 +1358 ==========================================
+ Hits 27678 29005 +1327 - Misses 2860 2891 +31 
Impacted FilesCoverage Δ
lightning/src/ln/channel.rs90.43% <ø> (+1.92%)⬆️
lightning/src/ln/channelmanager.rs86.76% <ø> (+2.86%)⬆️
lightning/src/chain/onchaintx.rs94.19% <93.75%> (ø)
lightning/src/chain/channelmonitor.rs90.80% <95.65%> (+0.04%)⬆️
lightning/src/ln/functional_test_utils.rs94.83% <100.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.34% <100.00%> (+0.17%)⬆️
lightning/src/util/events.rs15.15% <0.00%> (-2.15%)⬇️
lightning/src/ln/chanmon_update_fail_tests.rs98.55% <0.00%> (+0.77%)⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f472907...412cc9f. Read the comment docs.

Comment threadlightning/src/ln/functional_test_utils.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Might make sense to update our interfaces (here and elsewhere) to take &BestBlock where that is expected to avoid accidentally passing a confirmation height. Or even directly use it rather than passing it at all where that is possible.

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.

Hmm, suppose we could, I thought renaming height to conf_height was pretty clear (though I could spell it out). For OnchainTx I could tweak it if you think it makes more sense (given it doesn't have its own best_block field)?

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.

Seems like there are a few scenarios:

  • it doesn't need to be passed at all because the method already can access it through self, so why bother passing it? (e.g., remove would_broadcast_at_height can be named simply should_broadcast and doesn't need to take a height, nor does get_broadcasted_holder_claims
  • only the current height is needed, so passing &BestBlock makes it explicit and prevents passing the wrong height (e.g., has_reached_confirmation_threshold)
  • both heights are needed in OnchainTx (maybe leave as is since there is a calculation involved at the call site)

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.

I believe get_broadcasted_holder_claims needs the confirmation height, or at least appears to given it passes it to PackageTx::build_package as height_original, though its API seems like a good opportunity for cleanup. Did the rest.

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

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, in that case its being called with reference to a new commitment transaction being broadcast, not one which is confirmed (new_holder_commitment_transaction), so I think that's correct. Somewhat confusingly, its also called with a 0-height in provide_payment_preimage in a similar context, which is "wrong", but AFAICT harmless.

Ultimately it doesn't really matter much for this case, I think, cause its passed through as height_original in PackageTemplate which is only used to figure out when we can merge packages (which we can never do for the HolderHTLCOutputs that are used here), and when we can broadcast (which we just use the CLTV which is fine).

I'll add some comments and fix the 0 value version.

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 8216af0 to 642f5afCompareJune 25, 2021 19:29
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

Comment on lines +2014 to +2015
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller *must* update
/// `self.best_block` before calling, and set `conf_height` to the height at which any new

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.

Is this right? It isn't the case when called by transactions_confirmed as of now.

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.

I don't think it matters, but its probably simpler/nicer for users to actually set it there, so I did that.

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.

Is this behavior required to fix the bug? Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block? Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

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.

Is this behavior required to fix the bug?

No.

Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block?

Yes, I believe so, at least in ChannelMonitor, dunno about ChannelManager.

Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

I don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

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 don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

It seems like surprising behavior. I called X expecting Y but instead Y + Z happened. I guess I don't see why not just drop the comment rather than altering the behavior if it isn't required to fix the bug. At very least it should be a separate commit stating the reason for the change, IMHO.

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.

It definitely feels "more correct" and should happen, IMO. I moved it to a new commit, let me know what you think of the commit docs and if it sufficiently justifies it.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/onchaintx.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from eaff313 to 9fcc9d2CompareJune 27, 2021 22:18
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Updated to address feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9fcc9d2 to 540bcdeCompareJune 28, 2021 16:38

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A lot of interactions to think about after a first parse.

self.is_paying_spendable_output(&tx, height, &logger);
}

if height > self.best_block.height() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe you can add message commit here : "This avoids complexity in block_confirmed by never having a tracked height set which is higher than our current best confirmed chain, potentially avoiding some bugs in the rather-complicated code" ?

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.

I put it in the doc comment of block_confirmed instead.

if node_txn.len() == 1 {
check_spends!(node_txn[0], chan_2.3);
} else {
assert_eq!(node_txn.len(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Honestly this test path which covers TransactionsFirstSkippingBlocks, the connection style updated by this comment, doesn't make sense to me.

From what I remember, I don't understand why updating best_block in transactions_confirmed instead of best_block_updated only doesn't generate a B's commitment transaction broadcast, they both should fulfill should_broadcast.

I need to look deeper on this...

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.

In the old case, a few blocks before we get here we remove the funding tx output from OnchainTx::claimable_outpoints (which was originally tracked via commitment_tx[0]'s confirmation). Because its not there we can re-add it and re-broadcast our own commitment transaction. In the new case, we skip ahead a few blocks and just call OnchainTx::update_claims_view once, which means only at the very end do we remove the funding tx output, after we've already given up broadcasting the commitment tx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because its not there we can re-add it and re-broadcast our own commitment transaction.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Adding the following diff

diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 7e616ef2..4e1c1c5d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -536,6 +536,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
// been aggregated in a single tx and claimed so atomically
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
for outpoint in request.outpoints() {
+ log_trace!(logger, "Removing claiming outpoint for {}", outpoint.txid);
self.claimable_outpoints.remove(&outpoint);
}
}

And obtaining the following log, confirm that we might remove a tracked output to re-add just after as a new request ? (see 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e)


TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor DynamicOutputP2WPKH 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4:2 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2085] HTLC 75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a failure update has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor StaticOutput 4ee8b85ba72a1a1beb201438e8ec8903ce0acf2e48161e5e61c9e331d048fa70:0 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 0 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1963] Block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 connected with 0 txn matched INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 413] Test if outpoint can be aggregated with expiration 206 against 218 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 615] Adding claiming input for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 616] Finalized transaction b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 ready to broadcast TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 444] Registering claiming request for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 448] Broadcasting onchain commitment tx with txid b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 

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.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Its not height, its the previous outpoint itself. The claimable_outpoints map is indexed by previous outpoint, and in this case we've already registered that we're tracking a spend of the same outpoint, so we don't add a new one. This seems correct to me, if race-y.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmmmm i still think it's a weird height-race because note in the log channelmonitor is processing twice height 206 ? First time we remove the commitment's outpoint from OnchainTxHandler.claimable_outpoints then at second processing we add a new request for the already-confirmed commitment transaction in OnchainTxHandler.claimable_outpoints as the redundant-claim replay protection L393 doesn't play out ?

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, I totally agree that is a (very minor) bug, but this PR just removes that bug depending on the connection style, so I don't think it needs to get fixed here :).

let mut onchain_events_reaching_threshold_conf = Vec::new();
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(height) {
if entry.has_reached_confirmation_threshold(self.best_block.height()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is the updated code path solving the issue describing in the commit. Maybe add PR number and one-line mentioning we had a bug here.

@TheBlueMattTheBlueMattJun 29, 2021

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.

There were two issues, this is one, tbh I don't recall exactly which line in update_claims_view solved the other one. In general we haven't historically updated our code with comments where there was a bug, at least as long as we have a test which will fail if we ever regress (which we do here). It feels weird to start now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right, I don't seep mentions of former bugs over a quick browse of channel.rs/channelmanager.rs though for QA purposes what do you think of tracking PR in do_test_htlc_on_chain_timeout's updated comment about connect style scenarios ?

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.

Hmm, I generally just git blame and then look at the commit(s)/check github to see what PR it came from. We could add PR #s too, but I'm a bit hesitant to have the code explicitly reference the PR it was in. But if you feel strongly I can add it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well i'm a huge fan of good post-mortem tracking, kind of in the long-term of #573 :) Making a mental note to add to next agenda meeting as we have all differing opinions there.

Comment threadlightning/src/chain/onchaintx.rs
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
if let Some(h) = request.timer() {
if height >= h {
if cur_height >= h {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this change opening to the possibility of a duplicated broadcast ?

If you call first best_block_updated and bump best_block to N.
Then you call transaction_confirmed and register a request at block N - 3, you generate a first claim transaction.
Then you hit timer returning T >= H, you generate a second claim transaction.

I don't think because we call get_height_timer with cur_height in generate_claim_tx though I would be glad to have this potential interaction more highlighted.

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.

Hmmmm, should we swap the ordering of things in update_claims_view and first bump RBF transactions and only process the requests after that? I'm a bit hesitant to given it will probably mean a ton of test changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See above comment about height collision, I would favor this ordering swap to avoid re-broadcasting. I believe, post-anchor that kind of defaulting internal API issue might force us to reserve fee-bumping reserve for nothing ?

Though it's not related to this PR, and we can keep thinking about it.

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.

I'm a bit confused about that comment, but I'd think the solution is to track outputs which were on chain and spent and never re-spend them, instead of re-spending them after ANTI_REORG_DELAY.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah that would works too to blacklist confirmed utxos so i think it's more an issue on how duplicate block processing is interfering with update_claim_view() list of calls.

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.

Oh, hmm, I guess I just misread your above comment. Let me try the ordering swap in a new PR? I don't really want to hold this/0.0.99 up to redo a ton of tests.

Comment threadlightning/src/ln/channelmanager.rs Outdated
if self.broadcasted_holder_revokable_script.is_some() {
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this comment is a bit confusing, bumping timer is only function of current height and expiration timelock (see get_height_timer in package.rs, not of parent transaction conf. So leveraging the best known height as comparison point to refresh timers is the best we can do ?

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.

get_broadcasted_holder_claims sets the PackageTemplate::soonest_conf_deadline to the height given here, so it is the "expiration timelock".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes in this case parent conf height is equal to PackageTemplate::soonest_conf_deadline which is equal to "expiration timelock" but my point rather than relying on parent conf height, which have the side-effect of artificially increasing the fee-bump frequency and thus bleed feerate a bit, we should rely on the per-holder-claims timelocks themselves to fulfill soonest_conf_deadline.

Though I don't think this comment needs further action for now and sounds to be related just after.

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.

I'm still a bit confused here, but I'll leave it for a followup.

HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), height, false, height);
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is buggy, why we call build_package and initialize soonest_conf_deadline (i.e the timelock expiration height) to the confirmation or current height ? At least it's already what we do on master (c05347f), this PR doesn't seem to change anything...

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.

Yea, its "wrong", but only in the sense that there is no "right" answer. We can't RBF-bump the commitment transactions anyway, so its not broken, i don't think, and indeed its the same on master. I'd ideally prefer to clean it up in a separate PR, though I agree its a really poor API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well IMHO the change is as simple as this :

diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index b7f87883..9e6e64e4 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -1746,7 +1746,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
};
HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
- let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);
+ let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height);
claim_requests.push(htlc_package);
}
}

Though doesn't break any test on top of this branch, so likely need a test to demonstrate the fee-bumping policy change. Tracked as #982 and self-assigned. Note, it might duplicate htlc.cltv_expiry usage both at the package-level and at the output-level but i don't think that's a redundance as in the first case we would use for fee-bumping scheduling and in the second to generate the correct witnessScript ?

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! Duh, indeed, I was somehow confusing the htlc package for the commitment tx package. I updated it but agree we need more tests here.

self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're building a new commitment transaction, we should construct the package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing too, we never "build" new commitment transaction in the backend, at least we might regenerate one to ensure sanity but never build-and-sign a commitment transaction from scratch ?

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.

Hmm, semantics, but OK, I tweaked it.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch 2 times, most recently from 6a0dcd8 to 9abba15CompareJune 29, 2021 15:54
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9abba15 to 419e9faCompareJune 29, 2021 20:14
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of the logging dependency. No changes/squashes were done.

@jkczyzjkczyz left a comment

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.

Largely looks good though my comprehension of onchaintx.rs changes is lacking compared to @ariard.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
}

if height > self.best_block.height() {
self.best_block = BestBlock::new(block_hash, height);

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.

Should we log_trace here as we do in block_connected and best_block_updated? Regardless of which methods, should we log only when best_block is updated or (in particularly for the other two) unconditionally when the methods are called?

Taking a step back, it feels like some of this logging me be redundant across other monitors and may not provide much value. Maybe the logging should be placed in ChainMonitor instead? Then again, logging when the best block is actually updated could be valuable.

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.

Yea, I think there's a lot of room for improvement here, lets do it in a followup?

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.

SGTM

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notwithstanding already-existent issues mentioned in comment and we should address latter, this PR sounds good to me.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from e271d98 to 5e51f84CompareJuly 1, 2021 14:47
@ariard

ariard commented Jul 1, 2021

Copy link
Copy Markdown

Code Review ACK 5e51f84

Thanks for taking the latest suggestions!

@jkczyzjkczyz left a comment

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 further DRYs up some functional_test code and increases
coverage.
There are no visible effects of this, but it seems like good code
hygiene to not call a disconnect function in a different file if no
disconnect happened.
No matter the context, if we're told about a block which is
guaranteed by our API semantics to be on the best chain, and it has
a higher height than our current understanding of the best chain,
we should update our understanding. This avoids complexity
in `block_confirmed` by never having a height set which is *higher*
than our current best chain, potentially avoiding some bugs in the
rather-complicated code.
It also requires a minor test tweak as we in some cases now no
longer broadcast a conflicting transaction after the original has
reached the ANTI_REORG_DELAY.
We had a user who pointed out that we weren't creating
`SpendableOutputs` events when we should have been after they
called `ChannelMonitor::best_block_updated` with a block well
after a CSV locktime and then called
`ChannelMonitor::transactions_confirmed` with the transaction which
we should have been spending (with a block height/hash a ways in
the past).
This was due to `ChannelMonitor::transactions_confirmed` only
calling `ChannelMonitor::block_confirmed` with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.
Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.
To address this, we use the best block height for confirmation
threshold checks in `ChannelMonitor::block_confirmed` and pass both
the confirmation and current heights through to
`OnchainTx::update_claims_view`, using each as appropriate.
Fixeslightningdevkit#962.
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 5e51f84 to 412cc9fCompareJuly 2, 2021 17:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed with no diff, will take after CI:

$ git diff-tree -U1 5e51f84b 412cc9f0
$

@TheBlueMatt
TheBlueMatt merged commit 0c57018 into lightningdevkit:mainJul 2, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChannelMonitor::best_block_updated before ChannelMonitor::transactions_confirmed may needlessly delay broadcasting by a block

3 participants

@TheBlueMatt@ariard@jkczyz
, '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

Create SpendableOutputs events no matter the chain::Confirm order - #970

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay
Jul 2, 2021
Merged

Create SpendableOutputs events no matter the chain::Confirm order#970
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jun 25, 2021

Copy link
Copy Markdown
Collaborator

We had a user who pointed out that we weren't creating
SpendableOutputs events when we should have been after they
called ChannelMonitor::best_block_updated with a block well
after a CSV locktime and then called
ChannelMonitor::transactions_confirmed with the transaction which
we should have been spending (with a block height/hash a ways in
the past).

This was due to ChannelMonitor::transactions_confirmed only
calling ChannelMonitor::block_confirmed with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.

Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.

To address this, we use the best block height for confirmation
threshold checks in ChannelMonitor::block_confirmed and pass both
the confirmation and current heights through to
OnchainTx::update_claims_view, using each as appropriate.

Note to reviewers: please carefully check the update_claims_view part of the changeset here, there may very well be some unintended double-use of the height which wasn't properly separated (though I don't see any immediately).

Fixes#962.

@TheBlueMattTheBlueMatt added this to the 0.0.99 milestone Jun 25, 2021
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 7206c03 to 8216af0CompareJune 25, 2021 17:43
@codecov

codecovBot commented Jun 25, 2021

Copy link
Copy Markdown

Codecov Report

Merging #970 (5e51f84) into main (f472907) will increase coverage by 0.30%.
The diff coverage is 98.27%.

❗ Current head 5e51f84 differs from pull request most recent head 412cc9f. Consider uploading reports for the commit 412cc9f to get more accurate results
Impacted file tree graph

@@ Coverage Diff @@## main #970 +/- ##
==========================================
+ Coverage 90.63% 90.93% +0.30% 
==========================================
Files 60 60 Lines 30538 31896 +1358 ==========================================
+ Hits 27678 29005 +1327 - Misses 2860 2891 +31 
Impacted FilesCoverage Δ
lightning/src/ln/channel.rs90.43% <ø> (+1.92%)⬆️
lightning/src/ln/channelmanager.rs86.76% <ø> (+2.86%)⬆️
lightning/src/chain/onchaintx.rs94.19% <93.75%> (ø)
lightning/src/chain/channelmonitor.rs90.80% <95.65%> (+0.04%)⬆️
lightning/src/ln/functional_test_utils.rs94.83% <100.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.34% <100.00%> (+0.17%)⬆️
lightning/src/util/events.rs15.15% <0.00%> (-2.15%)⬇️
lightning/src/ln/chanmon_update_fail_tests.rs98.55% <0.00%> (+0.77%)⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f472907...412cc9f. Read the comment docs.

Comment threadlightning/src/ln/functional_test_utils.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Might make sense to update our interfaces (here and elsewhere) to take &BestBlock where that is expected to avoid accidentally passing a confirmation height. Or even directly use it rather than passing it at all where that is possible.

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.

Hmm, suppose we could, I thought renaming height to conf_height was pretty clear (though I could spell it out). For OnchainTx I could tweak it if you think it makes more sense (given it doesn't have its own best_block field)?

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.

Seems like there are a few scenarios:

  • it doesn't need to be passed at all because the method already can access it through self, so why bother passing it? (e.g., remove would_broadcast_at_height can be named simply should_broadcast and doesn't need to take a height, nor does get_broadcasted_holder_claims
  • only the current height is needed, so passing &BestBlock makes it explicit and prevents passing the wrong height (e.g., has_reached_confirmation_threshold)
  • both heights are needed in OnchainTx (maybe leave as is since there is a calculation involved at the call site)

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.

I believe get_broadcasted_holder_claims needs the confirmation height, or at least appears to given it passes it to PackageTx::build_package as height_original, though its API seems like a good opportunity for cleanup. Did the rest.

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

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, in that case its being called with reference to a new commitment transaction being broadcast, not one which is confirmed (new_holder_commitment_transaction), so I think that's correct. Somewhat confusingly, its also called with a 0-height in provide_payment_preimage in a similar context, which is "wrong", but AFAICT harmless.

Ultimately it doesn't really matter much for this case, I think, cause its passed through as height_original in PackageTemplate which is only used to figure out when we can merge packages (which we can never do for the HolderHTLCOutputs that are used here), and when we can broadcast (which we just use the CLTV which is fine).

I'll add some comments and fix the 0 value version.

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 8216af0 to 642f5afCompareJune 25, 2021 19:29
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

Comment on lines +2014 to +2015
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller *must* update
/// `self.best_block` before calling, and set `conf_height` to the height at which any new

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.

Is this right? It isn't the case when called by transactions_confirmed as of now.

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.

I don't think it matters, but its probably simpler/nicer for users to actually set it there, so I did that.

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.

Is this behavior required to fix the bug? Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block? Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

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.

Is this behavior required to fix the bug?

No.

Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block?

Yes, I believe so, at least in ChannelMonitor, dunno about ChannelManager.

Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

I don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

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 don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

It seems like surprising behavior. I called X expecting Y but instead Y + Z happened. I guess I don't see why not just drop the comment rather than altering the behavior if it isn't required to fix the bug. At very least it should be a separate commit stating the reason for the change, IMHO.

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.

It definitely feels "more correct" and should happen, IMO. I moved it to a new commit, let me know what you think of the commit docs and if it sufficiently justifies it.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/onchaintx.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from eaff313 to 9fcc9d2CompareJune 27, 2021 22:18
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Updated to address feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9fcc9d2 to 540bcdeCompareJune 28, 2021 16:38

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A lot of interactions to think about after a first parse.

self.is_paying_spendable_output(&tx, height, &logger);
}

if height > self.best_block.height() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe you can add message commit here : "This avoids complexity in block_confirmed by never having a tracked height set which is higher than our current best confirmed chain, potentially avoiding some bugs in the rather-complicated code" ?

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.

I put it in the doc comment of block_confirmed instead.

if node_txn.len() == 1 {
check_spends!(node_txn[0], chan_2.3);
} else {
assert_eq!(node_txn.len(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Honestly this test path which covers TransactionsFirstSkippingBlocks, the connection style updated by this comment, doesn't make sense to me.

From what I remember, I don't understand why updating best_block in transactions_confirmed instead of best_block_updated only doesn't generate a B's commitment transaction broadcast, they both should fulfill should_broadcast.

I need to look deeper on this...

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.

In the old case, a few blocks before we get here we remove the funding tx output from OnchainTx::claimable_outpoints (which was originally tracked via commitment_tx[0]'s confirmation). Because its not there we can re-add it and re-broadcast our own commitment transaction. In the new case, we skip ahead a few blocks and just call OnchainTx::update_claims_view once, which means only at the very end do we remove the funding tx output, after we've already given up broadcasting the commitment tx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because its not there we can re-add it and re-broadcast our own commitment transaction.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Adding the following diff

diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 7e616ef2..4e1c1c5d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -536,6 +536,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
// been aggregated in a single tx and claimed so atomically
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
for outpoint in request.outpoints() {
+ log_trace!(logger, "Removing claiming outpoint for {}", outpoint.txid);
self.claimable_outpoints.remove(&outpoint);
}
}

And obtaining the following log, confirm that we might remove a tracked output to re-add just after as a new request ? (see 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e)


TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor DynamicOutputP2WPKH 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4:2 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2085] HTLC 75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a failure update has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor StaticOutput 4ee8b85ba72a1a1beb201438e8ec8903ce0acf2e48161e5e61c9e331d048fa70:0 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 0 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1963] Block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 connected with 0 txn matched INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 413] Test if outpoint can be aggregated with expiration 206 against 218 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 615] Adding claiming input for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 616] Finalized transaction b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 ready to broadcast TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 444] Registering claiming request for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 448] Broadcasting onchain commitment tx with txid b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 

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.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Its not height, its the previous outpoint itself. The claimable_outpoints map is indexed by previous outpoint, and in this case we've already registered that we're tracking a spend of the same outpoint, so we don't add a new one. This seems correct to me, if race-y.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmmmm i still think it's a weird height-race because note in the log channelmonitor is processing twice height 206 ? First time we remove the commitment's outpoint from OnchainTxHandler.claimable_outpoints then at second processing we add a new request for the already-confirmed commitment transaction in OnchainTxHandler.claimable_outpoints as the redundant-claim replay protection L393 doesn't play out ?

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, I totally agree that is a (very minor) bug, but this PR just removes that bug depending on the connection style, so I don't think it needs to get fixed here :).

let mut onchain_events_reaching_threshold_conf = Vec::new();
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(height) {
if entry.has_reached_confirmation_threshold(self.best_block.height()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is the updated code path solving the issue describing in the commit. Maybe add PR number and one-line mentioning we had a bug here.

@TheBlueMattTheBlueMattJun 29, 2021

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.

There were two issues, this is one, tbh I don't recall exactly which line in update_claims_view solved the other one. In general we haven't historically updated our code with comments where there was a bug, at least as long as we have a test which will fail if we ever regress (which we do here). It feels weird to start now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right, I don't seep mentions of former bugs over a quick browse of channel.rs/channelmanager.rs though for QA purposes what do you think of tracking PR in do_test_htlc_on_chain_timeout's updated comment about connect style scenarios ?

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.

Hmm, I generally just git blame and then look at the commit(s)/check github to see what PR it came from. We could add PR #s too, but I'm a bit hesitant to have the code explicitly reference the PR it was in. But if you feel strongly I can add it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well i'm a huge fan of good post-mortem tracking, kind of in the long-term of #573 :) Making a mental note to add to next agenda meeting as we have all differing opinions there.

Comment threadlightning/src/chain/onchaintx.rs
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
if let Some(h) = request.timer() {
if height >= h {
if cur_height >= h {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this change opening to the possibility of a duplicated broadcast ?

If you call first best_block_updated and bump best_block to N.
Then you call transaction_confirmed and register a request at block N - 3, you generate a first claim transaction.
Then you hit timer returning T >= H, you generate a second claim transaction.

I don't think because we call get_height_timer with cur_height in generate_claim_tx though I would be glad to have this potential interaction more highlighted.

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.

Hmmmm, should we swap the ordering of things in update_claims_view and first bump RBF transactions and only process the requests after that? I'm a bit hesitant to given it will probably mean a ton of test changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See above comment about height collision, I would favor this ordering swap to avoid re-broadcasting. I believe, post-anchor that kind of defaulting internal API issue might force us to reserve fee-bumping reserve for nothing ?

Though it's not related to this PR, and we can keep thinking about it.

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.

I'm a bit confused about that comment, but I'd think the solution is to track outputs which were on chain and spent and never re-spend them, instead of re-spending them after ANTI_REORG_DELAY.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah that would works too to blacklist confirmed utxos so i think it's more an issue on how duplicate block processing is interfering with update_claim_view() list of calls.

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.

Oh, hmm, I guess I just misread your above comment. Let me try the ordering swap in a new PR? I don't really want to hold this/0.0.99 up to redo a ton of tests.

Comment threadlightning/src/ln/channelmanager.rs Outdated
if self.broadcasted_holder_revokable_script.is_some() {
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this comment is a bit confusing, bumping timer is only function of current height and expiration timelock (see get_height_timer in package.rs, not of parent transaction conf. So leveraging the best known height as comparison point to refresh timers is the best we can do ?

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.

get_broadcasted_holder_claims sets the PackageTemplate::soonest_conf_deadline to the height given here, so it is the "expiration timelock".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes in this case parent conf height is equal to PackageTemplate::soonest_conf_deadline which is equal to "expiration timelock" but my point rather than relying on parent conf height, which have the side-effect of artificially increasing the fee-bump frequency and thus bleed feerate a bit, we should rely on the per-holder-claims timelocks themselves to fulfill soonest_conf_deadline.

Though I don't think this comment needs further action for now and sounds to be related just after.

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.

I'm still a bit confused here, but I'll leave it for a followup.

HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), height, false, height);
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is buggy, why we call build_package and initialize soonest_conf_deadline (i.e the timelock expiration height) to the confirmation or current height ? At least it's already what we do on master (c05347f), this PR doesn't seem to change anything...

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.

Yea, its "wrong", but only in the sense that there is no "right" answer. We can't RBF-bump the commitment transactions anyway, so its not broken, i don't think, and indeed its the same on master. I'd ideally prefer to clean it up in a separate PR, though I agree its a really poor API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well IMHO the change is as simple as this :

diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index b7f87883..9e6e64e4 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -1746,7 +1746,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
};
HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
- let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);
+ let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height);
claim_requests.push(htlc_package);
}
}

Though doesn't break any test on top of this branch, so likely need a test to demonstrate the fee-bumping policy change. Tracked as #982 and self-assigned. Note, it might duplicate htlc.cltv_expiry usage both at the package-level and at the output-level but i don't think that's a redundance as in the first case we would use for fee-bumping scheduling and in the second to generate the correct witnessScript ?

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! Duh, indeed, I was somehow confusing the htlc package for the commitment tx package. I updated it but agree we need more tests here.

self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're building a new commitment transaction, we should construct the package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing too, we never "build" new commitment transaction in the backend, at least we might regenerate one to ensure sanity but never build-and-sign a commitment transaction from scratch ?

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.

Hmm, semantics, but OK, I tweaked it.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch 2 times, most recently from 6a0dcd8 to 9abba15CompareJune 29, 2021 15:54
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9abba15 to 419e9faCompareJune 29, 2021 20:14
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of the logging dependency. No changes/squashes were done.

@jkczyzjkczyz left a comment

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.

Largely looks good though my comprehension of onchaintx.rs changes is lacking compared to @ariard.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
}

if height > self.best_block.height() {
self.best_block = BestBlock::new(block_hash, height);

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.

Should we log_trace here as we do in block_connected and best_block_updated? Regardless of which methods, should we log only when best_block is updated or (in particularly for the other two) unconditionally when the methods are called?

Taking a step back, it feels like some of this logging me be redundant across other monitors and may not provide much value. Maybe the logging should be placed in ChainMonitor instead? Then again, logging when the best block is actually updated could be valuable.

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.

Yea, I think there's a lot of room for improvement here, lets do it in a followup?

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.

SGTM

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notwithstanding already-existent issues mentioned in comment and we should address latter, this PR sounds good to me.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from e271d98 to 5e51f84CompareJuly 1, 2021 14:47
@ariard

ariard commented Jul 1, 2021

Copy link
Copy Markdown

Code Review ACK 5e51f84

Thanks for taking the latest suggestions!

@jkczyzjkczyz left a comment

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 further DRYs up some functional_test code and increases
coverage.
There are no visible effects of this, but it seems like good code
hygiene to not call a disconnect function in a different file if no
disconnect happened.
No matter the context, if we're told about a block which is
guaranteed by our API semantics to be on the best chain, and it has
a higher height than our current understanding of the best chain,
we should update our understanding. This avoids complexity
in `block_confirmed` by never having a height set which is *higher*
than our current best chain, potentially avoiding some bugs in the
rather-complicated code.
It also requires a minor test tweak as we in some cases now no
longer broadcast a conflicting transaction after the original has
reached the ANTI_REORG_DELAY.
We had a user who pointed out that we weren't creating
`SpendableOutputs` events when we should have been after they
called `ChannelMonitor::best_block_updated` with a block well
after a CSV locktime and then called
`ChannelMonitor::transactions_confirmed` with the transaction which
we should have been spending (with a block height/hash a ways in
the past).
This was due to `ChannelMonitor::transactions_confirmed` only
calling `ChannelMonitor::block_confirmed` with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.
Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.
To address this, we use the best block height for confirmation
threshold checks in `ChannelMonitor::block_confirmed` and pass both
the confirmation and current heights through to
`OnchainTx::update_claims_view`, using each as appropriate.
Fixeslightningdevkit#962.
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 5e51f84 to 412cc9fCompareJuly 2, 2021 17:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed with no diff, will take after CI:

$ git diff-tree -U1 5e51f84b 412cc9f0
$

@TheBlueMatt
TheBlueMatt merged commit 0c57018 into lightningdevkit:mainJul 2, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChannelMonitor::best_block_updated before ChannelMonitor::transactions_confirmed may needlessly delay broadcasting by a block

3 participants

@TheBlueMatt@ariard@jkczyz
, '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

Create SpendableOutputs events no matter the chain::Confirm order - #970

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay
Jul 2, 2021
Merged

Create SpendableOutputs events no matter the chain::Confirm order#970
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jun 25, 2021

Copy link
Copy Markdown
Collaborator

We had a user who pointed out that we weren't creating
SpendableOutputs events when we should have been after they
called ChannelMonitor::best_block_updated with a block well
after a CSV locktime and then called
ChannelMonitor::transactions_confirmed with the transaction which
we should have been spending (with a block height/hash a ways in
the past).

This was due to ChannelMonitor::transactions_confirmed only
calling ChannelMonitor::block_confirmed with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.

Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.

To address this, we use the best block height for confirmation
threshold checks in ChannelMonitor::block_confirmed and pass both
the confirmation and current heights through to
OnchainTx::update_claims_view, using each as appropriate.

Note to reviewers: please carefully check the update_claims_view part of the changeset here, there may very well be some unintended double-use of the height which wasn't properly separated (though I don't see any immediately).

Fixes#962.

@TheBlueMattTheBlueMatt added this to the 0.0.99 milestone Jun 25, 2021
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 7206c03 to 8216af0CompareJune 25, 2021 17:43
@codecov

codecovBot commented Jun 25, 2021

Copy link
Copy Markdown

Codecov Report

Merging #970 (5e51f84) into main (f472907) will increase coverage by 0.30%.
The diff coverage is 98.27%.

❗ Current head 5e51f84 differs from pull request most recent head 412cc9f. Consider uploading reports for the commit 412cc9f to get more accurate results
Impacted file tree graph

@@ Coverage Diff @@## main #970 +/- ##
==========================================
+ Coverage 90.63% 90.93% +0.30% 
==========================================
Files 60 60 Lines 30538 31896 +1358 ==========================================
+ Hits 27678 29005 +1327 - Misses 2860 2891 +31 
Impacted FilesCoverage Δ
lightning/src/ln/channel.rs90.43% <ø> (+1.92%)⬆️
lightning/src/ln/channelmanager.rs86.76% <ø> (+2.86%)⬆️
lightning/src/chain/onchaintx.rs94.19% <93.75%> (ø)
lightning/src/chain/channelmonitor.rs90.80% <95.65%> (+0.04%)⬆️
lightning/src/ln/functional_test_utils.rs94.83% <100.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.34% <100.00%> (+0.17%)⬆️
lightning/src/util/events.rs15.15% <0.00%> (-2.15%)⬇️
lightning/src/ln/chanmon_update_fail_tests.rs98.55% <0.00%> (+0.77%)⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f472907...412cc9f. Read the comment docs.

Comment threadlightning/src/ln/functional_test_utils.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Might make sense to update our interfaces (here and elsewhere) to take &BestBlock where that is expected to avoid accidentally passing a confirmation height. Or even directly use it rather than passing it at all where that is possible.

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.

Hmm, suppose we could, I thought renaming height to conf_height was pretty clear (though I could spell it out). For OnchainTx I could tweak it if you think it makes more sense (given it doesn't have its own best_block field)?

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.

Seems like there are a few scenarios:

  • it doesn't need to be passed at all because the method already can access it through self, so why bother passing it? (e.g., remove would_broadcast_at_height can be named simply should_broadcast and doesn't need to take a height, nor does get_broadcasted_holder_claims
  • only the current height is needed, so passing &BestBlock makes it explicit and prevents passing the wrong height (e.g., has_reached_confirmation_threshold)
  • both heights are needed in OnchainTx (maybe leave as is since there is a calculation involved at the call site)

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.

I believe get_broadcasted_holder_claims needs the confirmation height, or at least appears to given it passes it to PackageTx::build_package as height_original, though its API seems like a good opportunity for cleanup. Did the rest.

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

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, in that case its being called with reference to a new commitment transaction being broadcast, not one which is confirmed (new_holder_commitment_transaction), so I think that's correct. Somewhat confusingly, its also called with a 0-height in provide_payment_preimage in a similar context, which is "wrong", but AFAICT harmless.

Ultimately it doesn't really matter much for this case, I think, cause its passed through as height_original in PackageTemplate which is only used to figure out when we can merge packages (which we can never do for the HolderHTLCOutputs that are used here), and when we can broadcast (which we just use the CLTV which is fine).

I'll add some comments and fix the 0 value version.

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 8216af0 to 642f5afCompareJune 25, 2021 19:29
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

Comment on lines +2014 to +2015
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller *must* update
/// `self.best_block` before calling, and set `conf_height` to the height at which any new

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.

Is this right? It isn't the case when called by transactions_confirmed as of now.

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.

I don't think it matters, but its probably simpler/nicer for users to actually set it there, so I did that.

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.

Is this behavior required to fix the bug? Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block? Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

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.

Is this behavior required to fix the bug?

No.

Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block?

Yes, I believe so, at least in ChannelMonitor, dunno about ChannelManager.

Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

I don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

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 don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

It seems like surprising behavior. I called X expecting Y but instead Y + Z happened. I guess I don't see why not just drop the comment rather than altering the behavior if it isn't required to fix the bug. At very least it should be a separate commit stating the reason for the change, IMHO.

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.

It definitely feels "more correct" and should happen, IMO. I moved it to a new commit, let me know what you think of the commit docs and if it sufficiently justifies it.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/onchaintx.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from eaff313 to 9fcc9d2CompareJune 27, 2021 22:18
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Updated to address feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9fcc9d2 to 540bcdeCompareJune 28, 2021 16:38

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A lot of interactions to think about after a first parse.

self.is_paying_spendable_output(&tx, height, &logger);
}

if height > self.best_block.height() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe you can add message commit here : "This avoids complexity in block_confirmed by never having a tracked height set which is higher than our current best confirmed chain, potentially avoiding some bugs in the rather-complicated code" ?

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.

I put it in the doc comment of block_confirmed instead.

if node_txn.len() == 1 {
check_spends!(node_txn[0], chan_2.3);
} else {
assert_eq!(node_txn.len(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Honestly this test path which covers TransactionsFirstSkippingBlocks, the connection style updated by this comment, doesn't make sense to me.

From what I remember, I don't understand why updating best_block in transactions_confirmed instead of best_block_updated only doesn't generate a B's commitment transaction broadcast, they both should fulfill should_broadcast.

I need to look deeper on this...

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.

In the old case, a few blocks before we get here we remove the funding tx output from OnchainTx::claimable_outpoints (which was originally tracked via commitment_tx[0]'s confirmation). Because its not there we can re-add it and re-broadcast our own commitment transaction. In the new case, we skip ahead a few blocks and just call OnchainTx::update_claims_view once, which means only at the very end do we remove the funding tx output, after we've already given up broadcasting the commitment tx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because its not there we can re-add it and re-broadcast our own commitment transaction.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Adding the following diff

diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 7e616ef2..4e1c1c5d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -536,6 +536,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
// been aggregated in a single tx and claimed so atomically
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
for outpoint in request.outpoints() {
+ log_trace!(logger, "Removing claiming outpoint for {}", outpoint.txid);
self.claimable_outpoints.remove(&outpoint);
}
}

And obtaining the following log, confirm that we might remove a tracked output to re-add just after as a new request ? (see 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e)


TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor DynamicOutputP2WPKH 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4:2 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2085] HTLC 75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a failure update has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor StaticOutput 4ee8b85ba72a1a1beb201438e8ec8903ce0acf2e48161e5e61c9e331d048fa70:0 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 0 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1963] Block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 connected with 0 txn matched INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 413] Test if outpoint can be aggregated with expiration 206 against 218 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 615] Adding claiming input for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 616] Finalized transaction b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 ready to broadcast TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 444] Registering claiming request for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 448] Broadcasting onchain commitment tx with txid b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 

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.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Its not height, its the previous outpoint itself. The claimable_outpoints map is indexed by previous outpoint, and in this case we've already registered that we're tracking a spend of the same outpoint, so we don't add a new one. This seems correct to me, if race-y.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmmmm i still think it's a weird height-race because note in the log channelmonitor is processing twice height 206 ? First time we remove the commitment's outpoint from OnchainTxHandler.claimable_outpoints then at second processing we add a new request for the already-confirmed commitment transaction in OnchainTxHandler.claimable_outpoints as the redundant-claim replay protection L393 doesn't play out ?

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, I totally agree that is a (very minor) bug, but this PR just removes that bug depending on the connection style, so I don't think it needs to get fixed here :).

let mut onchain_events_reaching_threshold_conf = Vec::new();
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(height) {
if entry.has_reached_confirmation_threshold(self.best_block.height()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is the updated code path solving the issue describing in the commit. Maybe add PR number and one-line mentioning we had a bug here.

@TheBlueMattTheBlueMattJun 29, 2021

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.

There were two issues, this is one, tbh I don't recall exactly which line in update_claims_view solved the other one. In general we haven't historically updated our code with comments where there was a bug, at least as long as we have a test which will fail if we ever regress (which we do here). It feels weird to start now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right, I don't seep mentions of former bugs over a quick browse of channel.rs/channelmanager.rs though for QA purposes what do you think of tracking PR in do_test_htlc_on_chain_timeout's updated comment about connect style scenarios ?

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.

Hmm, I generally just git blame and then look at the commit(s)/check github to see what PR it came from. We could add PR #s too, but I'm a bit hesitant to have the code explicitly reference the PR it was in. But if you feel strongly I can add it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well i'm a huge fan of good post-mortem tracking, kind of in the long-term of #573 :) Making a mental note to add to next agenda meeting as we have all differing opinions there.

Comment threadlightning/src/chain/onchaintx.rs
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
if let Some(h) = request.timer() {
if height >= h {
if cur_height >= h {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this change opening to the possibility of a duplicated broadcast ?

If you call first best_block_updated and bump best_block to N.
Then you call transaction_confirmed and register a request at block N - 3, you generate a first claim transaction.
Then you hit timer returning T >= H, you generate a second claim transaction.

I don't think because we call get_height_timer with cur_height in generate_claim_tx though I would be glad to have this potential interaction more highlighted.

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.

Hmmmm, should we swap the ordering of things in update_claims_view and first bump RBF transactions and only process the requests after that? I'm a bit hesitant to given it will probably mean a ton of test changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See above comment about height collision, I would favor this ordering swap to avoid re-broadcasting. I believe, post-anchor that kind of defaulting internal API issue might force us to reserve fee-bumping reserve for nothing ?

Though it's not related to this PR, and we can keep thinking about it.

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.

I'm a bit confused about that comment, but I'd think the solution is to track outputs which were on chain and spent and never re-spend them, instead of re-spending them after ANTI_REORG_DELAY.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah that would works too to blacklist confirmed utxos so i think it's more an issue on how duplicate block processing is interfering with update_claim_view() list of calls.

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.

Oh, hmm, I guess I just misread your above comment. Let me try the ordering swap in a new PR? I don't really want to hold this/0.0.99 up to redo a ton of tests.

Comment threadlightning/src/ln/channelmanager.rs Outdated
if self.broadcasted_holder_revokable_script.is_some() {
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this comment is a bit confusing, bumping timer is only function of current height and expiration timelock (see get_height_timer in package.rs, not of parent transaction conf. So leveraging the best known height as comparison point to refresh timers is the best we can do ?

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.

get_broadcasted_holder_claims sets the PackageTemplate::soonest_conf_deadline to the height given here, so it is the "expiration timelock".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes in this case parent conf height is equal to PackageTemplate::soonest_conf_deadline which is equal to "expiration timelock" but my point rather than relying on parent conf height, which have the side-effect of artificially increasing the fee-bump frequency and thus bleed feerate a bit, we should rely on the per-holder-claims timelocks themselves to fulfill soonest_conf_deadline.

Though I don't think this comment needs further action for now and sounds to be related just after.

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.

I'm still a bit confused here, but I'll leave it for a followup.

HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), height, false, height);
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is buggy, why we call build_package and initialize soonest_conf_deadline (i.e the timelock expiration height) to the confirmation or current height ? At least it's already what we do on master (c05347f), this PR doesn't seem to change anything...

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.

Yea, its "wrong", but only in the sense that there is no "right" answer. We can't RBF-bump the commitment transactions anyway, so its not broken, i don't think, and indeed its the same on master. I'd ideally prefer to clean it up in a separate PR, though I agree its a really poor API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well IMHO the change is as simple as this :

diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index b7f87883..9e6e64e4 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -1746,7 +1746,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
};
HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
- let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);
+ let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height);
claim_requests.push(htlc_package);
}
}

Though doesn't break any test on top of this branch, so likely need a test to demonstrate the fee-bumping policy change. Tracked as #982 and self-assigned. Note, it might duplicate htlc.cltv_expiry usage both at the package-level and at the output-level but i don't think that's a redundance as in the first case we would use for fee-bumping scheduling and in the second to generate the correct witnessScript ?

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! Duh, indeed, I was somehow confusing the htlc package for the commitment tx package. I updated it but agree we need more tests here.

self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're building a new commitment transaction, we should construct the package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing too, we never "build" new commitment transaction in the backend, at least we might regenerate one to ensure sanity but never build-and-sign a commitment transaction from scratch ?

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.

Hmm, semantics, but OK, I tweaked it.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch 2 times, most recently from 6a0dcd8 to 9abba15CompareJune 29, 2021 15:54
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9abba15 to 419e9faCompareJune 29, 2021 20:14
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of the logging dependency. No changes/squashes were done.

@jkczyzjkczyz left a comment

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.

Largely looks good though my comprehension of onchaintx.rs changes is lacking compared to @ariard.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
}

if height > self.best_block.height() {
self.best_block = BestBlock::new(block_hash, height);

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.

Should we log_trace here as we do in block_connected and best_block_updated? Regardless of which methods, should we log only when best_block is updated or (in particularly for the other two) unconditionally when the methods are called?

Taking a step back, it feels like some of this logging me be redundant across other monitors and may not provide much value. Maybe the logging should be placed in ChainMonitor instead? Then again, logging when the best block is actually updated could be valuable.

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.

Yea, I think there's a lot of room for improvement here, lets do it in a followup?

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.

SGTM

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notwithstanding already-existent issues mentioned in comment and we should address latter, this PR sounds good to me.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from e271d98 to 5e51f84CompareJuly 1, 2021 14:47
@ariard

ariard commented Jul 1, 2021

Copy link
Copy Markdown

Code Review ACK 5e51f84

Thanks for taking the latest suggestions!

@jkczyzjkczyz left a comment

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 further DRYs up some functional_test code and increases
coverage.
There are no visible effects of this, but it seems like good code
hygiene to not call a disconnect function in a different file if no
disconnect happened.
No matter the context, if we're told about a block which is
guaranteed by our API semantics to be on the best chain, and it has
a higher height than our current understanding of the best chain,
we should update our understanding. This avoids complexity
in `block_confirmed` by never having a height set which is *higher*
than our current best chain, potentially avoiding some bugs in the
rather-complicated code.
It also requires a minor test tweak as we in some cases now no
longer broadcast a conflicting transaction after the original has
reached the ANTI_REORG_DELAY.
We had a user who pointed out that we weren't creating
`SpendableOutputs` events when we should have been after they
called `ChannelMonitor::best_block_updated` with a block well
after a CSV locktime and then called
`ChannelMonitor::transactions_confirmed` with the transaction which
we should have been spending (with a block height/hash a ways in
the past).
This was due to `ChannelMonitor::transactions_confirmed` only
calling `ChannelMonitor::block_confirmed` with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.
Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.
To address this, we use the best block height for confirmation
threshold checks in `ChannelMonitor::block_confirmed` and pass both
the confirmation and current heights through to
`OnchainTx::update_claims_view`, using each as appropriate.
Fixeslightningdevkit#962.
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 5e51f84 to 412cc9fCompareJuly 2, 2021 17:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed with no diff, will take after CI:

$ git diff-tree -U1 5e51f84b 412cc9f0
$

@TheBlueMatt
TheBlueMatt merged commit 0c57018 into lightningdevkit:mainJul 2, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChannelMonitor::best_block_updated before ChannelMonitor::transactions_confirmed may needlessly delay broadcasting by a block

3 participants

@TheBlueMatt@ariard@jkczyz
, '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

Create SpendableOutputs events no matter the chain::Confirm order - #970

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay
Jul 2, 2021
Merged

Create SpendableOutputs events no matter the chain::Confirm order#970
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jun 25, 2021

Copy link
Copy Markdown
Collaborator

We had a user who pointed out that we weren't creating
SpendableOutputs events when we should have been after they
called ChannelMonitor::best_block_updated with a block well
after a CSV locktime and then called
ChannelMonitor::transactions_confirmed with the transaction which
we should have been spending (with a block height/hash a ways in
the past).

This was due to ChannelMonitor::transactions_confirmed only
calling ChannelMonitor::block_confirmed with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.

Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.

To address this, we use the best block height for confirmation
threshold checks in ChannelMonitor::block_confirmed and pass both
the confirmation and current heights through to
OnchainTx::update_claims_view, using each as appropriate.

Note to reviewers: please carefully check the update_claims_view part of the changeset here, there may very well be some unintended double-use of the height which wasn't properly separated (though I don't see any immediately).

Fixes#962.

@TheBlueMattTheBlueMatt added this to the 0.0.99 milestone Jun 25, 2021
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 7206c03 to 8216af0CompareJune 25, 2021 17:43
@codecov

codecovBot commented Jun 25, 2021

Copy link
Copy Markdown

Codecov Report

Merging #970 (5e51f84) into main (f472907) will increase coverage by 0.30%.
The diff coverage is 98.27%.

❗ Current head 5e51f84 differs from pull request most recent head 412cc9f. Consider uploading reports for the commit 412cc9f to get more accurate results
Impacted file tree graph

@@ Coverage Diff @@## main #970 +/- ##
==========================================
+ Coverage 90.63% 90.93% +0.30% 
==========================================
Files 60 60 Lines 30538 31896 +1358 ==========================================
+ Hits 27678 29005 +1327 - Misses 2860 2891 +31 
Impacted FilesCoverage Δ
lightning/src/ln/channel.rs90.43% <ø> (+1.92%)⬆️
lightning/src/ln/channelmanager.rs86.76% <ø> (+2.86%)⬆️
lightning/src/chain/onchaintx.rs94.19% <93.75%> (ø)
lightning/src/chain/channelmonitor.rs90.80% <95.65%> (+0.04%)⬆️
lightning/src/ln/functional_test_utils.rs94.83% <100.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.34% <100.00%> (+0.17%)⬆️
lightning/src/util/events.rs15.15% <0.00%> (-2.15%)⬇️
lightning/src/ln/chanmon_update_fail_tests.rs98.55% <0.00%> (+0.77%)⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f472907...412cc9f. Read the comment docs.

Comment threadlightning/src/ln/functional_test_utils.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Might make sense to update our interfaces (here and elsewhere) to take &BestBlock where that is expected to avoid accidentally passing a confirmation height. Or even directly use it rather than passing it at all where that is possible.

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.

Hmm, suppose we could, I thought renaming height to conf_height was pretty clear (though I could spell it out). For OnchainTx I could tweak it if you think it makes more sense (given it doesn't have its own best_block field)?

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.

Seems like there are a few scenarios:

  • it doesn't need to be passed at all because the method already can access it through self, so why bother passing it? (e.g., remove would_broadcast_at_height can be named simply should_broadcast and doesn't need to take a height, nor does get_broadcasted_holder_claims
  • only the current height is needed, so passing &BestBlock makes it explicit and prevents passing the wrong height (e.g., has_reached_confirmation_threshold)
  • both heights are needed in OnchainTx (maybe leave as is since there is a calculation involved at the call site)

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.

I believe get_broadcasted_holder_claims needs the confirmation height, or at least appears to given it passes it to PackageTx::build_package as height_original, though its API seems like a good opportunity for cleanup. Did the rest.

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

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, in that case its being called with reference to a new commitment transaction being broadcast, not one which is confirmed (new_holder_commitment_transaction), so I think that's correct. Somewhat confusingly, its also called with a 0-height in provide_payment_preimage in a similar context, which is "wrong", but AFAICT harmless.

Ultimately it doesn't really matter much for this case, I think, cause its passed through as height_original in PackageTemplate which is only used to figure out when we can merge packages (which we can never do for the HolderHTLCOutputs that are used here), and when we can broadcast (which we just use the CLTV which is fine).

I'll add some comments and fix the 0 value version.

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 8216af0 to 642f5afCompareJune 25, 2021 19:29
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

Comment on lines +2014 to +2015
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller *must* update
/// `self.best_block` before calling, and set `conf_height` to the height at which any new

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.

Is this right? It isn't the case when called by transactions_confirmed as of now.

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.

I don't think it matters, but its probably simpler/nicer for users to actually set it there, so I did that.

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.

Is this behavior required to fix the bug? Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block? Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

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.

Is this behavior required to fix the bug?

No.

Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block?

Yes, I believe so, at least in ChannelMonitor, dunno about ChannelManager.

Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

I don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

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 don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

It seems like surprising behavior. I called X expecting Y but instead Y + Z happened. I guess I don't see why not just drop the comment rather than altering the behavior if it isn't required to fix the bug. At very least it should be a separate commit stating the reason for the change, IMHO.

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.

It definitely feels "more correct" and should happen, IMO. I moved it to a new commit, let me know what you think of the commit docs and if it sufficiently justifies it.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/onchaintx.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from eaff313 to 9fcc9d2CompareJune 27, 2021 22:18
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Updated to address feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9fcc9d2 to 540bcdeCompareJune 28, 2021 16:38

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A lot of interactions to think about after a first parse.

self.is_paying_spendable_output(&tx, height, &logger);
}

if height > self.best_block.height() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe you can add message commit here : "This avoids complexity in block_confirmed by never having a tracked height set which is higher than our current best confirmed chain, potentially avoiding some bugs in the rather-complicated code" ?

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.

I put it in the doc comment of block_confirmed instead.

if node_txn.len() == 1 {
check_spends!(node_txn[0], chan_2.3);
} else {
assert_eq!(node_txn.len(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Honestly this test path which covers TransactionsFirstSkippingBlocks, the connection style updated by this comment, doesn't make sense to me.

From what I remember, I don't understand why updating best_block in transactions_confirmed instead of best_block_updated only doesn't generate a B's commitment transaction broadcast, they both should fulfill should_broadcast.

I need to look deeper on this...

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.

In the old case, a few blocks before we get here we remove the funding tx output from OnchainTx::claimable_outpoints (which was originally tracked via commitment_tx[0]'s confirmation). Because its not there we can re-add it and re-broadcast our own commitment transaction. In the new case, we skip ahead a few blocks and just call OnchainTx::update_claims_view once, which means only at the very end do we remove the funding tx output, after we've already given up broadcasting the commitment tx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because its not there we can re-add it and re-broadcast our own commitment transaction.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Adding the following diff

diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 7e616ef2..4e1c1c5d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -536,6 +536,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
// been aggregated in a single tx and claimed so atomically
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
for outpoint in request.outpoints() {
+ log_trace!(logger, "Removing claiming outpoint for {}", outpoint.txid);
self.claimable_outpoints.remove(&outpoint);
}
}

And obtaining the following log, confirm that we might remove a tracked output to re-add just after as a new request ? (see 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e)


TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor DynamicOutputP2WPKH 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4:2 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2085] HTLC 75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a failure update has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor StaticOutput 4ee8b85ba72a1a1beb201438e8ec8903ce0acf2e48161e5e61c9e331d048fa70:0 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 0 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1963] Block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 connected with 0 txn matched INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 413] Test if outpoint can be aggregated with expiration 206 against 218 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 615] Adding claiming input for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 616] Finalized transaction b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 ready to broadcast TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 444] Registering claiming request for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 448] Broadcasting onchain commitment tx with txid b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 

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.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Its not height, its the previous outpoint itself. The claimable_outpoints map is indexed by previous outpoint, and in this case we've already registered that we're tracking a spend of the same outpoint, so we don't add a new one. This seems correct to me, if race-y.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmmmm i still think it's a weird height-race because note in the log channelmonitor is processing twice height 206 ? First time we remove the commitment's outpoint from OnchainTxHandler.claimable_outpoints then at second processing we add a new request for the already-confirmed commitment transaction in OnchainTxHandler.claimable_outpoints as the redundant-claim replay protection L393 doesn't play out ?

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, I totally agree that is a (very minor) bug, but this PR just removes that bug depending on the connection style, so I don't think it needs to get fixed here :).

let mut onchain_events_reaching_threshold_conf = Vec::new();
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(height) {
if entry.has_reached_confirmation_threshold(self.best_block.height()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is the updated code path solving the issue describing in the commit. Maybe add PR number and one-line mentioning we had a bug here.

@TheBlueMattTheBlueMattJun 29, 2021

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.

There were two issues, this is one, tbh I don't recall exactly which line in update_claims_view solved the other one. In general we haven't historically updated our code with comments where there was a bug, at least as long as we have a test which will fail if we ever regress (which we do here). It feels weird to start now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right, I don't seep mentions of former bugs over a quick browse of channel.rs/channelmanager.rs though for QA purposes what do you think of tracking PR in do_test_htlc_on_chain_timeout's updated comment about connect style scenarios ?

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.

Hmm, I generally just git blame and then look at the commit(s)/check github to see what PR it came from. We could add PR #s too, but I'm a bit hesitant to have the code explicitly reference the PR it was in. But if you feel strongly I can add it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well i'm a huge fan of good post-mortem tracking, kind of in the long-term of #573 :) Making a mental note to add to next agenda meeting as we have all differing opinions there.

Comment threadlightning/src/chain/onchaintx.rs
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
if let Some(h) = request.timer() {
if height >= h {
if cur_height >= h {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this change opening to the possibility of a duplicated broadcast ?

If you call first best_block_updated and bump best_block to N.
Then you call transaction_confirmed and register a request at block N - 3, you generate a first claim transaction.
Then you hit timer returning T >= H, you generate a second claim transaction.

I don't think because we call get_height_timer with cur_height in generate_claim_tx though I would be glad to have this potential interaction more highlighted.

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.

Hmmmm, should we swap the ordering of things in update_claims_view and first bump RBF transactions and only process the requests after that? I'm a bit hesitant to given it will probably mean a ton of test changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See above comment about height collision, I would favor this ordering swap to avoid re-broadcasting. I believe, post-anchor that kind of defaulting internal API issue might force us to reserve fee-bumping reserve for nothing ?

Though it's not related to this PR, and we can keep thinking about it.

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.

I'm a bit confused about that comment, but I'd think the solution is to track outputs which were on chain and spent and never re-spend them, instead of re-spending them after ANTI_REORG_DELAY.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah that would works too to blacklist confirmed utxos so i think it's more an issue on how duplicate block processing is interfering with update_claim_view() list of calls.

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.

Oh, hmm, I guess I just misread your above comment. Let me try the ordering swap in a new PR? I don't really want to hold this/0.0.99 up to redo a ton of tests.

Comment threadlightning/src/ln/channelmanager.rs Outdated
if self.broadcasted_holder_revokable_script.is_some() {
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this comment is a bit confusing, bumping timer is only function of current height and expiration timelock (see get_height_timer in package.rs, not of parent transaction conf. So leveraging the best known height as comparison point to refresh timers is the best we can do ?

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.

get_broadcasted_holder_claims sets the PackageTemplate::soonest_conf_deadline to the height given here, so it is the "expiration timelock".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes in this case parent conf height is equal to PackageTemplate::soonest_conf_deadline which is equal to "expiration timelock" but my point rather than relying on parent conf height, which have the side-effect of artificially increasing the fee-bump frequency and thus bleed feerate a bit, we should rely on the per-holder-claims timelocks themselves to fulfill soonest_conf_deadline.

Though I don't think this comment needs further action for now and sounds to be related just after.

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.

I'm still a bit confused here, but I'll leave it for a followup.

HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), height, false, height);
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is buggy, why we call build_package and initialize soonest_conf_deadline (i.e the timelock expiration height) to the confirmation or current height ? At least it's already what we do on master (c05347f), this PR doesn't seem to change anything...

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.

Yea, its "wrong", but only in the sense that there is no "right" answer. We can't RBF-bump the commitment transactions anyway, so its not broken, i don't think, and indeed its the same on master. I'd ideally prefer to clean it up in a separate PR, though I agree its a really poor API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well IMHO the change is as simple as this :

diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index b7f87883..9e6e64e4 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -1746,7 +1746,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
};
HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
- let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);
+ let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height);
claim_requests.push(htlc_package);
}
}

Though doesn't break any test on top of this branch, so likely need a test to demonstrate the fee-bumping policy change. Tracked as #982 and self-assigned. Note, it might duplicate htlc.cltv_expiry usage both at the package-level and at the output-level but i don't think that's a redundance as in the first case we would use for fee-bumping scheduling and in the second to generate the correct witnessScript ?

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! Duh, indeed, I was somehow confusing the htlc package for the commitment tx package. I updated it but agree we need more tests here.

self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're building a new commitment transaction, we should construct the package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing too, we never "build" new commitment transaction in the backend, at least we might regenerate one to ensure sanity but never build-and-sign a commitment transaction from scratch ?

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.

Hmm, semantics, but OK, I tweaked it.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch 2 times, most recently from 6a0dcd8 to 9abba15CompareJune 29, 2021 15:54
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9abba15 to 419e9faCompareJune 29, 2021 20:14
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of the logging dependency. No changes/squashes were done.

@jkczyzjkczyz left a comment

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.

Largely looks good though my comprehension of onchaintx.rs changes is lacking compared to @ariard.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
}

if height > self.best_block.height() {
self.best_block = BestBlock::new(block_hash, height);

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.

Should we log_trace here as we do in block_connected and best_block_updated? Regardless of which methods, should we log only when best_block is updated or (in particularly for the other two) unconditionally when the methods are called?

Taking a step back, it feels like some of this logging me be redundant across other monitors and may not provide much value. Maybe the logging should be placed in ChainMonitor instead? Then again, logging when the best block is actually updated could be valuable.

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.

Yea, I think there's a lot of room for improvement here, lets do it in a followup?

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.

SGTM

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notwithstanding already-existent issues mentioned in comment and we should address latter, this PR sounds good to me.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from e271d98 to 5e51f84CompareJuly 1, 2021 14:47
@ariard

ariard commented Jul 1, 2021

Copy link
Copy Markdown

Code Review ACK 5e51f84

Thanks for taking the latest suggestions!

@jkczyzjkczyz left a comment

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 further DRYs up some functional_test code and increases
coverage.
There are no visible effects of this, but it seems like good code
hygiene to not call a disconnect function in a different file if no
disconnect happened.
No matter the context, if we're told about a block which is
guaranteed by our API semantics to be on the best chain, and it has
a higher height than our current understanding of the best chain,
we should update our understanding. This avoids complexity
in `block_confirmed` by never having a height set which is *higher*
than our current best chain, potentially avoiding some bugs in the
rather-complicated code.
It also requires a minor test tweak as we in some cases now no
longer broadcast a conflicting transaction after the original has
reached the ANTI_REORG_DELAY.
We had a user who pointed out that we weren't creating
`SpendableOutputs` events when we should have been after they
called `ChannelMonitor::best_block_updated` with a block well
after a CSV locktime and then called
`ChannelMonitor::transactions_confirmed` with the transaction which
we should have been spending (with a block height/hash a ways in
the past).
This was due to `ChannelMonitor::transactions_confirmed` only
calling `ChannelMonitor::block_confirmed` with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.
Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.
To address this, we use the best block height for confirmation
threshold checks in `ChannelMonitor::block_confirmed` and pass both
the confirmation and current heights through to
`OnchainTx::update_claims_view`, using each as appropriate.
Fixeslightningdevkit#962.
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 5e51f84 to 412cc9fCompareJuly 2, 2021 17:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed with no diff, will take after CI:

$ git diff-tree -U1 5e51f84b 412cc9f0
$

@TheBlueMatt
TheBlueMatt merged commit 0c57018 into lightningdevkit:mainJul 2, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChannelMonitor::best_block_updated before ChannelMonitor::transactions_confirmed may needlessly delay broadcasting by a block

3 participants

@TheBlueMatt@ariard@jkczyz
, '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

Create SpendableOutputs events no matter the chain::Confirm order - #970

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay
Jul 2, 2021
Merged

Create SpendableOutputs events no matter the chain::Confirm order#970
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jun 25, 2021

Copy link
Copy Markdown
Collaborator

We had a user who pointed out that we weren't creating
SpendableOutputs events when we should have been after they
called ChannelMonitor::best_block_updated with a block well
after a CSV locktime and then called
ChannelMonitor::transactions_confirmed with the transaction which
we should have been spending (with a block height/hash a ways in
the past).

This was due to ChannelMonitor::transactions_confirmed only
calling ChannelMonitor::block_confirmed with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.

Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.

To address this, we use the best block height for confirmation
threshold checks in ChannelMonitor::block_confirmed and pass both
the confirmation and current heights through to
OnchainTx::update_claims_view, using each as appropriate.

Note to reviewers: please carefully check the update_claims_view part of the changeset here, there may very well be some unintended double-use of the height which wasn't properly separated (though I don't see any immediately).

Fixes#962.

@TheBlueMattTheBlueMatt added this to the 0.0.99 milestone Jun 25, 2021
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 7206c03 to 8216af0CompareJune 25, 2021 17:43
@codecov

codecovBot commented Jun 25, 2021

Copy link
Copy Markdown

Codecov Report

Merging #970 (5e51f84) into main (f472907) will increase coverage by 0.30%.
The diff coverage is 98.27%.

❗ Current head 5e51f84 differs from pull request most recent head 412cc9f. Consider uploading reports for the commit 412cc9f to get more accurate results
Impacted file tree graph

@@ Coverage Diff @@## main #970 +/- ##
==========================================
+ Coverage 90.63% 90.93% +0.30% 
==========================================
Files 60 60 Lines 30538 31896 +1358 ==========================================
+ Hits 27678 29005 +1327 - Misses 2860 2891 +31 
Impacted FilesCoverage Δ
lightning/src/ln/channel.rs90.43% <ø> (+1.92%)⬆️
lightning/src/ln/channelmanager.rs86.76% <ø> (+2.86%)⬆️
lightning/src/chain/onchaintx.rs94.19% <93.75%> (ø)
lightning/src/chain/channelmonitor.rs90.80% <95.65%> (+0.04%)⬆️
lightning/src/ln/functional_test_utils.rs94.83% <100.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.34% <100.00%> (+0.17%)⬆️
lightning/src/util/events.rs15.15% <0.00%> (-2.15%)⬇️
lightning/src/ln/chanmon_update_fail_tests.rs98.55% <0.00%> (+0.77%)⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f472907...412cc9f. Read the comment docs.

Comment threadlightning/src/ln/functional_test_utils.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Might make sense to update our interfaces (here and elsewhere) to take &BestBlock where that is expected to avoid accidentally passing a confirmation height. Or even directly use it rather than passing it at all where that is possible.

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.

Hmm, suppose we could, I thought renaming height to conf_height was pretty clear (though I could spell it out). For OnchainTx I could tweak it if you think it makes more sense (given it doesn't have its own best_block field)?

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.

Seems like there are a few scenarios:

  • it doesn't need to be passed at all because the method already can access it through self, so why bother passing it? (e.g., remove would_broadcast_at_height can be named simply should_broadcast and doesn't need to take a height, nor does get_broadcasted_holder_claims
  • only the current height is needed, so passing &BestBlock makes it explicit and prevents passing the wrong height (e.g., has_reached_confirmation_threshold)
  • both heights are needed in OnchainTx (maybe leave as is since there is a calculation involved at the call site)

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.

I believe get_broadcasted_holder_claims needs the confirmation height, or at least appears to given it passes it to PackageTx::build_package as height_original, though its API seems like a good opportunity for cleanup. Did the rest.

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

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, in that case its being called with reference to a new commitment transaction being broadcast, not one which is confirmed (new_holder_commitment_transaction), so I think that's correct. Somewhat confusingly, its also called with a 0-height in provide_payment_preimage in a similar context, which is "wrong", but AFAICT harmless.

Ultimately it doesn't really matter much for this case, I think, cause its passed through as height_original in PackageTemplate which is only used to figure out when we can merge packages (which we can never do for the HolderHTLCOutputs that are used here), and when we can broadcast (which we just use the CLTV which is fine).

I'll add some comments and fix the 0 value version.

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 8216af0 to 642f5afCompareJune 25, 2021 19:29
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

Comment on lines +2014 to +2015
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller *must* update
/// `self.best_block` before calling, and set `conf_height` to the height at which any new

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.

Is this right? It isn't the case when called by transactions_confirmed as of now.

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.

I don't think it matters, but its probably simpler/nicer for users to actually set it there, so I did that.

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.

Is this behavior required to fix the bug? Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block? Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

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.

Is this behavior required to fix the bug?

No.

Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block?

Yes, I believe so, at least in ChannelMonitor, dunno about ChannelManager.

Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

I don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

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 don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

It seems like surprising behavior. I called X expecting Y but instead Y + Z happened. I guess I don't see why not just drop the comment rather than altering the behavior if it isn't required to fix the bug. At very least it should be a separate commit stating the reason for the change, IMHO.

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.

It definitely feels "more correct" and should happen, IMO. I moved it to a new commit, let me know what you think of the commit docs and if it sufficiently justifies it.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/onchaintx.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from eaff313 to 9fcc9d2CompareJune 27, 2021 22:18
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Updated to address feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9fcc9d2 to 540bcdeCompareJune 28, 2021 16:38

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A lot of interactions to think about after a first parse.

self.is_paying_spendable_output(&tx, height, &logger);
}

if height > self.best_block.height() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe you can add message commit here : "This avoids complexity in block_confirmed by never having a tracked height set which is higher than our current best confirmed chain, potentially avoiding some bugs in the rather-complicated code" ?

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.

I put it in the doc comment of block_confirmed instead.

if node_txn.len() == 1 {
check_spends!(node_txn[0], chan_2.3);
} else {
assert_eq!(node_txn.len(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Honestly this test path which covers TransactionsFirstSkippingBlocks, the connection style updated by this comment, doesn't make sense to me.

From what I remember, I don't understand why updating best_block in transactions_confirmed instead of best_block_updated only doesn't generate a B's commitment transaction broadcast, they both should fulfill should_broadcast.

I need to look deeper on this...

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.

In the old case, a few blocks before we get here we remove the funding tx output from OnchainTx::claimable_outpoints (which was originally tracked via commitment_tx[0]'s confirmation). Because its not there we can re-add it and re-broadcast our own commitment transaction. In the new case, we skip ahead a few blocks and just call OnchainTx::update_claims_view once, which means only at the very end do we remove the funding tx output, after we've already given up broadcasting the commitment tx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because its not there we can re-add it and re-broadcast our own commitment transaction.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Adding the following diff

diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 7e616ef2..4e1c1c5d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -536,6 +536,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
// been aggregated in a single tx and claimed so atomically
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
for outpoint in request.outpoints() {
+ log_trace!(logger, "Removing claiming outpoint for {}", outpoint.txid);
self.claimable_outpoints.remove(&outpoint);
}
}

And obtaining the following log, confirm that we might remove a tracked output to re-add just after as a new request ? (see 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e)


TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor DynamicOutputP2WPKH 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4:2 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2085] HTLC 75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a failure update has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor StaticOutput 4ee8b85ba72a1a1beb201438e8ec8903ce0acf2e48161e5e61c9e331d048fa70:0 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 0 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1963] Block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 connected with 0 txn matched INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 413] Test if outpoint can be aggregated with expiration 206 against 218 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 615] Adding claiming input for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 616] Finalized transaction b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 ready to broadcast TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 444] Registering claiming request for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 448] Broadcasting onchain commitment tx with txid b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 

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.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Its not height, its the previous outpoint itself. The claimable_outpoints map is indexed by previous outpoint, and in this case we've already registered that we're tracking a spend of the same outpoint, so we don't add a new one. This seems correct to me, if race-y.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmmmm i still think it's a weird height-race because note in the log channelmonitor is processing twice height 206 ? First time we remove the commitment's outpoint from OnchainTxHandler.claimable_outpoints then at second processing we add a new request for the already-confirmed commitment transaction in OnchainTxHandler.claimable_outpoints as the redundant-claim replay protection L393 doesn't play out ?

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, I totally agree that is a (very minor) bug, but this PR just removes that bug depending on the connection style, so I don't think it needs to get fixed here :).

let mut onchain_events_reaching_threshold_conf = Vec::new();
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(height) {
if entry.has_reached_confirmation_threshold(self.best_block.height()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is the updated code path solving the issue describing in the commit. Maybe add PR number and one-line mentioning we had a bug here.

@TheBlueMattTheBlueMattJun 29, 2021

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.

There were two issues, this is one, tbh I don't recall exactly which line in update_claims_view solved the other one. In general we haven't historically updated our code with comments where there was a bug, at least as long as we have a test which will fail if we ever regress (which we do here). It feels weird to start now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right, I don't seep mentions of former bugs over a quick browse of channel.rs/channelmanager.rs though for QA purposes what do you think of tracking PR in do_test_htlc_on_chain_timeout's updated comment about connect style scenarios ?

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.

Hmm, I generally just git blame and then look at the commit(s)/check github to see what PR it came from. We could add PR #s too, but I'm a bit hesitant to have the code explicitly reference the PR it was in. But if you feel strongly I can add it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well i'm a huge fan of good post-mortem tracking, kind of in the long-term of #573 :) Making a mental note to add to next agenda meeting as we have all differing opinions there.

Comment threadlightning/src/chain/onchaintx.rs
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
if let Some(h) = request.timer() {
if height >= h {
if cur_height >= h {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this change opening to the possibility of a duplicated broadcast ?

If you call first best_block_updated and bump best_block to N.
Then you call transaction_confirmed and register a request at block N - 3, you generate a first claim transaction.
Then you hit timer returning T >= H, you generate a second claim transaction.

I don't think because we call get_height_timer with cur_height in generate_claim_tx though I would be glad to have this potential interaction more highlighted.

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.

Hmmmm, should we swap the ordering of things in update_claims_view and first bump RBF transactions and only process the requests after that? I'm a bit hesitant to given it will probably mean a ton of test changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See above comment about height collision, I would favor this ordering swap to avoid re-broadcasting. I believe, post-anchor that kind of defaulting internal API issue might force us to reserve fee-bumping reserve for nothing ?

Though it's not related to this PR, and we can keep thinking about it.

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.

I'm a bit confused about that comment, but I'd think the solution is to track outputs which were on chain and spent and never re-spend them, instead of re-spending them after ANTI_REORG_DELAY.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah that would works too to blacklist confirmed utxos so i think it's more an issue on how duplicate block processing is interfering with update_claim_view() list of calls.

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.

Oh, hmm, I guess I just misread your above comment. Let me try the ordering swap in a new PR? I don't really want to hold this/0.0.99 up to redo a ton of tests.

Comment threadlightning/src/ln/channelmanager.rs Outdated
if self.broadcasted_holder_revokable_script.is_some() {
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this comment is a bit confusing, bumping timer is only function of current height and expiration timelock (see get_height_timer in package.rs, not of parent transaction conf. So leveraging the best known height as comparison point to refresh timers is the best we can do ?

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.

get_broadcasted_holder_claims sets the PackageTemplate::soonest_conf_deadline to the height given here, so it is the "expiration timelock".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes in this case parent conf height is equal to PackageTemplate::soonest_conf_deadline which is equal to "expiration timelock" but my point rather than relying on parent conf height, which have the side-effect of artificially increasing the fee-bump frequency and thus bleed feerate a bit, we should rely on the per-holder-claims timelocks themselves to fulfill soonest_conf_deadline.

Though I don't think this comment needs further action for now and sounds to be related just after.

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.

I'm still a bit confused here, but I'll leave it for a followup.

HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), height, false, height);
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is buggy, why we call build_package and initialize soonest_conf_deadline (i.e the timelock expiration height) to the confirmation or current height ? At least it's already what we do on master (c05347f), this PR doesn't seem to change anything...

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.

Yea, its "wrong", but only in the sense that there is no "right" answer. We can't RBF-bump the commitment transactions anyway, so its not broken, i don't think, and indeed its the same on master. I'd ideally prefer to clean it up in a separate PR, though I agree its a really poor API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well IMHO the change is as simple as this :

diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index b7f87883..9e6e64e4 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -1746,7 +1746,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
};
HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
- let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);
+ let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height);
claim_requests.push(htlc_package);
}
}

Though doesn't break any test on top of this branch, so likely need a test to demonstrate the fee-bumping policy change. Tracked as #982 and self-assigned. Note, it might duplicate htlc.cltv_expiry usage both at the package-level and at the output-level but i don't think that's a redundance as in the first case we would use for fee-bumping scheduling and in the second to generate the correct witnessScript ?

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! Duh, indeed, I was somehow confusing the htlc package for the commitment tx package. I updated it but agree we need more tests here.

self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're building a new commitment transaction, we should construct the package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing too, we never "build" new commitment transaction in the backend, at least we might regenerate one to ensure sanity but never build-and-sign a commitment transaction from scratch ?

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.

Hmm, semantics, but OK, I tweaked it.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch 2 times, most recently from 6a0dcd8 to 9abba15CompareJune 29, 2021 15:54
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9abba15 to 419e9faCompareJune 29, 2021 20:14
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of the logging dependency. No changes/squashes were done.

@jkczyzjkczyz left a comment

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.

Largely looks good though my comprehension of onchaintx.rs changes is lacking compared to @ariard.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
}

if height > self.best_block.height() {
self.best_block = BestBlock::new(block_hash, height);

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.

Should we log_trace here as we do in block_connected and best_block_updated? Regardless of which methods, should we log only when best_block is updated or (in particularly for the other two) unconditionally when the methods are called?

Taking a step back, it feels like some of this logging me be redundant across other monitors and may not provide much value. Maybe the logging should be placed in ChainMonitor instead? Then again, logging when the best block is actually updated could be valuable.

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.

Yea, I think there's a lot of room for improvement here, lets do it in a followup?

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.

SGTM

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notwithstanding already-existent issues mentioned in comment and we should address latter, this PR sounds good to me.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from e271d98 to 5e51f84CompareJuly 1, 2021 14:47
@ariard

ariard commented Jul 1, 2021

Copy link
Copy Markdown

Code Review ACK 5e51f84

Thanks for taking the latest suggestions!

@jkczyzjkczyz left a comment

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 further DRYs up some functional_test code and increases
coverage.
There are no visible effects of this, but it seems like good code
hygiene to not call a disconnect function in a different file if no
disconnect happened.
No matter the context, if we're told about a block which is
guaranteed by our API semantics to be on the best chain, and it has
a higher height than our current understanding of the best chain,
we should update our understanding. This avoids complexity
in `block_confirmed` by never having a height set which is *higher*
than our current best chain, potentially avoiding some bugs in the
rather-complicated code.
It also requires a minor test tweak as we in some cases now no
longer broadcast a conflicting transaction after the original has
reached the ANTI_REORG_DELAY.
We had a user who pointed out that we weren't creating
`SpendableOutputs` events when we should have been after they
called `ChannelMonitor::best_block_updated` with a block well
after a CSV locktime and then called
`ChannelMonitor::transactions_confirmed` with the transaction which
we should have been spending (with a block height/hash a ways in
the past).
This was due to `ChannelMonitor::transactions_confirmed` only
calling `ChannelMonitor::block_confirmed` with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.
Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.
To address this, we use the best block height for confirmation
threshold checks in `ChannelMonitor::block_confirmed` and pass both
the confirmation and current heights through to
`OnchainTx::update_claims_view`, using each as appropriate.
Fixeslightningdevkit#962.
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 5e51f84 to 412cc9fCompareJuly 2, 2021 17:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed with no diff, will take after CI:

$ git diff-tree -U1 5e51f84b 412cc9f0
$

@TheBlueMatt
TheBlueMatt merged commit 0c57018 into lightningdevkit:mainJul 2, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChannelMonitor::best_block_updated before ChannelMonitor::transactions_confirmed may needlessly delay broadcasting by a block

3 participants

@TheBlueMatt@ariard@jkczyz
, '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

Create SpendableOutputs events no matter the chain::Confirm order - #970

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay
Jul 2, 2021
Merged

Create SpendableOutputs events no matter the chain::Confirm order#970
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jun 25, 2021

Copy link
Copy Markdown
Collaborator

We had a user who pointed out that we weren't creating
SpendableOutputs events when we should have been after they
called ChannelMonitor::best_block_updated with a block well
after a CSV locktime and then called
ChannelMonitor::transactions_confirmed with the transaction which
we should have been spending (with a block height/hash a ways in
the past).

This was due to ChannelMonitor::transactions_confirmed only
calling ChannelMonitor::block_confirmed with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.

Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.

To address this, we use the best block height for confirmation
threshold checks in ChannelMonitor::block_confirmed and pass both
the confirmation and current heights through to
OnchainTx::update_claims_view, using each as appropriate.

Note to reviewers: please carefully check the update_claims_view part of the changeset here, there may very well be some unintended double-use of the height which wasn't properly separated (though I don't see any immediately).

Fixes#962.

@TheBlueMattTheBlueMatt added this to the 0.0.99 milestone Jun 25, 2021
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 7206c03 to 8216af0CompareJune 25, 2021 17:43
@codecov

codecovBot commented Jun 25, 2021

Copy link
Copy Markdown

Codecov Report

Merging #970 (5e51f84) into main (f472907) will increase coverage by 0.30%.
The diff coverage is 98.27%.

❗ Current head 5e51f84 differs from pull request most recent head 412cc9f. Consider uploading reports for the commit 412cc9f to get more accurate results
Impacted file tree graph

@@ Coverage Diff @@## main #970 +/- ##
==========================================
+ Coverage 90.63% 90.93% +0.30% 
==========================================
Files 60 60 Lines 30538 31896 +1358 ==========================================
+ Hits 27678 29005 +1327 - Misses 2860 2891 +31 
Impacted FilesCoverage Δ
lightning/src/ln/channel.rs90.43% <ø> (+1.92%)⬆️
lightning/src/ln/channelmanager.rs86.76% <ø> (+2.86%)⬆️
lightning/src/chain/onchaintx.rs94.19% <93.75%> (ø)
lightning/src/chain/channelmonitor.rs90.80% <95.65%> (+0.04%)⬆️
lightning/src/ln/functional_test_utils.rs94.83% <100.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.34% <100.00%> (+0.17%)⬆️
lightning/src/util/events.rs15.15% <0.00%> (-2.15%)⬇️
lightning/src/ln/chanmon_update_fail_tests.rs98.55% <0.00%> (+0.77%)⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f472907...412cc9f. Read the comment docs.

Comment threadlightning/src/ln/functional_test_utils.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Might make sense to update our interfaces (here and elsewhere) to take &BestBlock where that is expected to avoid accidentally passing a confirmation height. Or even directly use it rather than passing it at all where that is possible.

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.

Hmm, suppose we could, I thought renaming height to conf_height was pretty clear (though I could spell it out). For OnchainTx I could tweak it if you think it makes more sense (given it doesn't have its own best_block field)?

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.

Seems like there are a few scenarios:

  • it doesn't need to be passed at all because the method already can access it through self, so why bother passing it? (e.g., remove would_broadcast_at_height can be named simply should_broadcast and doesn't need to take a height, nor does get_broadcasted_holder_claims
  • only the current height is needed, so passing &BestBlock makes it explicit and prevents passing the wrong height (e.g., has_reached_confirmation_threshold)
  • both heights are needed in OnchainTx (maybe leave as is since there is a calculation involved at the call site)

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.

I believe get_broadcasted_holder_claims needs the confirmation height, or at least appears to given it passes it to PackageTx::build_package as height_original, though its API seems like a good opportunity for cleanup. Did the rest.

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

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, in that case its being called with reference to a new commitment transaction being broadcast, not one which is confirmed (new_holder_commitment_transaction), so I think that's correct. Somewhat confusingly, its also called with a 0-height in provide_payment_preimage in a similar context, which is "wrong", but AFAICT harmless.

Ultimately it doesn't really matter much for this case, I think, cause its passed through as height_original in PackageTemplate which is only used to figure out when we can merge packages (which we can never do for the HolderHTLCOutputs that are used here), and when we can broadcast (which we just use the CLTV which is fine).

I'll add some comments and fix the 0 value version.

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 8216af0 to 642f5afCompareJune 25, 2021 19:29
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

Comment on lines +2014 to +2015
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller *must* update
/// `self.best_block` before calling, and set `conf_height` to the height at which any new

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.

Is this right? It isn't the case when called by transactions_confirmed as of now.

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.

I don't think it matters, but its probably simpler/nicer for users to actually set it there, so I did that.

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.

Is this behavior required to fix the bug? Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block? Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

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.

Is this behavior required to fix the bug?

No.

Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block?

Yes, I believe so, at least in ChannelMonitor, dunno about ChannelManager.

Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

I don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

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 don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

It seems like surprising behavior. I called X expecting Y but instead Y + Z happened. I guess I don't see why not just drop the comment rather than altering the behavior if it isn't required to fix the bug. At very least it should be a separate commit stating the reason for the change, IMHO.

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.

It definitely feels "more correct" and should happen, IMO. I moved it to a new commit, let me know what you think of the commit docs and if it sufficiently justifies it.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/onchaintx.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from eaff313 to 9fcc9d2CompareJune 27, 2021 22:18
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Updated to address feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9fcc9d2 to 540bcdeCompareJune 28, 2021 16:38

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A lot of interactions to think about after a first parse.

self.is_paying_spendable_output(&tx, height, &logger);
}

if height > self.best_block.height() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe you can add message commit here : "This avoids complexity in block_confirmed by never having a tracked height set which is higher than our current best confirmed chain, potentially avoiding some bugs in the rather-complicated code" ?

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.

I put it in the doc comment of block_confirmed instead.

if node_txn.len() == 1 {
check_spends!(node_txn[0], chan_2.3);
} else {
assert_eq!(node_txn.len(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Honestly this test path which covers TransactionsFirstSkippingBlocks, the connection style updated by this comment, doesn't make sense to me.

From what I remember, I don't understand why updating best_block in transactions_confirmed instead of best_block_updated only doesn't generate a B's commitment transaction broadcast, they both should fulfill should_broadcast.

I need to look deeper on this...

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.

In the old case, a few blocks before we get here we remove the funding tx output from OnchainTx::claimable_outpoints (which was originally tracked via commitment_tx[0]'s confirmation). Because its not there we can re-add it and re-broadcast our own commitment transaction. In the new case, we skip ahead a few blocks and just call OnchainTx::update_claims_view once, which means only at the very end do we remove the funding tx output, after we've already given up broadcasting the commitment tx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because its not there we can re-add it and re-broadcast our own commitment transaction.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Adding the following diff

diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 7e616ef2..4e1c1c5d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -536,6 +536,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
// been aggregated in a single tx and claimed so atomically
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
for outpoint in request.outpoints() {
+ log_trace!(logger, "Removing claiming outpoint for {}", outpoint.txid);
self.claimable_outpoints.remove(&outpoint);
}
}

And obtaining the following log, confirm that we might remove a tracked output to re-add just after as a new request ? (see 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e)


TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor DynamicOutputP2WPKH 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4:2 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2085] HTLC 75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a failure update has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor StaticOutput 4ee8b85ba72a1a1beb201438e8ec8903ce0acf2e48161e5e61c9e331d048fa70:0 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 0 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1963] Block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 connected with 0 txn matched INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 413] Test if outpoint can be aggregated with expiration 206 against 218 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 615] Adding claiming input for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 616] Finalized transaction b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 ready to broadcast TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 444] Registering claiming request for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 448] Broadcasting onchain commitment tx with txid b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 

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.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Its not height, its the previous outpoint itself. The claimable_outpoints map is indexed by previous outpoint, and in this case we've already registered that we're tracking a spend of the same outpoint, so we don't add a new one. This seems correct to me, if race-y.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmmmm i still think it's a weird height-race because note in the log channelmonitor is processing twice height 206 ? First time we remove the commitment's outpoint from OnchainTxHandler.claimable_outpoints then at second processing we add a new request for the already-confirmed commitment transaction in OnchainTxHandler.claimable_outpoints as the redundant-claim replay protection L393 doesn't play out ?

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, I totally agree that is a (very minor) bug, but this PR just removes that bug depending on the connection style, so I don't think it needs to get fixed here :).

let mut onchain_events_reaching_threshold_conf = Vec::new();
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(height) {
if entry.has_reached_confirmation_threshold(self.best_block.height()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is the updated code path solving the issue describing in the commit. Maybe add PR number and one-line mentioning we had a bug here.

@TheBlueMattTheBlueMattJun 29, 2021

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.

There were two issues, this is one, tbh I don't recall exactly which line in update_claims_view solved the other one. In general we haven't historically updated our code with comments where there was a bug, at least as long as we have a test which will fail if we ever regress (which we do here). It feels weird to start now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right, I don't seep mentions of former bugs over a quick browse of channel.rs/channelmanager.rs though for QA purposes what do you think of tracking PR in do_test_htlc_on_chain_timeout's updated comment about connect style scenarios ?

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.

Hmm, I generally just git blame and then look at the commit(s)/check github to see what PR it came from. We could add PR #s too, but I'm a bit hesitant to have the code explicitly reference the PR it was in. But if you feel strongly I can add it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well i'm a huge fan of good post-mortem tracking, kind of in the long-term of #573 :) Making a mental note to add to next agenda meeting as we have all differing opinions there.

Comment threadlightning/src/chain/onchaintx.rs
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
if let Some(h) = request.timer() {
if height >= h {
if cur_height >= h {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this change opening to the possibility of a duplicated broadcast ?

If you call first best_block_updated and bump best_block to N.
Then you call transaction_confirmed and register a request at block N - 3, you generate a first claim transaction.
Then you hit timer returning T >= H, you generate a second claim transaction.

I don't think because we call get_height_timer with cur_height in generate_claim_tx though I would be glad to have this potential interaction more highlighted.

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.

Hmmmm, should we swap the ordering of things in update_claims_view and first bump RBF transactions and only process the requests after that? I'm a bit hesitant to given it will probably mean a ton of test changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See above comment about height collision, I would favor this ordering swap to avoid re-broadcasting. I believe, post-anchor that kind of defaulting internal API issue might force us to reserve fee-bumping reserve for nothing ?

Though it's not related to this PR, and we can keep thinking about it.

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.

I'm a bit confused about that comment, but I'd think the solution is to track outputs which were on chain and spent and never re-spend them, instead of re-spending them after ANTI_REORG_DELAY.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah that would works too to blacklist confirmed utxos so i think it's more an issue on how duplicate block processing is interfering with update_claim_view() list of calls.

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.

Oh, hmm, I guess I just misread your above comment. Let me try the ordering swap in a new PR? I don't really want to hold this/0.0.99 up to redo a ton of tests.

Comment threadlightning/src/ln/channelmanager.rs Outdated
if self.broadcasted_holder_revokable_script.is_some() {
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this comment is a bit confusing, bumping timer is only function of current height and expiration timelock (see get_height_timer in package.rs, not of parent transaction conf. So leveraging the best known height as comparison point to refresh timers is the best we can do ?

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.

get_broadcasted_holder_claims sets the PackageTemplate::soonest_conf_deadline to the height given here, so it is the "expiration timelock".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes in this case parent conf height is equal to PackageTemplate::soonest_conf_deadline which is equal to "expiration timelock" but my point rather than relying on parent conf height, which have the side-effect of artificially increasing the fee-bump frequency and thus bleed feerate a bit, we should rely on the per-holder-claims timelocks themselves to fulfill soonest_conf_deadline.

Though I don't think this comment needs further action for now and sounds to be related just after.

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.

I'm still a bit confused here, but I'll leave it for a followup.

HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), height, false, height);
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is buggy, why we call build_package and initialize soonest_conf_deadline (i.e the timelock expiration height) to the confirmation or current height ? At least it's already what we do on master (c05347f), this PR doesn't seem to change anything...

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.

Yea, its "wrong", but only in the sense that there is no "right" answer. We can't RBF-bump the commitment transactions anyway, so its not broken, i don't think, and indeed its the same on master. I'd ideally prefer to clean it up in a separate PR, though I agree its a really poor API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well IMHO the change is as simple as this :

diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index b7f87883..9e6e64e4 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -1746,7 +1746,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
};
HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
- let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);
+ let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height);
claim_requests.push(htlc_package);
}
}

Though doesn't break any test on top of this branch, so likely need a test to demonstrate the fee-bumping policy change. Tracked as #982 and self-assigned. Note, it might duplicate htlc.cltv_expiry usage both at the package-level and at the output-level but i don't think that's a redundance as in the first case we would use for fee-bumping scheduling and in the second to generate the correct witnessScript ?

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! Duh, indeed, I was somehow confusing the htlc package for the commitment tx package. I updated it but agree we need more tests here.

self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're building a new commitment transaction, we should construct the package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing too, we never "build" new commitment transaction in the backend, at least we might regenerate one to ensure sanity but never build-and-sign a commitment transaction from scratch ?

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.

Hmm, semantics, but OK, I tweaked it.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch 2 times, most recently from 6a0dcd8 to 9abba15CompareJune 29, 2021 15:54
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9abba15 to 419e9faCompareJune 29, 2021 20:14
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of the logging dependency. No changes/squashes were done.

@jkczyzjkczyz left a comment

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.

Largely looks good though my comprehension of onchaintx.rs changes is lacking compared to @ariard.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
}

if height > self.best_block.height() {
self.best_block = BestBlock::new(block_hash, height);

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.

Should we log_trace here as we do in block_connected and best_block_updated? Regardless of which methods, should we log only when best_block is updated or (in particularly for the other two) unconditionally when the methods are called?

Taking a step back, it feels like some of this logging me be redundant across other monitors and may not provide much value. Maybe the logging should be placed in ChainMonitor instead? Then again, logging when the best block is actually updated could be valuable.

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.

Yea, I think there's a lot of room for improvement here, lets do it in a followup?

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.

SGTM

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notwithstanding already-existent issues mentioned in comment and we should address latter, this PR sounds good to me.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from e271d98 to 5e51f84CompareJuly 1, 2021 14:47
@ariard

ariard commented Jul 1, 2021

Copy link
Copy Markdown

Code Review ACK 5e51f84

Thanks for taking the latest suggestions!

@jkczyzjkczyz left a comment

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 further DRYs up some functional_test code and increases
coverage.
There are no visible effects of this, but it seems like good code
hygiene to not call a disconnect function in a different file if no
disconnect happened.
No matter the context, if we're told about a block which is
guaranteed by our API semantics to be on the best chain, and it has
a higher height than our current understanding of the best chain,
we should update our understanding. This avoids complexity
in `block_confirmed` by never having a height set which is *higher*
than our current best chain, potentially avoiding some bugs in the
rather-complicated code.
It also requires a minor test tweak as we in some cases now no
longer broadcast a conflicting transaction after the original has
reached the ANTI_REORG_DELAY.
We had a user who pointed out that we weren't creating
`SpendableOutputs` events when we should have been after they
called `ChannelMonitor::best_block_updated` with a block well
after a CSV locktime and then called
`ChannelMonitor::transactions_confirmed` with the transaction which
we should have been spending (with a block height/hash a ways in
the past).
This was due to `ChannelMonitor::transactions_confirmed` only
calling `ChannelMonitor::block_confirmed` with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.
Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.
To address this, we use the best block height for confirmation
threshold checks in `ChannelMonitor::block_confirmed` and pass both
the confirmation and current heights through to
`OnchainTx::update_claims_view`, using each as appropriate.
Fixeslightningdevkit#962.
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 5e51f84 to 412cc9fCompareJuly 2, 2021 17:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed with no diff, will take after CI:

$ git diff-tree -U1 5e51f84b 412cc9f0
$

@TheBlueMatt
TheBlueMatt merged commit 0c57018 into lightningdevkit:mainJul 2, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChannelMonitor::best_block_updated before ChannelMonitor::transactions_confirmed may needlessly delay broadcasting by a block

3 participants

@TheBlueMatt@ariard@jkczyz
, '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

Create SpendableOutputs events no matter the chain::Confirm order - #970

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay
Jul 2, 2021
Merged

Create SpendableOutputs events no matter the chain::Confirm order#970
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
TheBlueMatt:2021-06-no-confirmed-csv-delay

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jun 25, 2021

Copy link
Copy Markdown
Collaborator

We had a user who pointed out that we weren't creating
SpendableOutputs events when we should have been after they
called ChannelMonitor::best_block_updated with a block well
after a CSV locktime and then called
ChannelMonitor::transactions_confirmed with the transaction which
we should have been spending (with a block height/hash a ways in
the past).

This was due to ChannelMonitor::transactions_confirmed only
calling ChannelMonitor::block_confirmed with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.

Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.

To address this, we use the best block height for confirmation
threshold checks in ChannelMonitor::block_confirmed and pass both
the confirmation and current heights through to
OnchainTx::update_claims_view, using each as appropriate.

Note to reviewers: please carefully check the update_claims_view part of the changeset here, there may very well be some unintended double-use of the height which wasn't properly separated (though I don't see any immediately).

Fixes#962.

@TheBlueMattTheBlueMatt added this to the 0.0.99 milestone Jun 25, 2021
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 7206c03 to 8216af0CompareJune 25, 2021 17:43
@codecov

codecovBot commented Jun 25, 2021

Copy link
Copy Markdown

Codecov Report

Merging #970 (5e51f84) into main (f472907) will increase coverage by 0.30%.
The diff coverage is 98.27%.

❗ Current head 5e51f84 differs from pull request most recent head 412cc9f. Consider uploading reports for the commit 412cc9f to get more accurate results
Impacted file tree graph

@@ Coverage Diff @@## main #970 +/- ##
==========================================
+ Coverage 90.63% 90.93% +0.30% 
==========================================
Files 60 60 Lines 30538 31896 +1358 ==========================================
+ Hits 27678 29005 +1327 - Misses 2860 2891 +31 
Impacted FilesCoverage Δ
lightning/src/ln/channel.rs90.43% <ø> (+1.92%)⬆️
lightning/src/ln/channelmanager.rs86.76% <ø> (+2.86%)⬆️
lightning/src/chain/onchaintx.rs94.19% <93.75%> (ø)
lightning/src/chain/channelmonitor.rs90.80% <95.65%> (+0.04%)⬆️
lightning/src/ln/functional_test_utils.rs94.83% <100.00%> (+0.01%)⬆️
lightning/src/ln/functional_tests.rs97.34% <100.00%> (+0.17%)⬆️
lightning/src/util/events.rs15.15% <0.00%> (-2.15%)⬇️
lightning/src/ln/chanmon_update_fail_tests.rs98.55% <0.00%> (+0.77%)⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f472907...412cc9f. Read the comment docs.

Comment threadlightning/src/ln/functional_test_utils.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Might make sense to update our interfaces (here and elsewhere) to take &BestBlock where that is expected to avoid accidentally passing a confirmation height. Or even directly use it rather than passing it at all where that is possible.

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.

Hmm, suppose we could, I thought renaming height to conf_height was pretty clear (though I could spell it out). For OnchainTx I could tweak it if you think it makes more sense (given it doesn't have its own best_block field)?

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.

Seems like there are a few scenarios:

  • it doesn't need to be passed at all because the method already can access it through self, so why bother passing it? (e.g., remove would_broadcast_at_height can be named simply should_broadcast and doesn't need to take a height, nor does get_broadcasted_holder_claims
  • only the current height is needed, so passing &BestBlock makes it explicit and prevents passing the wrong height (e.g., has_reached_confirmation_threshold)
  • both heights are needed in OnchainTx (maybe leave as is since there is a calculation involved at the call site)

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.

I believe get_broadcasted_holder_claims needs the confirmation height, or at least appears to given it passes it to PackageTx::build_package as height_original, though its API seems like a good opportunity for cleanup. Did the rest.

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

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, in that case its being called with reference to a new commitment transaction being broadcast, not one which is confirmed (new_holder_commitment_transaction), so I think that's correct. Somewhat confusingly, its also called with a 0-height in provide_payment_preimage in a similar context, which is "wrong", but AFAICT harmless.

Ultimately it doesn't really matter much for this case, I think, cause its passed through as height_original in PackageTemplate which is only used to figure out when we can merge packages (which we can never do for the HolderHTLCOutputs that are used here), and when we can broadcast (which we just use the CLTV which is fine).

I'll add some comments and fix the 0 value version.

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 8216af0 to 642f5afCompareJune 25, 2021 19:29
L::Target: Logger,
{
let should_broadcast = self.would_broadcast_at_height(height, &logger);
let should_broadcast = self.would_broadcast_at_height(self.best_block.height(), logger);

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.

Hmmm... I see but you're calling get_broadcasted_holder_claims with the best block height in block_confirmed, right?

Comment on lines +2014 to +2015
/// Update state for new block(s)/transaction(s) confirmed. Note that the caller *must* update
/// `self.best_block` before calling, and set `conf_height` to the height at which any new

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.

Is this right? It isn't the case when called by transactions_confirmed as of now.

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.

I don't think it matters, but its probably simpler/nicer for users to actually set it there, so I did that.

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.

Is this behavior required to fix the bug? Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block? Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

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.

Is this behavior required to fix the bug?

No.

Adding it there is equivalent to calling best_block_updated after each transactions_confirmed. So doesn't this mean calling best_block_updated is no longer required when confirming transactions at the same block?

Yes, I believe so, at least in ChannelMonitor, dunno about ChannelManager.

Seems it is less simpler for users because it adds a conditional to Confirm's contract, albeit optional.

I don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

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 don't see why? It makes a call optional in our current implementation, yes, but we don't have to tell users that.

It seems like surprising behavior. I called X expecting Y but instead Y + Z happened. I guess I don't see why not just drop the comment rather than altering the behavior if it isn't required to fix the bug. At very least it should be a separate commit stating the reason for the change, IMHO.

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.

It definitely feels "more correct" and should happen, IMO. I moved it to a new commit, let me know what you think of the commit docs and if it sufficiently justifies it.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs Outdated
Comment threadlightning/src/chain/onchaintx.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from eaff313 to 9fcc9d2CompareJune 27, 2021 22:18
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Updated to address feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9fcc9d2 to 540bcdeCompareJune 28, 2021 16:38

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A lot of interactions to think about after a first parse.

self.is_paying_spendable_output(&tx, height, &logger);
}

if height > self.best_block.height() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe you can add message commit here : "This avoids complexity in block_confirmed by never having a tracked height set which is higher than our current best confirmed chain, potentially avoiding some bugs in the rather-complicated code" ?

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.

I put it in the doc comment of block_confirmed instead.

if node_txn.len() == 1 {
check_spends!(node_txn[0], chan_2.3);
} else {
assert_eq!(node_txn.len(), 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Honestly this test path which covers TransactionsFirstSkippingBlocks, the connection style updated by this comment, doesn't make sense to me.

From what I remember, I don't understand why updating best_block in transactions_confirmed instead of best_block_updated only doesn't generate a B's commitment transaction broadcast, they both should fulfill should_broadcast.

I need to look deeper on this...

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.

In the old case, a few blocks before we get here we remove the funding tx output from OnchainTx::claimable_outpoints (which was originally tracked via commitment_tx[0]'s confirmation). Because its not there we can re-add it and re-broadcast our own commitment transaction. In the new case, we skip ahead a few blocks and just call OnchainTx::update_claims_view once, which means only at the very end do we remove the funding tx output, after we've already given up broadcasting the commitment tx.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Because its not there we can re-add it and re-broadcast our own commitment transaction.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Adding the following diff

diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs
index 7e616ef2..4e1c1c5d 100644
--- a/lightning/src/chain/onchaintx.rs
+++ b/lightning/src/chain/onchaintx.rs
@@ -536,6 +536,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
// been aggregated in a single tx and claimed so atomically
if let Some(request) = self.pending_claim_requests.remove(&claim_request) {
for outpoint in request.outpoints() {
+ log_trace!(logger, "Removing claiming outpoint for {}", outpoint.txid);
self.claimable_outpoints.remove(&outpoint);
}
}

And obtaining the following log, confirm that we might remove a tracked output to re-add just after as a new request ? (see 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e)


TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor DynamicOutputP2WPKH 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4:2 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2085] HTLC 75877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a failure update has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2093] Descriptor StaticOutput 4ee8b85ba72a1a1beb201438e8ec8903ce0acf2e48161e5e61c9e331d048fa70:0 marked for spending has got enough confirmations to be passed upstream TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 539] Removing claiming outpoint for 012b0c6223264225513eb7fd1661b3e04e6076f8c0868b48565ba85d9c3c24c4 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1925] New best block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 0 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 563] Bumping 0 candidates
TRACE node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 1963] Block 88fb36eb50c852280f8b85410462252dff993bbf50a3e752dec3f8a54b978c41 at height 206 connected with 0 txn matched INFO node 1 [lightning::chain::channelmonitor : lightning/src/chain/channelmonitor.rs, 2250] Force-closing channel due to outbound HTLC timeout, HTLC expiry is 81 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 385] Updating claims view at height 206 with 0 matched transactions and 2 claim requests TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 413] Test if outpoint can be aggregated with expiration 206 against 218 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 394] Ignoring second claim for outpoint b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3:0, already registered its claiming request TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 615] Adding claiming input for outpoint 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::package : lightning/src/chain/package.rs, 616] Finalized transaction b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 ready to broadcast TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 444] Registering claiming request for 24647a3792b08dbcd7c9516ae5118fce200b2a11c49cbc496b48da466b125e3e:0 TRACE node 1 [lightning::chain::onchaintx : lightning/src/chain/onchaintx.rs, 448] Broadcasting onchain commitment tx with txid b00e0a6ecc3ad8958be2552cf1eefc75232be2c68ec4668e14f6c784d5d050e3 

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.

This is the weirdness I didn't understand and AFAICT it sounds an issue with our already-existent update_claim_view logic where we might have a "height" collision between maturity of OnchainEvent::Claim for commitment_tx[0] and hitting a should_broadcast()==true generating a new request for the same commitment.

Its not height, its the previous outpoint itself. The claimable_outpoints map is indexed by previous outpoint, and in this case we've already registered that we're tracking a spend of the same outpoint, so we don't add a new one. This seems correct to me, if race-y.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmmmm i still think it's a weird height-race because note in the log channelmonitor is processing twice height 206 ? First time we remove the commitment's outpoint from OnchainTxHandler.claimable_outpoints then at second processing we add a new request for the already-confirmed commitment transaction in OnchainTxHandler.claimable_outpoints as the redundant-claim replay protection L393 doesn't play out ?

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, I totally agree that is a (very minor) bug, but this PR just removes that bug depending on the connection style, so I don't think it needs to get fixed here :).

let mut onchain_events_reaching_threshold_conf = Vec::new();
for entry in onchain_events_awaiting_threshold_conf {
if entry.has_reached_confirmation_threshold(height) {
if entry.has_reached_confirmation_threshold(self.best_block.height()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is the updated code path solving the issue describing in the commit. Maybe add PR number and one-line mentioning we had a bug here.

@TheBlueMattTheBlueMattJun 29, 2021

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.

There were two issues, this is one, tbh I don't recall exactly which line in update_claims_view solved the other one. In general we haven't historically updated our code with comments where there was a bug, at least as long as we have a test which will fail if we ever regress (which we do here). It feels weird to start now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right, I don't seep mentions of former bugs over a quick browse of channel.rs/channelmanager.rs though for QA purposes what do you think of tracking PR in do_test_htlc_on_chain_timeout's updated comment about connect style scenarios ?

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.

Hmm, I generally just git blame and then look at the commit(s)/check github to see what PR it came from. We could add PR #s too, but I'm a bit hesitant to have the code explicitly reference the PR it was in. But if you feel strongly I can add it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well i'm a huge fan of good post-mortem tracking, kind of in the long-term of #573 :) Making a mental note to add to next agenda meeting as we have all differing opinions there.

Comment threadlightning/src/chain/onchaintx.rs
for (first_claim_txid, ref request) in self.pending_claim_requests.iter() {
if let Some(h) = request.timer() {
if height >= h {
if cur_height >= h {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this change opening to the possibility of a duplicated broadcast ?

If you call first best_block_updated and bump best_block to N.
Then you call transaction_confirmed and register a request at block N - 3, you generate a first claim transaction.
Then you hit timer returning T >= H, you generate a second claim transaction.

I don't think because we call get_height_timer with cur_height in generate_claim_tx though I would be glad to have this potential interaction more highlighted.

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.

Hmmmm, should we swap the ordering of things in update_claims_view and first bump RBF transactions and only process the requests after that? I'm a bit hesitant to given it will probably mean a ton of test changes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See above comment about height collision, I would favor this ordering swap to avoid re-broadcasting. I believe, post-anchor that kind of defaulting internal API issue might force us to reserve fee-bumping reserve for nothing ?

Though it's not related to this PR, and we can keep thinking about it.

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.

I'm a bit confused about that comment, but I'd think the solution is to track outputs which were on chain and spent and never re-spend them, instead of re-spending them after ANTI_REORG_DELAY.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah that would works too to blacklist confirmed utxos so i think it's more an issue on how duplicate block processing is interfering with update_claim_view() list of calls.

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.

Oh, hmm, I guess I just misread your above comment. Let me try the ordering swap in a new PR? I don't really want to hold this/0.0.99 up to redo a ton of tests.

Comment threadlightning/src/ln/channelmanager.rs Outdated
if self.broadcasted_holder_revokable_script.is_some() {
let (claim_reqs, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, 0);
// Assume that the broadcasted commitment transaction confirmed in the current best
// block. Even if not, its a reasonable metric for the bump criteria on the HTLC

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this comment is a bit confusing, bumping timer is only function of current height and expiration timelock (see get_height_timer in package.rs, not of parent transaction conf. So leveraging the best known height as comparison point to refresh timers is the best we can do ?

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.

get_broadcasted_holder_claims sets the PackageTemplate::soonest_conf_deadline to the height given here, so it is the "expiration timelock".

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yes in this case parent conf height is equal to PackageTemplate::soonest_conf_deadline which is equal to "expiration timelock" but my point rather than relying on parent conf height, which have the side-effect of artificially increasing the fee-bump frequency and thus bleed feerate a bit, we should rely on the per-holder-claims timelocks themselves to fulfill soonest_conf_deadline.

Though I don't think this comment needs further action for now and sounds to be related just after.

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.

I'm still a bit confused here, but I'll leave it for a followup.

HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), height, false, height);
let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this is buggy, why we call build_package and initialize soonest_conf_deadline (i.e the timelock expiration height) to the confirmation or current height ? At least it's already what we do on master (c05347f), this PR doesn't seem to change anything...

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.

Yea, its "wrong", but only in the sense that there is no "right" answer. We can't RBF-bump the commitment transactions anyway, so its not broken, i don't think, and indeed its the same on master. I'd ideally prefer to clean it up in a separate PR, though I agree its a really poor API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well IMHO the change is as simple as this :

diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index b7f87883..9e6e64e4 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -1746,7 +1746,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
};
HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat)
};
- let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), conf_height, false, conf_height);
+ let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height);
claim_requests.push(htlc_package);
}
}

Though doesn't break any test on top of this branch, so likely need a test to demonstrate the fee-bumping policy change. Tracked as #982 and self-assigned. Note, it might duplicate htlc.cltv_expiry usage both at the package-level and at the output-level but i don't think that's a redundance as in the first case we would use for fee-bumping scheduling and in the second to generate the correct witnessScript ?

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! Duh, indeed, I was somehow confusing the htlc package for the commitment tx package. I updated it but agree we need more tests here.

self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're building a new commitment transaction, we should construct the package

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is confusing too, we never "build" new commitment transaction in the backend, at least we might regenerate one to ensure sanity but never build-and-sign a commitment transaction from scratch ?

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.

Hmm, semantics, but OK, I tweaked it.

@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch 2 times, most recently from 6a0dcd8 to 9abba15CompareJune 29, 2021 15:54
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 9abba15 to 419e9faCompareJune 29, 2021 20:14
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased after merge of the logging dependency. No changes/squashes were done.

@jkczyzjkczyz left a comment

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.

Largely looks good though my comprehension of onchaintx.rs changes is lacking compared to @ariard.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
}

if height > self.best_block.height() {
self.best_block = BestBlock::new(block_hash, height);

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.

Should we log_trace here as we do in block_connected and best_block_updated? Regardless of which methods, should we log only when best_block is updated or (in particularly for the other two) unconditionally when the methods are called?

Taking a step back, it feels like some of this logging me be redundant across other monitors and may not provide much value. Maybe the logging should be placed in ChainMonitor instead? Then again, logging when the best block is actually updated could be valuable.

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.

Yea, I think there's a lot of room for improvement here, lets do it in a followup?

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.

SGTM

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/chain/channelmonitor.rs

@ariardariard left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notwithstanding already-existent issues mentioned in comment and we should address latter, this PR sounds good to me.

Comment threadlightning/src/chain/channelmonitor.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from e271d98 to 5e51f84CompareJuly 1, 2021 14:47
@ariard

ariard commented Jul 1, 2021

Copy link
Copy Markdown

Code Review ACK 5e51f84

Thanks for taking the latest suggestions!

@jkczyzjkczyz left a comment

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 further DRYs up some functional_test code and increases
coverage.
There are no visible effects of this, but it seems like good code
hygiene to not call a disconnect function in a different file if no
disconnect happened.
No matter the context, if we're told about a block which is
guaranteed by our API semantics to be on the best chain, and it has
a higher height than our current understanding of the best chain,
we should update our understanding. This avoids complexity
in `block_confirmed` by never having a height set which is *higher*
than our current best chain, potentially avoiding some bugs in the
rather-complicated code.
It also requires a minor test tweak as we in some cases now no
longer broadcast a conflicting transaction after the original has
reached the ANTI_REORG_DELAY.
We had a user who pointed out that we weren't creating
`SpendableOutputs` events when we should have been after they
called `ChannelMonitor::best_block_updated` with a block well
after a CSV locktime and then called
`ChannelMonitor::transactions_confirmed` with the transaction which
we should have been spending (with a block height/hash a ways in
the past).
This was due to `ChannelMonitor::transactions_confirmed` only
calling `ChannelMonitor::block_confirmed` with the height at which
the transactions were confirmed, resulting in all checks being done
against that, not the current height.
Further, in the same scenario, we also would not fail-back and HTLC
where the HTLC-Timeout transaction was confirmed more than
ANTI_REORG_DELAY blocks ago.
To address this, we use the best block height for confirmation
threshold checks in `ChannelMonitor::block_confirmed` and pass both
the confirmation and current heights through to
`OnchainTx::update_claims_view`, using each as appropriate.
Fixeslightningdevkit#962.
@TheBlueMatt
TheBlueMattforce-pushed the 2021-06-no-confirmed-csv-delay branch from 5e51f84 to 412cc9fCompareJuly 2, 2021 17:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed with no diff, will take after CI:

$ git diff-tree -U1 5e51f84b 412cc9f0
$

@TheBlueMatt
TheBlueMatt merged commit 0c57018 into lightningdevkit:mainJul 2, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChannelMonitor::best_block_updated before ChannelMonitor::transactions_confirmed may needlessly delay broadcasting by a block

3 participants

@TheBlueMatt@ariard@jkczyz