Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rfq/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ func createChannelWithCustomData(t *testing.T, id asset.ID, localBalance,
),
},
nil, nil, lnwallet.CommitAuxLeaves{},
false,
),
OpenChan: *tpchmsg.NewOpenChannel(
[]*tpchmsg.AssetOutput{
Expand Down
8 changes: 6 additions & 2 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,9 @@ func (s *Server) FetchLeavesFromView(

// 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.FetchLeavesFromView(s.chainParams, in)
return tapchannel.FetchLeavesFromView(
s.chainParams, in, s.cfg.AuxChanNegotiator,
)
}

// FetchLeavesFromCommit attempts to fetch the auxiliary leaves that
Expand Down Expand Up @@ -889,7 +891,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,
Copy link
Member

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).

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

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

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.

Why not? We generate that blob ourselves. If say someone changes that information to say that there's only 5 beefbux in a channel but we had 10, then we'd act on that information. You're correct that atm it isn't directly cryptographically authenticated, but we assume that the information we wrote is correct.

We can protect against downgrading if we store the current negotiated feature bit information in the funding blob. If we come online, and the peer tells us we're not using the new channel type, then we can fail to proceed.

Copy link
Member

Choose a reason for hiding this comment

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

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.

Yep, you're correct about that. We only call that when accepting or creating a new commitment.

)
}

// InlineParseCustomData replaces any custom data binary blob in the given RPC
Expand Down
2 changes: 2 additions & 0 deletions tapcfg/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
AssetSyncer: addrBook,
FeatureBits: lndFeatureBitsVerifier,
IgnoreChecker: ignoreCheckerOpt,
AuxChanNegotiator: auxChanNegotiator,
ErrChan: mainErrChan,
},
)
Expand Down Expand Up @@ -619,6 +620,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
GroupVerifier: groupVerifier,
ChainBridge: chainBridge,
IgnoreChecker: ignoreCheckerOpt,
AuxChanNegotiator: auxChanNegotiator,
},
)
auxSweeper := tapchannel.NewAuxSweeper(
Expand Down
2 changes: 1 addition & 1 deletion tapchannel/auf_leaf_signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func setupAuxLeafSigner(t *testing.T, numJobs int32) (*AuxLeafSigner,
}

com := cmsg.NewCommitment(
nil, nil, outgoingHtlcs, nil, lnwallet.CommitAuxLeaves{},
nil, nil, outgoingHtlcs, nil, lnwallet.CommitAuxLeaves{}, false,
)
cancelChan := make(chan struct{})

Expand Down
32 changes: 30 additions & 2 deletions tapchannel/aux_closer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/proof"
"github.com/lightninglabs/taproot-assets/tapchannelmsg"
"github.com/lightninglabs/taproot-assets/tapfeatures"
"github.com/lightninglabs/taproot-assets/tapfreighter"
"github.com/lightninglabs/taproot-assets/tapgarden"
"github.com/lightninglabs/taproot-assets/tappsbt"
Expand Down Expand Up @@ -66,6 +67,11 @@ type AuxChanCloserCfg struct {
// IgnoreChecker is an optional function that can be used to check if
// a proof should be ignored.
IgnoreChecker lfn.Option[proof.IgnoreChecker]

// AuxChanNegotiator is responsible for producing the extra tlv blob
// that is encapsulated in the init and reestablish peer messages. This
// helps us communicate custom feature bits with our peer.
AuxChanNegotiator *tapfeatures.AuxChannelNegotiator
}

// assetCloseInfo houses the information we need to finalize the close of an
Expand Down Expand Up @@ -454,11 +460,21 @@ func (a *AuxChanCloser) AuxCloseOutputs(
"packets: %w", err)
}

features := a.cfg.AuxChanNegotiator.GetChannelFeatures(
lnwire.NewChanIDFromOutPoint(desc.ChanPoint),
)
supportSTXO := features.HasFeature(tapfeatures.STXOOptional)

var opts []tapsend.OutputCommitmentOption
if !supportSTXO {
opts = append(opts, tapsend.WithNoSTXOProofs())
}

// With the outputs prepared, we can now create the set of output
// commitments, then with the output index locations known, we can set
// the output indexes in the allocations.
outCommitments, err := tapsend.CreateOutputCommitments(
vPackets, tapsend.WithNoSTXOProofs(),
vPackets, opts...,
)
if err != nil {
return none, fmt.Errorf("unable to create output "+
Expand Down Expand Up @@ -733,10 +749,22 @@ func (a *AuxChanCloser) FinalizeClose(desc chancloser.AuxCloseDesc,
closeInfo.allocations,
)

features := a.cfg.AuxChanNegotiator.GetChannelFeatures(
lnwire.NewChanIDFromOutPoint(desc.ChanPoint),
)
supportSTXO := features.HasFeature(
tapfeatures.STXOOptional,
)

var opts []proof.GenOption
if !supportSTXO {
opts = append(opts, proof.WithNoSTXOProofs())
}

proofSuffix, err := tapsend.CreateProofSuffixCustom(
closeTx, vPkt, closeInfo.outputCommitments,
outIdx, closeInfo.vPackets, exclusionCreator,
proof.WithNoSTXOProofs(),
opts...,
)
if err != nil {
return fmt.Errorf("unable to create proof "+
Expand Down
78 changes: 68 additions & 10 deletions tapchannel/aux_funding_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/lightninglabs/taproot-assets/rfq"
cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg"
"github.com/lightninglabs/taproot-assets/tapdb"
"github.com/lightninglabs/taproot-assets/tapfeatures"
"github.com/lightninglabs/taproot-assets/tapfreighter"
"github.com/lightninglabs/taproot-assets/tapgarden"
"github.com/lightninglabs/taproot-assets/tappsbt"
Expand All @@ -41,6 +42,7 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/msgmux"
"github.com/lightningnetwork/lnd/routing/route"
)

const (
Expand Down Expand Up @@ -269,6 +271,11 @@ type FundingControllerCfg struct {
// a proof should be ignored.
IgnoreChecker lfn.Option[proof.IgnoreChecker]

// AuxChanNegotiator is responsible for producing the extra tlv blob
// that is encapsulated in the init and reestablish peer messages. This
// helps us communicate custom feature bits with our peer.
AuxChanNegotiator *tapfeatures.AuxChannelNegotiator

// ErrChan is used to report errors back to the main server.
ErrChan chan<- error
}
Expand Down Expand Up @@ -419,6 +426,8 @@ type pendingAssetFunding struct {

initiator bool

stxo bool

amt uint64

pushAmt btcutil.Amount
Expand Down Expand Up @@ -462,14 +471,32 @@ func (p *pendingAssetFunding) assetOutputs() []*cmsg.AssetOutput {
}

// addToFundingCommitment adds a new asset to the funding commitment.
func (p *pendingAssetFunding) addToFundingCommitment(a *asset.Asset) error {
func (p *pendingAssetFunding) addToFundingCommitment(a *asset.Asset,
stxo bool) error {

newCommitment, err := commitment.FromAssets(
fn.Ptr(commitment.TapCommitmentV2), a,
)
if err != nil {
return fmt.Errorf("unable to create commitment: %w", err)
}

// If our peer supports STXO we go ahead and append the
// appropriate alt leaves to the VOutput.
if stxo {
altLeaves, err := asset.CollectSTXO(a)
if err != nil {
return err
}

err = newCommitment.MergeAltLeaves(altLeaves)
if err != nil {
return err
}

p.stxo = stxo
}

newCommitment, err = commitment.TrimSplitWitnesses(
&newCommitment.Version, newCommitment,
)
Expand Down Expand Up @@ -524,7 +551,8 @@ func (p *pendingAssetFunding) addInputProofChunk(
func newCommitBlobAndLeaves(pendingFunding *pendingAssetFunding,
lndOpenChan lnwallet.AuxChanState, assetOpenChan *cmsg.OpenChannel,
keyRing lntypes.Dual[lnwallet.CommitmentKeyRing],
whoseCommit lntypes.ChannelParty) ([]byte, lnwallet.CommitAuxLeaves,
whoseCommit lntypes.ChannelParty,
stxo bool) ([]byte, lnwallet.CommitAuxLeaves,
error) {

chanAssets := assetOpenChan.FundedAssets.Val.Outputs
Expand Down Expand Up @@ -559,6 +587,7 @@ func newCommitBlobAndLeaves(pendingFunding *pendingAssetFunding,
// needs the sum of the remote+local assets, so we'll populate that.
fakePrevState := cmsg.NewCommitment(
localAssets, remoteAssets, nil, nil, lnwallet.CommitAuxLeaves{},
stxo,
)

// Just like above, we don't have a real HTLC view here, so we'll pass
Expand All @@ -571,6 +600,7 @@ func newCommitBlobAndLeaves(pendingFunding *pendingAssetFunding,
fakePrevState, lndOpenChan, assetOpenChan, whoseCommit,
localSatBalance, remoteSatBalance, fakeView,
pendingFunding.chainParams, keyRing.GetForParty(whoseCommit),
stxo,
)
if err != nil {
return nil, lnwallet.CommitAuxLeaves{}, err
Expand Down Expand Up @@ -613,12 +643,14 @@ func (p *pendingAssetFunding) toAuxFundingDesc(req *bindFundingReq,
// This will be the information for the very first state (state 0).
localCommitBlob, localAuxLeaves, err := newCommitBlobAndLeaves(
p, req.openChan, openChanDesc, req.keyRing, lntypes.Local,
p.stxo,
)
if err != nil {
return nil, err
}
remoteCommitBlob, remoteAuxLeaves, err := newCommitBlobAndLeaves(
p, req.openChan, openChanDesc, req.keyRing, lntypes.Remote,
p.stxo,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1086,14 +1118,19 @@ func (f *FundingController) signAllVPackets(ctx context.Context,
// complete, but unsigned PSBT packet that can be used to create out asset
// channel.
func (f *FundingController) anchorVPackets(fundedPkt *tapsend.FundedPsbt,
allPackets []*tappsbt.VPacket) ([]*proof.Proof, error) {
allPackets []*tappsbt.VPacket, stxo bool) ([]*proof.Proof, error) {

log.Infof("Anchoring funding vPackets to funding PSBT")

var opts []tapsend.OutputCommitmentOption
if !stxo {
opts = append(opts, tapsend.WithNoSTXOProofs())
}

// Given the set of vPackets we've created, we'll now merge them all to
// create a map from output index to final tap commitment.
outputCommitments, err := tapsend.CreateOutputCommitments(
allPackets, tapsend.WithNoSTXOProofs(),
allPackets, opts...,
)
if err != nil {
return nil, fmt.Errorf("unable to create new output "+
Expand Down Expand Up @@ -1122,11 +1159,16 @@ func (f *FundingController) anchorVPackets(fundedPkt *tapsend.FundedPsbt,
for idx := range allPackets {
vPkt := allPackets[idx]

var opts []proof.GenOption
if !stxo {
opts = append(opts, proof.WithNoSTXOProofs())
}

for vOutIdx := range vPkt.Outputs {
proofSuffix, err := tapsend.CreateProofSuffix(
fundedPkt.Pkt.UnsignedTx, fundedPkt.Pkt.Outputs,
vPkt, outputCommitments, vOutIdx, allPackets,
proof.WithNoSTXOProofs(),
opts...,
)
if err != nil {
return nil, fmt.Errorf("unable to create "+
Expand Down Expand Up @@ -1219,7 +1261,8 @@ func (f *FundingController) sendAssetFundingCreated(ctx context.Context,
// ultimately broadcasting the funding transaction.
func (f *FundingController) completeChannelFunding(ctx context.Context,
fundingState *pendingAssetFunding,
fundedVpkt *tapfreighter.FundedVPacket) (*wire.OutPoint, error) {
fundedVpkt *tapfreighter.FundedVPacket,
stxoEnabled bool) (*wire.OutPoint, error) {

log.Debugf("Finalizing funding vPackets and PSBT...")

Expand Down Expand Up @@ -1330,7 +1373,7 @@ func (f *FundingController) completeChannelFunding(ctx context.Context,
// PSBT. This'll update all the pkScripts for our funding output and
// change.
fundingOutputProofs, err := f.anchorVPackets(
finalFundedPsbt, signedPkts,
finalFundedPsbt, signedPkts, stxoEnabled,
)
if err != nil {
return nil, fmt.Errorf("unable to anchor vPackets: %w", err)
Expand Down Expand Up @@ -1545,11 +1588,17 @@ func (f *FundingController) processFundingMsg(ctx context.Context,
"proof: %w", err)
}

features := f.cfg.AuxChanNegotiator.GetPeerFeatures(
route.Vertex(msg.PeerPub.SerializeCompressed()),
)

supportSTXO := features.HasFeature(tapfeatures.STXOOptional)

// If we reached this point, then the asset output and all
// inputs are valid, so we'll store the funding asset
// commitment.
err = assetFunding.addToFundingCommitment(
&assetProof.AssetOutput.Val,
&assetProof.AssetOutput.Val, supportSTXO,
)
if err != nil {
return tempPID, fmt.Errorf("unable to create "+
Expand Down Expand Up @@ -1738,6 +1787,15 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex,
maxNumAssetIDs)
}

// Now let's see if we should be using STXOs for this channel funding.
features := f.cfg.AuxChanNegotiator.GetPeerFeatures(
route.Vertex(fundReq.PeerPub.SerializeCompressed()),
)

supportSTXO := features.HasFeature(tapfeatures.STXOOptional)

fundingState.stxo = supportSTXO

// Now that we know the final funding asset root along with the splits,
// we can derive the tapscript root that'll be used alongside the
// internal key (which we'll only learn from lnd later as we finalize
Expand All @@ -1750,7 +1808,7 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex,
}

err = fundingState.addToFundingCommitment(
fundingOut.Asset.Copy(),
fundingOut.Asset.Copy(), supportSTXO,
)
if err != nil {
return fmt.Errorf("unable to add asset to funding "+
Expand Down Expand Up @@ -1814,7 +1872,7 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex,
}

chanPoint, err := f.completeChannelFunding(
fundReq.ctx, fundingState, fundingVpkt,
fundReq.ctx, fundingState, fundingVpkt, supportSTXO,
)
if err != nil {
// If anything went wrong during the funding process,
Expand Down
Loading
Loading