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
Breach Arbiter Sweep 2nd layer HTLCs #264
Breach Arbiter Sweep 2nd layer HTLCs #264
Conversation
@cfromknecht, thanks for your PR! By analyzing the history of the files in this pull request, we identified @Roasbeef, @bryanvu and @cjamthagen to be potential reviewers. |
f06fa4d
to
d80a41f
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.
Nice work! Most of my comments are pretty minor: mostly style related, constant usage, etc.
I'll wait until you've finished up the remainder of the TODO items (tests, the slight decoupling) to start the next review cycle.
breacharbiter.go
Outdated
return nil | ||
} | ||
|
||
func (b *breachArbiter) waitForPendingCloseConfs(currentHeight int32) error { |
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.
Missing a godoc
comment on this new method. I dig the code move though, makes the Start
method much more digestible.
} | ||
|
||
// TODO(conner): move responsibility of channel closure into |
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.
As is now, it seems the this is unnecessary as the goroutine within the LightningChannel
struct should block until it's received a signal that the breachArbiter
has written the retribution info to disk.
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.
Actually, scratch that. I remember now that we started to draft up this approach, but abandoned it at the might for the sake of minimizing that prior PR.
Can you create an issue describing this case and what needs to be done to address it? Thanks!
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.
Finally got around to this 🐢🐢🐢, peep le issue #327
breacharbiter.go
Outdated
witnessFunc lnwallet.WitnessGenerator | ||
} | ||
|
||
// newBreachedOutput assembles new breachedOutput that can be used by the breach |
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.
assembles new -> assembles a new
breacharbiter.go
Outdated
// deterministically generate a valid witness for each output. This will | ||
// allow the breach arbiter to recover from failures, in the event that | ||
// it must sign and broadcast the justice transaction. | ||
var htlcOutputs = make([]*breachedOutput, nHtlcs) |
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.
Any particular reason for this initialization style? Could instead just be:
htlcOutputs := make([]*breachedOutput, nHtlcs)
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.
Typically I use var
to alert the reader to the fact that the variable is mutable, which helps to logically categorize them (especially in debugging+review) from static references, e.g. :=
. Most often, the scope in which a variable is modified is limited to the following stanza, which permits the reader to assume that following stanza modifies or constructs the mutable variable being introduced. Even if the variable is modified much later in the function, the reader is at least alerted to which variables are intended to be altered dynamically. Counterintuitively, this in fact gives greater confidence as to which variables can be assumed to be static, which reduces working complexity and cognitive load on behalf of the reader. Otherwise, one more or less has to assume that any variable assigned with :=
could be mutable. There are certainly exceptions, but in general I've found it to be a very helpful form of self-documentation. Happy to change if we prefer the brevity of define+assign.
breacharbiter.go
Outdated
) | ||
} | ||
|
||
// TODO(conner) remove dependency on channel snapshot after decoupling |
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.
Minor nit on the TODO's added in this commit/PR: customarily in the project the format we use is TODO(nick):
(notice the colon there). If we all use the same format, then it'll be easy for us to grep through later as we only have a single pattern to look for.
Plus, my current vimconfig detects this pattern and high-lights it distinctly ^_^
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.
Noted. 😂
breacharbiter.go
Outdated
// witness_header_size. | ||
var txWeight uint64 = 4*53 + 2 | ||
// Add receiver script witness and tx input | ||
txWeight += 325 + 4*41 |
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.
Similar comment here to that above: line spacing and magic numbers.
breacharbiter.go
Outdated
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of | ||
// spendable outputs by sweeping the funds into a single p2wkh output. | ||
func (b *breachArbiter) sweepSpendableOutputsTxn( | ||
txWeight uint64, |
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.
Minor style nit: I think the first param can safely lie on the first line w/o going over the character limit.
breacharbiter.go
Outdated
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of | ||
// spendable outputs by sweeping the funds into a single p2wkh output. | ||
func (b *breachArbiter) sweepSpendableOutputsTxn( | ||
txWeight uint64, |
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.
It seems that this function accounts for the weight of the inputs added (with their witnesses), but not the weight of the added outputs.
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.
To remedy this, we can instead modify the param to be the totalWitnessWeight
alone. Then within this function, we can craft the sweep transaction and compute the total weight of that. The final weight we'll use for fee estimation is the sum of both of these computed weights.
|
||
return sweepTx, nil | ||
return txn, nil |
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.
Before returning transaction for singing, we should ensure that the transaction itself is sane. We can use blockchain.CheckTransactionSanity
for this purpose. This'll catch things like a negative output due to a very high estimated fee.
In general whenever crafting transactions like this, we'll need to ensure that we don't try to create negative valued outputs, the CheckTransactionSanity
function can do this job for us.
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.
Didn't realize this was available, makes a lot of sense! :)
breacharbiter.go
Outdated
|
||
// With the fee calculated, we can now create the transaction using the | ||
// information gathered above and the provided retribution information. | ||
var txn = wire.NewMsgTx(2) |
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.
tnx := wire.NewMsgTx(2)
b4a163e
to
2351449
Compare
breacharbiter.go
Outdated
|
||
// waitForPendingCloseConfs dispatches confirmation notification subscribers | ||
// that mark any pending channels as fully closed when signaled. | ||
func (b *breachArbiter) waitForPendingCloseConfs(currentHeight int32) error { |
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.
The name wiatFor..
indicates that the method is synchronous, maybe call it watchForPendingCloseConfs
breacharbiter.go
Outdated
// First, record the breach information and witness type for the local | ||
// channel point. This will allow us to completely generate a valid | ||
// witness in the event of failures, as it will be persisted in the | ||
// retribution store. Here we use CommitmentNoDelay since |
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.
since..?
breacharbiter.go
Outdated
@@ -1207,8 +1338,7 @@ func (bo *breachedOutput) Encode(w io.Writer) error { | |||
return err | |||
} | |||
|
|||
if err := lnwallet.WriteSignDescriptor( | |||
w, &bo.signDescriptor); err != nil { | |||
if err := lnwallet.WriteSignDescriptor(w, bo.signDesc); err != nil { |
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.
if line is too long, then maybe
err := lnwallet.WriteSignDescriptor(w, bo.signDesc)
if err != nil {
is preferred?
breacharbiter_test.go
Outdated
@@ -1,3 +1,5 @@ | |||
// +build !rpctest |
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 thought this flag was for integration tests?
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.
Correct! This one is saying to not include the unit tests in this file when compiling the integration tests, note the exclaimation point :)
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.
ah, right XD
061f5b7
to
026ddd6
Compare
breacharbiter.go
Outdated
Store RetributionStore | ||
|
||
// GenSweepScript generates the receiving scripts for swept outputs. | ||
GenSweepScript func(lnwallet.WalletController) ([]byte, error) |
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.
Don't think this actually needs to take a WalletController
as an argument. The function itself can simply close over the WalletController
and take no arguments.
breacharbiter.go
Outdated
// HtlcSwitch gives the breach arbiter access to shutdown any channel | ||
// links for which it detects a breach, ensuring no further activity | ||
// will continue across the link. | ||
HtlcSwitch *htlcswitch.Switch |
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.
We don't need the entire switch. Instead, we can simply take something with identical arguments to CloseLink
.
breacharbiter.go
Outdated
|
||
// Wallet facilitates sweep transaction publication and further | ||
// facilitates the generation of receiver scripts when sweeping funds. | ||
Wallet *lnwallet.LightningWallet |
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.
The Wallet
itself can similarly be omitted. From the PoV of the breach arb, the only functionality it needs from the wallet is `PublishTransaction.
5fad2d9
to
4449a0e
Compare
a8f7ad5
to
dd56302
Compare
This commit alters the existing retribution post breach conf test case with the intention of testing the correct response in event that the remote party broadcasts a prior state while HTLCs have been extended. This serves as a preliminary integration for an expansion of the breach arbiter integration tests. The primary change involves using the new htlchodl mode for debugging, which causes the remote peer to ignore any intent to settle incoming HTLCs. The result is that any payments sent to the remote party are held in limbo, allowing us to test for these conditions more accurately. Currently the test case only tests that the justice transaction is mined. After we have fully integrated the breach arbiter to sweep 2nd layer HTLCs, this test will be altered to check for spends from the appropriate inputs.
This commit also adds a BreachConfig to abstract the instantiation of the breach arbiter, as well as various formatting improvements.
This commit also adds an incoming flag to HtlcRetribution struct to allow the breach arbiter to generate the appropriate witness based on the htlc's directionality. It also ensures that the size of the htlc retribution slice is now determined by the size of the number of htlcs present in the revoked snapshot, which fixes a minor bug that could lead to nil pointer deferences.
dd56302
to
ec288dd
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 🎉
This PR builds upon the initial work of #241 by enabling the breach arbiter to sweep 2nd layer HTLCs. In it's current state, this PR:
Looking into a fix for issue #256 as this seems like the appropriate time to resolve this. A fix has been implemented, though it will be added in a separate PR to keep this one from spreading further.
This PR builds upon #308 which adds HodlHTLC mode. The modified integration test (
testRevokedCloseRetributionRemoteHodl
) requires the remote node to be started with the--htlchodl
flag in order to properly test that we are sweeping extended-but-unsettled HTLCs.