-
Notifications
You must be signed in to change notification settings - Fork 385
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
Fix to_remote SpendableOutputs generation in rare reorg cases #1022
Fix to_remote SpendableOutputs generation in rare reorg cases #1022
Conversation
Obviously a pretty important bugfix, so tagging 0.0.100. |
7282abd
to
8a9b7e5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script { | ||
if broadcasted_holder_revokable_script.0 == outp.script_pubkey { | ||
spendable_output = Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some print statements and it seems like the issue was: it'd enter this else if
statement, but wouldn't pass the check on the second highlighted line, and then naturally fail to even consider the next else if
check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the second if clause overwrites spendable_output, it seems like essentially a reordering of the clauses?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That might have worked too, but it's really the if else blocks miss that one in the middle isn't fully captured by the check. Because the check is split across the if let and the inner if, we sometimes incorrectly short-circuit the if else tree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was wondering if they could have possibly been merge-able into one condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, rather obnoxiously Rust doesn't let you merge if let into any additional constraints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here my understanding of the fixed issue.
If a holder commitment is broadcast, we set ChannelMonitor::broadcasted_holder_revokable_script
to Some
in check_spend_holder_transaction
, from then always marking the spendable output as a DelayedPaymentOutput
in is_paying_spendable_output
.
After this PR, the spendable output type detection is moved from a "else if" control flow to a "if" one, thus preventing the accidental jump over StaticPaymentOutput
detection.
assert_eq!(outputs.len(), 1); | ||
let spend_tx = nodes[0].keys_manager.backing.spend_spendable_outputs(&[&outputs[0]], Vec::new(), | ||
Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(), 253, &Secp256k1::new()).unwrap(); | ||
check_spends!(spend_tx, remote_txn_b[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
admittedly not really the place for this remark, but check_spends sounds awfully vague. Especially with the macro not being defined in this file, a more descriptive name might be helpful for readers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't say I disagree. There's a lot of open PRs right now, however, so if we want to change it maybe we should wait until things are a bit calmer in a month or two.
// nodes[1] is waiting for the to_self_delay to expire, which is many more than | ||
// ANTI_REORG_DELAY. Instead, walk it back and confirm the original remote_txn_a commitment | ||
// again and check that nodes[1] generates a similar spendable output. | ||
// Technically a reorg of ANTI_REORG_DELAY violates our assumptions, so this is undefined by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a list for which events we wouldn't know how to handle correctly based on max possible reorg size assumptions? Might be useful to link from this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think probably roughly all of them. In general we don't bother to correctly handle anything that includes a reorg of ANTI_REORG_DELAY. Many things are likely to work just fine, probably, but it's outside our security model entirely. This could maybe be better documented.
if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script { | ||
if broadcasted_holder_revokable_script.0 == outp.script_pubkey { | ||
spendable_output = Some(SpendableOutputDescriptor::DelayedPaymentOutput(DelayedPaymentOutputDescriptor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the second if clause overwrites spendable_output, it seems like essentially a reordering of the clauses?
Nit, can you take this : ariard@6747827 ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise ACK 8a9b7e5.
I did verify the presence of the current issue and effectiveness of the fix. Mea culpa for this, iirc i introduced is_paying_spendable_output
@@ -2451,7 +2451,8 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> { | |||
output: outp.clone(), | |||
}); | |||
break; | |||
} else if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script { | |||
} | |||
if let Some(ref broadcasted_holder_revokable_script) = self.broadcasted_holder_revokable_script { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One step further to minimize unseen interactions like this could be to unset self.broadcasted_holder_revokeable_script
in block_disconnected
/transaction_unconfirmed
's onchain_events_awaiting_threshold_conf
. If the removed event is of type MaturingOutput
and SpendableOutputDescriptor == DelayedPaymentOutput
, self.broadcasted_holder_revokeable_script==None
I think ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean with this no longer being an if else
branch list it should "always work", and unsettnig scripts to match seems risky.
8a9b7e5
to
0545842
Compare
If we first see a local commitment transaction, and then a reorg causes the confirmed channel close transaction to instead be a remote commitment transaction, we would fail a spurious `if else` check, resulting in us not generating the correct `SpendableOutput` event for the to_remote output now confirmed on chain. This resolves the incorrect logic and adds a regression test.
0545842
to
ad44590
Compare
I have several more PRs coming in this code area, I'll throw it in the next one. Otherwise, added a
|
Codecov Report
@@ Coverage Diff @@
## main #1022 +/- ##
==========================================
+ Coverage 90.82% 91.49% +0.67%
==========================================
Files 60 61 +1
Lines 31455 39398 +7943
==========================================
+ Hits 28568 36046 +7478
- Misses 2887 3352 +465
Continue to review full report at Codecov.
|
If we first see a local commitment transaction, and then a reorg
causes the confirmed channel close transaction to instead be a
remote commitment transaction, we would fail a spurious
if else
check, resulting in us not generating the correct
SpendableOutput
event for the to_remote output now confirmed on chain.
This resolves the incorrect logic and adds a regression test.