-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
chanfunding: create new package to abstract over funding workflows #3659
chanfunding: create new package to abstract over funding workflows #3659
Conversation
The final diff looks larger than it actually is due to the code movement that has taken place. |
Haven't started review yet, but there are a few linter failures causing Travis to fail. |
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.
Did an initial pass, and I like the direction we are goin in with the interfaces. I still think we can extract even more of the logic behind the interfaces, such that the subsystems can have even less assumptions about the used Assembler
.
Also I think several of the commits won't compile.
498fa47
to
e02289e
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.
Very nice abstraction! It's great to see this push towards external funding of channels.
I did a high-level review, mostly to understand what the goal with the Assembler
is. Left some nits along the way.
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 found the PR a bit involved to follow and review due to the commit structure, though it may be too late to address this now.
// OutpointLocker allows a caller to lock/unlock an outpoint. When locked, the | ||
// outpoints shouldn't be used for any sort of channel funding of coin | ||
// selection. Locked outpoints are not expect to be persisted between restarts. | ||
type OutpointLocker interface { |
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.
Not a blocker for this PR, but some of these interfaces are already defined within sweep/walletsweep.go
. Do we plan to import these there?
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.
They were purposefully copied over as they're pretty small interfaces, so would rather not create additional dependancies. It's also the case that if the interfaces in the sweeper are used, then a circular dependency cycle is created.
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.
There's no circular dependency here since this is the chanfunding
package?
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 also agree that it is nice for a package to define its own set of interfaces, since it has it own set of requirements separate from the other package that might diverge in the future.
) | ||
if err != nil { | ||
selected.unlockCoins() | ||
if fundingIntent != nil { | ||
fundingIntent.Cancel() |
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.
Shouldn't we also do these for the other steps, e.g., handleContributionMsg
?
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 free up the coins? If a reservation is failed, and doesn't proceed, then they'll be reclaimed via the zombie reservation sweeper within the fundingManager
.
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.
Following that logic, why bother doing it here then? Seems inconsistent.
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.
could do it in all FailFundingFlow
cases perhaps
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's done here optimistically to immediately clean up any coins for a funding request that hasn't even advanced passed the reservation phase. failFundingFlow
already does this (in response to another error, or an invalid funding workflow message by the remote peer) since it calls Cancel
on the reservation object, which will cancel and remove the intent.
e02289e
to
abfa7f0
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, commit structure improved! Was able to follow the changes more easily.
Some commits are probably not in the right order, some of them don't compile.
Also the unit tests fail:
interface_test.go:491: unable to consume alice's sigs: input to funding tx does not exist: target output was not found
@@ -166,6 +171,12 @@ func (c ChannelType) IsTweakless() bool { | |||
return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit | |||
} | |||
|
|||
// HasFundingTx returns true if this channel type is one that has a funding | |||
// transaction stored locally. | |||
func (c ChannelType) HasFundingTx() bool { |
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.
This might actually be a bit dangerous, as old channels which we are not initiator will not have the funding tx on disk, but also the bit won't be set.
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 the bit isn't set, then that means we have the funding transaction. If it's set, then it means we don't. As written, if we're about to attempt to handle the funding transaction, we also check if we're the initiator or not.
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 the confusion arises from the method name. HasFundingTx
makes it sound like it's the only check needed to retrieve the transaction, perhaps something like CraftedExternally
is better?
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 also check if we're the initiator or not.
Yeah, this needs to be done which is why I think HasFundingTx
is a dangerous name, as there is no guarantee we have it even though it returns true.
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 guess it makes sense since it is the channel type that has the funding tx, not the channel. I'm okay with keeping the name, but adding to the godoc that it is only the initiator that has the tx strored locally, but I agree with @wpaulino that a more descriptive name could lessen the confusion.
abfa7f0
to
3711cf5
Compare
The issue in the tests should be fixed now, see the last commit w.r.t the exact situation ( |
0811963
to
d2dc3e0
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.
I did another pass and the changes look solid to me 🔥
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.
Code LGTM, left a few minor comments.
@@ -166,6 +171,12 @@ func (c ChannelType) IsTweakless() bool { | |||
return c&SingleFunderTweaklessBit == SingleFunderTweaklessBit | |||
} | |||
|
|||
// HasFundingTx returns true if this channel type is one that has a funding | |||
// transaction stored locally. | |||
func (c ChannelType) HasFundingTx() bool { |
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 the confusion arises from the method name. HasFundingTx
makes it sound like it's the only check needed to retrieve the transaction, perhaps something like CraftedExternally
is better?
// OutpointLocker allows a caller to lock/unlock an outpoint. When locked, the | ||
// outpoints shouldn't be used for any sort of channel funding of coin | ||
// selection. Locked outpoints are not expect to be persisted between restarts. | ||
type OutpointLocker interface { |
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.
There's no circular dependency here since this is the chanfunding
package?
) | ||
if err != nil { | ||
selected.unlockCoins() | ||
if fundingIntent != nil { | ||
fundingIntent.Cancel() |
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.
Following that logic, why bother doing it here then? Seems inconsistent.
testName := fmt.Sprintf("%v/%v:%v", walletType, backEnd, | ||
walletTest.name) | ||
success := t.Run(testName, func(t *testing.T) { | ||
if backEnd == "neutrino" && | ||
strings.Contains(walletTest.name, "dual funder") { |
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.
Nit: just add backend check in the test case itself?
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.
AFAICT, the test doesn't know which backend is being used. The config for btcwallet
is a private variable so the test can't access that to perform a type assertion, I tried that route before this one.
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 this is coming together now, and after also looking at the follow-up PR I see the big picture of this change.
However, I still think there are still significant cleanup that can be made, by removing reservation logic from the wallet that is now all handled by the ChanFunder
. Since the the funding transaction crafting now is handled by the ChanFunder
, keys, signatures, funding inputs etc can be opaque to the reservation logic, as it should be abstracted away by the assembler interface (it already is for the external funding case). That would get us on a much needed path towards simplifying the reservation logic :)
// OutpointLocker allows a caller to lock/unlock an outpoint. When locked, the | ||
// outpoints shouldn't be used for any sort of channel funding of coin | ||
// selection. Locked outpoints are not expect to be persisted between restarts. | ||
type OutpointLocker interface { |
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 also agree that it is nice for a package to define its own set of interfaces, since it has it own set of requirements separate from the other package that might diverge in the future.
IMO this is out of scope for this PR, I purposefully kept that logic in place in order to not have the PR be too large. The reservation logic is still the bridge between our internal representatio+provisioning of the various channel parmaters (keys, csv delay, sigs, etc) and how things are manifested on the p2p layer (openchannel, acceptchannel, etc). This PR is only about abstracting away how the funding transaction and channel point are obtained by the wallet, and nothing more. |
664af73
to
1227a57
Compare
In this commit, we add a new bit to the existing ChannelType bitfield. If this bit is set, then it signals that we have the funding transaction stored on disk. A future change will enable lnd to have the funding transaction be constructed externally, allowing for things like funding from a hardware wallet, or a channel created as a sub-branch within an existing channel factory.
…ackage In this commit, we make an incremental change to move the existing coin selection code into a new chanfunding package. In later commits, this package will grow to serve all the lower level channel funding needs in the daemon.
We also remove some related and also unused attributes as well along the way.
In this commit, we introduce a series of new abstractions for channel funding. The end goal is to enable uses cases that construct the funding transaction externally, eventually handing the funding outpoint to lnd. An example of such a use case includes channel factories and external channel funding using a hardware wallet. We also add a new chanfunding.Assembler meant to allow external channel funding in contexts similar to how channel factories can be constructed. With this channel funder, we'll only obtain the channel point and funding output from it, as this alone is enough to carry out a funding flow as normal.
…ackage In this commit, we begin to integrate the new channel funding package into the existing codebase. With this set of changes, we'll no longer construct and sign the funding transaction within this package, instead delegating it to the new chanfunding package. We use the new chanfunding.WalletAssembler to carry out all channel funding, providing it with an implementation of all its interfaces backed by the wallet.
In this commit, we make the wallet aware of the second type of funding intent: the ShimIntent. If we have one of these, then we don't need to construct the funding transaction, and can instead just obtain the outpoint directly from it.
…ntext In this commit, we start to thread the pending channel ID from wire protocol all the way down into the reservation context. This change will allow negotiation to take place _outside_ the protocol that may result in a particular chanfunding.Assembler being dispatched.
In this commit, we add a new method `RegisterFundingIntent` that allows a caller to "inject" a pre-populated chanfunding.Intent into a funding workflow. As an example, if we've already agreed upon the "shape" of the funding output _outside_ the protocol, then we can use this to pass down the details of the output, then leverage the normal wire protocol to carry out the remainder of the funding flow.
…eutrino In this commit, we fix a long standing bug within the newly created `verifyFundingInputs` method. Before this commit, the method would attempt to derive the pkScript by looking at the last items on the witness stack, and making a p2wsh output script from that. This is incorrect as typically non of these scripts will actually be p2wsh, and instead will be p2wkh. We fix this by using the newly available `txscript.ComputePkScript` method to derive the proper pkScript. This resolves an issue w.r.t passing incorrect arguments for all backends, but an issue still stands for the neutrino backend. As is, we pass a height hint of zero into the `GetUtxo` method call. With the way the current utxo scanner is set up for neutrino, this'll cause it to never find the UTXO, as it takes the height hint as a UTXO birth height, rather than a lower bound of the birth of the UTXO.
1227a57
to
b1940d6
Compare
Since this is merged, it seems #3772 should now be possible. Looking forward to that. |
In this commit, we create a new package
chanfunding
, that introduces a number of abstractions to our funding workflow. The base implementation of the core new abstraction (chanfunding.Assembler
), ischanfunding.WalletAssembler
. Construction of this concrete implementation of the new interfaces involved heavily refactoring much of the code withinwallet.go
. As a result, much of the channel funding code now lives outside of thelnwallet
package.The second instance of a new channel funder introduced in this PR is the
chanfunding.CannedAssembler
. This channel funder is used in the scenario wherein the "shape" of the funding output is agreed upon outside the normal protocol, while the construction of the commitment transaction and so forth are carried out as normal. Instances where this might be the case include variants of channel factory leaf channel construction, and external hardware wallet based channel funding.This PR is the 3rd in a series of PRs that revamps the way channel funding works within
lnd
to allow funding transactions to be constructed externally, with the grand goal of a PSBT (safety first!!!) based channel funding workflow.