Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly detect missing HTLCs when a local commitment tx was broadcast #1025

Conversation

TheBlueMatt
Copy link
Collaborator

If we forward an HTLC to our counterparty, but we force-closed the
channel before our counterparty provides us an updated commitment
transaction, we'll end up with a commitment transaction that does
not contain the HTLC which we attempted to forward. In this case,
we need to wait ANTI_REORG_DELAY blocks and then fail back the
HTLC as there is no way for us to learn the preimage and the
confirmed commitment transaction paid us the value of the HTLC.

However, check_spend_holder_transaction did not do this - it
instead only looked for dust HTLCs in the confirmed commitment
transaction, paying no attention to what other HTLCs may exist that
are missed.

This will eventually lead to channel force-closure as the channel
on which we received the inbound HTLC to forward will be closed in
time for the initial sender to claim the HTLC on-chain.

This is a semi-important DoS issue, so tagging 0.0.100.

@TheBlueMatt TheBlueMatt added this to the 0.0.100 milestone Jul 31, 2021
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-detect-htlcs-on-local-commitment branch from 931b054 to b688365 Compare July 31, 2021 14:34
@codecov
Copy link

codecov bot commented Jul 31, 2021

Codecov Report

Merging #1025 (c3c0dff) into main (8530078) will increase coverage by 0.80%.
The diff coverage is 67.69%.

❗ Current head c3c0dff differs from pull request most recent head 6bfab9d. Consider uploading reports for the commit 6bfab9d to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1025      +/-   ##
==========================================
+ Coverage   90.80%   91.60%   +0.80%     
==========================================
  Files          61       62       +1     
  Lines       31582    39598    +8016     
==========================================
+ Hits        28678    36274    +7596     
- Misses       2904     3324     +420     
Impacted Files Coverage Δ
lightning/src/ln/mod.rs 90.00% <ø> (ø)
lightning/src/chain/channelmonitor.rs 93.14% <40.00%> (+2.56%) ⬆️
lightning/src/ln/monitor_tests.rs 100.00% <100.00%> (ø)
lightning/src/util/events.rs 12.50% <0.00%> (-1.44%) ⬇️
lightning/src/ln/functional_tests.rs 96.82% <0.00%> (-0.50%) ⬇️
lightning/src/ln/chanmon_update_fail_tests.rs 97.25% <0.00%> (-0.49%) ⬇️
lightning/src/util/ser_macros.rs 95.91% <0.00%> (-0.40%) ⬇️
lightning-background-processor/src/lib.rs 95.58% <0.00%> (ø)
lightning/src/chain/keysinterface.rs 95.41% <0.00%> (+0.04%) ⬆️
... and 21 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 8530078...6bfab9d. Read the comment docs.

Comment on lines 1790 to 1811
if is_holder_tx {
fail_dust_htlcs_after_threshold_conf!(self.current_holder_commitment_tx);
if let &Some(ref holder_tx) = &self.prev_holder_signed_commitment_tx {
fail_dust_htlcs_after_threshold_conf!(holder_tx);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Empty if block?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yea, it'll be re-filled in the PR this one grew out of so I left it. I can remove it temporarily if you want.

Copy link
Contributor

Choose a reason for hiding this comment

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

What PR is that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll let you know when I open it :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

let chanmon_cfgs = create_chanmon_cfgs(3);
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
Copy link
Contributor

Choose a reason for hiding this comment

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

unnecessary mut

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Old rustc incorrectly rejected this as missing-mut, see comment in lightning/src/ln/mod.rs. I added an ignore for this.

error[E0596]: cannot borrow `nodes` as mutable, as it is not declared as mutable
  --> lightning/src/ln/monitor_tests.rs:51:87
   |
46 |     let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
   |         ----- help: consider changing this to be mutable: `mut nodes`
...
51 |     let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], 1_000_000);
   |                                                                                          ^^^^^ cannot borrow as mutable

}
}
}
fail_unbroadcast_htlcs!(self, "previous holder", height, holder_tx.htlc_outputs.iter().map(|(a, _, c)| (a, c.as_ref())), logger);
Copy link
Contributor

Choose a reason for hiding this comment

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

To clarify: is the main difference in logic that before we would only consider the relevant commitment tx's outputs but now we consider all of counterparty_claimable_outpoints?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right, so the trick here is that the counterparty commitment transaction is the "latest" one, because its the one that we generated. The local commitment transaction is the one our counterparty generated, so isn't always the HTLC state that matches our ChannelMonitor state.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we add some docs (including the info in your comment^) to counterparty_claimable_outpoints? I had to search around the file to get some context about it

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added some docs to counterparty_claimable_outpoints, and expanded the docs on fail_unbroadcast_htlcs to describe my reasoning.

@TheBlueMatt TheBlueMatt force-pushed the 2021-07-detect-htlcs-on-local-commitment branch from 0722912 to bb1a050 Compare August 4, 2021 03:13
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-detect-htlcs-on-local-commitment branch from c3c0dff to 3f3aff1 Compare August 9, 2021 15:47
@TheBlueMatt
Copy link
Collaborator Author

Rebased and squashed.

This extracts the HTLC-not-in-broadcasted-commitment-transaction
code from check_spend_counterparty_transaction and moves it to a
global macro, DRYing up the two very similar codepaths (fixing
some minor logging inconsistencies) in the process.

This macro will be used for local commitment transaction HTLC
failure as well in the next commit.

This commit has no functional change outside of logging.
If we forward an HTLC to our counterparty, but we force-closed the
channel before our counterparty provides us an updated commitment
transaction, we'll end up with a commitment transaction that does
not contain the HTLC which we attempted to forward. In this case,
we need to wait `ANTI_REORG_DELAY` blocks and then fail back the
HTLC as there is no way for us to learn the preimage and the
confirmed commitment transaction paid us the value of the HTLC.

However, check_spend_holder_transaction did not do this - it
instead only looked for dust HTLCs in the confirmed commitment
transaction, paying no attention to what other HTLCs may exist that
are missed.

This will eventually lead to channel force-closure as the channel
on which we received the inbound HTLC to forward will be closed in
time for the initial sender to claim the HTLC on-chain.
@TheBlueMatt TheBlueMatt force-pushed the 2021-07-detect-htlcs-on-local-commitment branch from 3f3aff1 to 6bfab9d Compare August 9, 2021 16:13
@jkczyz jkczyz requested a review from arik-so August 9, 2021 19:11
@@ -510,6 +510,9 @@ pub(crate) struct ChannelMonitorImpl<Signer: Sign> {
on_holder_tx_csv: u16,

commitment_secrets: CounterpartyCommitmentSecrets,
/// The set of outpoints in each counterparty commitment transaction. We always need at least
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a true statement: counterparty_claimable_outpoints is the set of (outpoints_in_prev_counterparty_commit_tx_if_not_revoked, outpoints_in_curr_counterparty_commit_tx)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

counterparty_claimable_outpoints, somewhat confusingly named, is a map from a single counterparty commitment transaction (by txid) to the set of HTLCs which were included in that transaction.

Copy link
Contributor

@valentinewallace valentinewallace left a comment

Choose a reason for hiding this comment

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

LGTM

@TheBlueMatt TheBlueMatt merged commit d4b6f58 into lightningdevkit:main Aug 10, 2021
Sign up for free to 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.

3 participants