-
Notifications
You must be signed in to change notification settings - Fork 133
Prepare tap channels for v1 proofs #1779
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
base: feature-bits
Are you sure you want to change the base?
Conversation
6b5fffb
to
6fdc32e
Compare
74d98bf
to
611a9fc
Compare
6fdc32e
to
87174c6
Compare
LiT tests are expected to fail, the related LiT PR is here |
tapsend/send.go
Outdated
@@ -995,7 +1013,7 @@ func CreateOutputCommitments(packets []*tappsbt.VPacket, | |||
|
|||
// commitPacket creates the output commitments for a virtual packet and merges | |||
// it with the existing commitments for the anchor outputs. | |||
func commitPacket(vPkt *tappsbt.VPacket, noSTXOProofs bool, | |||
func commitPacket(vPkt *tappsbt.VPacket, noSTXOProofs, existingSTXOLeaves 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.
Perhaps this should take a mini options/config struct? A common bug that can arise with two side-by-side params w/ the same type is that a caller accidentally passes them in w/ the wrong order.
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 that commitPacket
is only a helper used within this package, with just one call site, I'll just wrap the two flags in a config struct to make sure the field name shows up next to the value, in order to prevent such mistakes
tapsend/send.go
Outdated
|
||
// Verify that the new set of alt leaves has | ||
// unique keys. | ||
err := asset.AddLeafKeysVerifyUnique( |
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.
What about adding this protection, or assertion at a higher level?
When can this happen (non-unique STXO leaves passed in) within the context of the protocol?
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.
When funding a channel we need to "prematurely" create and include the alt leaves on the commitment because LND will cache the tapscript for future reference. That's the only case where we pre-generate the alt leaves, and that's why we added the option here for this "soft" check.
Generally we want to error out on alt leaf key conflict, but for this specific case we want to let this tool know that they may already exist -- in which case we only append the new leaves if no conflict occurs.
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 rationale behind adding the option is to be able to re-use the commitPacket
helper even from the chan funding call site, where the above special conditions are met.
tapsend/send.go
Outdated
vOut.AltLeaves = append( | ||
vOut.AltLeaves, stxoAssets..., | ||
) | ||
} |
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 actually handle the error case here, so what good is this 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.
If there was a leaf key conflict we avoid touching the existing alt leaves. We only append if the set contains unique keys -- the err == nil
case.
Could change to instead keep just the unique values, but didn't want to overengineer it.
@@ -889,7 +892,9 @@ func (s *Server) ApplyHtlcView( | |||
|
|||
// The aux leaf creator is fully stateless, and we don't need to wait | |||
// for the server to be started before being able to use it. | |||
return tapchannel.ApplyHtlcView(s.chainParams, in) | |||
return tapchannel.ApplyHtlcView( | |||
s.chainParams, in, s.cfg.AuxChanNegotiator, |
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.
First thought at seeing this change: do we actually need to propagate this all the way here just to do something like fetch the set of aux leaves, or to apply an HTLC view?
Even before a channel is created, we know if we're going to use the STXO feature and also noop feature (for new channels). Therefore, when preparing the funding and commitment blobs, we can just store this information right there and there. So then we don't need to query for feature bits each time we want to make a new commitment or validate one from the remote peer.
The other dependnacy that pops up here is: these methods needs to always work, even if we're not currently connected to a peer. IIUC, this new aux negotiator interface can only query live peer state (what did they send us in init
, etc).
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.
Therefore, when preparing the funding and commitment blobs, we can just store this information right there and there
Where exactly are you referring to? If you're referring to the funding/commit blobs then we can't really rely on those values for future decisions. Given that (at least for a short time window) downgrading from v1 will be allowed, then we need to know what the latest state of our peer is. Last commitment could have been using v1, but then upon reconnection our peer could stop supporting it.
The other dependnacy that pops up here is: these methods needs to always work, even if we're not currently connected to a peer. IIUC, this new aux negotiator interface can only query live peer state (what did they send us in init, etc).
I think for ApplyHTLCView
we know that the channel must be active for the method to be called, which means we have communicated/agreed upon feature bits with corresponding peer. You're right for some of these cases that we could skip using the AuxChanNegotiator and rely on the commit blob, will change those hooks accordingly.
Generally, if the method affects the creation of the next commitment we'll want to consult the negotiator. If it just cares about some existing commitment, we could look up the flag in the blob.
tapchannel/aux_leaf_creator.go
Outdated
@@ -50,10 +65,16 @@ func FetchLeavesFromView(chainParams *address.ChainParams, | |||
"commit state: %w", err)) | |||
} | |||
|
|||
supportsSTXO := bitFetcher.GetChannelFeatures( |
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.
In this case, we have asccess to the blobs that we have lnd
store (funding + commitment), so we can just read that state.
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.
Same goes for all the other instances in this commit.
IMO, we only need to query this the very first time when we create a channel.
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.
Thinking about things anew, I think the ideal implementation here would actually set a feature bit in the channel type message. Otherwise, we're falling back to the old implicit channel type negotiation, which we wanted to get away from by making the type of the channel being negotiated explicit.
With this route, an extension point would actually go near the funding manager in lnd, which is what decides if we understand a feature bit combo to open a new channel type.
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.
See #1779 (comment), focusing on closing remark
features := a.cfg.AuxChanNegotiator.GetChannelFeatures( | ||
lnwire.NewChanIDFromOutPoint(desc.ChanPoint), | ||
) | ||
supportSTXO := features.HasFeature(tapfeatures.STXOOptional) |
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.
Same here, we already know if the channel was created with STXO support or not.
Or is the idea that if they update after the fact, we still want to use stxos in the new 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.
At the time of running the above code lines we're crafting the coop close transaction. Both parties need to agree on whether they use v1 or not in order to craft the same transaction.
Could be the case that we used v0 for the whole lifecycle of the channel, then peer updated right before coop closing.
|
||
// STXO is a flag indicating whether this commitment supports stxo | ||
// proofs. | ||
STXO tlv.RecordT[tlv.TlvType5, 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.
Cool, so if we're using this here, then I think we can remove many of the perior instances in the earlier commits where we're doing queries on feature bits after channel funding.
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 really. This entry is juts a historical entry.
The fact that the last commit used STXO doesn't mean that the next commit will use STXO too. Our peer could downgrade in the meantime.
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 above entry was only really introduced for our contract resolver
taproot-assets/tapchannel/aux_sweeper.go
Line 2498 in 87174c6
req.resp <- a.resolveContract(req.req) |
specifically the part where we import the commit tx:
taproot-assets/tapchannel/aux_sweeper.go
Line 1588 in 87174c6
supportSTXO := commitState.STXO.Val |
3d4c18f
to
f9e10d7
Compare
We change the commitment related generations to now include stxo proofs if the setting allows so. Previously we would always exclude stxo proofs, but now we add them if both parties understand & expect them.
87174c6
to
24347b7
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.
Did a first pass and mad comments throughout. Starts to look good! This PR depends on the litd itest working, right? (lightninglabs/lightning-terminal#1138)
tapsend/send.go
Outdated
// existingSTXOLeaves indicates whether we should be aware that alt | ||
// leaves that correspond to stxo assets may already exist in asset | ||
// outputs of the vpsbt. | ||
existingSTXOLeaves 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 understand the reason of this boolean and premature tapscript generation that is referenced by lnd
, but I don't like it.
This complexity makes the code harder to reason about and could be fragile. Isn't there a way to establishes a single point of responsibility for adding STXO?
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.
Thanks for this comment.
I was getting ready to write a long explanation on how this isn't a quirk of the commitment related toolkit but a characteristic of the chan funding flow -- i.e we need to craft the correct commitment even though we haven't yet finalized the vpsbt that creates it.
Then I realized that in my latest push, where I simplified a lot of code, I also omitted the (now unnecessary) part of actually appending the alt leaves to the vOut. This means that even though we do collect STXOs prematurely, we don't really attach it to the vOut, leaving it "clean" for the toolkit to handle it. This means that we actually never hit a case where there's duplicate alt leaves, as we'd never really persist them between preparation & finalization phase.
Will just drop the commit that adds WithExistingSTXOLeaves
altogether.
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.
Interesting. But if we don't append them to the vOut
why do we still collect the STXOs prematurely? Where do we use them in the chan funding flow?
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 directly merge them into the commitment that will be used to derive the tapscript for LND. The vOut alt leaves are just the placeholder for keeping the leaves somewhere, until we'd eventually want to merge them into the tap commitment.
So now we simply skip the step of adding the alt leaves to the vOut and later when the helper functions get the vPSBT they'll be free to re-collect/append the alt leaves without conflicts or extra ticks needed
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.
Had some back and forth on this with @GeorgeTsagk and an LLM who will remain anonymous. I'm posting it here for future reference.
- Premature Stage: Funding Transaction Construction (
addToFundingCommitment
)
This stage computes the correctTapscriptRoot
for the on-chain P2TR funding output script. LND needs this root early, before the final proofs are generated.
Call Flow:
FundingController.processFundingReq
->pendingAssetFunding.addToFundingCommitment
Action:addToFundingCommitment
callsasset.CollectSTXO
and merges the resulting leaves directly into an in-memorycommitment.TapCommitment
. This allows for the correctTapscriptRoot
to be provided to LND. This process does not modify anyVPacket
'sAltLeaves
. - Finalization Stage: Final Proof Generation (
commitPacket
)
This stage generates the final Taproot Asset proofs for the assets being created in the channel after the funding transaction is fully known.
Call Flow:
FundingController.processFundingReq
->FundingController.completeChannelFunding
->FundingController.anchorVPackets
->tapsend.CreateOutputCommitments
->commitPacket
Action: ThecommitPacket
function is called on theVPacket
s representing the channel assets. It callsasset.CollectSTXO
again and appends the resulting leaves to thevOut.AltLeaves
slice before creating the final commitment for the proof.
This commit updates the aux funding controller to conditionally use stxo proofs, depending on whether the feature bit was negotiated or not with the remote peer. Our funding commitment will now include the alt leaves for stxos if the flag is set. The funder needs to construct the correct tapscript early on, as LND will query the tapscript root before we reach the finalization phase. That's why we manually calculate and merge the stxo alt leaves in the funding process. The fundee now also needs to calculate and merge the alt leaves that result from the asset outputs, in order to arrive to the same tapscript root.
We extend the commitment blob to also store a flag, indicating whether STXO was active when that commitment was created. This can be useful for future sweeps that need to know whether that commitment used stxo alt leaves, which affects the reconstruction of the tap commitment.
We also add stxo support for the aux leaf creation. This is crucial and needs to be the same across both channel parties. We rely on the consistency of the feature bit for whether we'll include the stxo alt leaves or not. A disagreement here could lead to a force close.
For the cooperative channel closing we also need to check if the parties have agreed upon the usage of stxo proofs. If that is the case, we'll include those commitments in the coop channel closing transaction & proof.
This commit adds stxo support to the aux sweeper. When importing a commit tx to the wallet we now consult the commit blob flag in order to find out whether that commitment was using stxo proofs or not.
24347b7
to
be7349d
Compare
Yeap. Will update the LiT PR soon to reflect all of recent changes made here |
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.
Nearly there! I made some small remarks and some questions.
|
||
log.Infof("Anchoring funding vPackets to funding PSBT") | ||
|
||
// We have collected the stxo leaves for the outputs in a previous step. |
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 comment doesn't make any sense anymore now that we avoided alt leaves collisions anyway.
@@ -1713,6 +1766,15 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex, | |||
} | |||
} | |||
|
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 block should be pushed a few lines down, such that the registration of the defer is still directly below the creation of the unlockLeases
closure.
@@ -24,10 +26,19 @@ const ( | |||
DefaultTimeout = 30 * time.Second | |||
) | |||
|
|||
// FeatureBitFetcher is responsible for fetching feature bits either by | |||
// referencing a remote peer, or an established channel. |
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 comment isn't up to date anymore, now that it's impossible to reference a peer. It only works byu referencing a channel.
outCommitments, err := tapsend.CreateOutputCommitments( | ||
directPkts, tapsend.WithNoSTXOProofs(), | ||
) | ||
outCommitments, err := tapsend.CreateOutputCommitments(directPkts) |
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.
For my understanding: From now on we always create a sweeping Tx with stxo proofs.
Why didn't we do so before? Oli needed the WithNoSTXOProofs
to make the channel commitments work, but it seems to me that the sweeping Tx could always have had stxo proofs. (This is more a question about Oli's PR than about this PR, but I'm still wondering.)
Description
Adds support for v1 proofs for all channel related transactions (funding/commitments/closing/sweeping).
We also guard this feature behind a feature bit, which must be set and supported by both channel peers in order to use v1 proofs.
For some cases during sweeping, we don't really care about the feature being supported by the remote party as we're sweeping locally to our own address.
Fixes #1582