diff --git a/docs/examples/basic-price-oracle/go.mod b/docs/examples/basic-price-oracle/go.mod index eb9690c95..6102b5780 100644 --- a/docs/examples/basic-price-oracle/go.mod +++ b/docs/examples/basic-price-oracle/go.mod @@ -92,11 +92,11 @@ require ( github.com/kkdai/bstream v1.0.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect - github.com/lightninglabs/lndclient v0.18.4-3 // indirect + github.com/lightninglabs/lndclient v0.18.4-5 // indirect github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd // indirect github.com/lightninglabs/neutrino/cache v1.1.2 // indirect github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb // indirect - github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241025090009-615f3d633e61 // indirect + github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241120143113-9246d5c51cd2 // indirect github.com/lightningnetwork/lnd/clock v1.1.1 // indirect github.com/lightningnetwork/lnd/fn v1.2.3 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.5 // indirect diff --git a/docs/examples/basic-price-oracle/go.sum b/docs/examples/basic-price-oracle/go.sum index a38fa5d17..67bfddc1c 100644 --- a/docs/examples/basic-price-oracle/go.sum +++ b/docs/examples/basic-price-oracle/go.sum @@ -418,8 +418,8 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQ github.com/lightninglabs/lightning-node-connect v0.2.5-alpha h1:ZRVChwczFXK0CEbxOCWwUA6TIZvrkE0APd1T3WjFAwg= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 h1:Er1miPZD2XZwcfE4xoS5AILqP1mj7kqnhbBSxW9BDxY= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2/go.mod h1:antQGRDRJiuyQF6l+k6NECCSImgCpwaZapATth2Chv4= -github.com/lightninglabs/lndclient v0.18.4-3 h1:Xk3ZuCQE4ZlF70jaToryL2MvRcryiE0zTfUjJbmzUBY= -github.com/lightninglabs/lndclient v0.18.4-3/go.mod h1:/HLqmZGL9MtP8F1g+laq+L9VrsugBN5tsTct3C5wWCg= +github.com/lightninglabs/lndclient v0.18.4-5 h1:KokX5ZlFuZEmtD7sHWg1cXzee0ZsnBWuSKV9/RcTEv4= +github.com/lightninglabs/lndclient v0.18.4-5/go.mod h1:tafbfrisn1Iwt3em3nYWdE06C8jpoZtpNyiSB485OCg= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd h1:D8aRocHpoCv43hL8egXEMYyPmyOiefFHZ66338KQB2s= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= @@ -428,8 +428,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241025090009-615f3d633e61 h1:EcBM2tz+iyspYRFaDVjUe5a2bkuBWFxOWD2mzdCraUc= -github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241025090009-615f3d633e61/go.mod h1:q2DlXwj6ev8TMbo+CvfJ3BIrqw42HFM/fSBoyCFrjdc= +github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241120143113-9246d5c51cd2 h1:zGnSH1gTpPA637465d5tp7VkdWw5sVyWZxxmfZ0rKo4= +github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241120143113-9246d5c51cd2/go.mod h1:nPRQzLla5uHPQFyyZn8r9Vgddkd23PBUDa9rggEPOfY= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= github.com/lightningnetwork/lnd/fn v1.2.3 h1:Q1OrgNSgQynVheBNa16CsKVov1JI5N2AR6G07x9Mles= diff --git a/go.mod b/go.mod index 9963af80f..fa966f1a4 100644 --- a/go.mod +++ b/go.mod @@ -26,9 +26,9 @@ require ( github.com/lib/pq v1.10.9 github.com/lightninglabs/aperture v0.3.2-beta.0.20241015115230-d59b5514c19a github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 - github.com/lightninglabs/lndclient v0.18.4-3 + github.com/lightninglabs/lndclient v0.18.4-5 github.com/lightninglabs/neutrino/cache v1.1.2 - github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241025090009-615f3d633e61 + github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241120143113-9246d5c51cd2 github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/clock v1.1.1 github.com/lightningnetwork/lnd/fn v1.2.3 diff --git a/go.sum b/go.sum index ff805b32f..f9fd01e4c 100644 --- a/go.sum +++ b/go.sum @@ -486,8 +486,8 @@ github.com/lightninglabs/lightning-node-connect v0.2.5-alpha h1:ZRVChwczFXK0CEbx github.com/lightninglabs/lightning-node-connect v0.2.5-alpha/go.mod h1:A9Pof9fETkH+F67BnOmrBDThPKstqp73wlImWOZvTXQ= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 h1:Er1miPZD2XZwcfE4xoS5AILqP1mj7kqnhbBSxW9BDxY= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2/go.mod h1:antQGRDRJiuyQF6l+k6NECCSImgCpwaZapATth2Chv4= -github.com/lightninglabs/lndclient v0.18.4-3 h1:Xk3ZuCQE4ZlF70jaToryL2MvRcryiE0zTfUjJbmzUBY= -github.com/lightninglabs/lndclient v0.18.4-3/go.mod h1:/HLqmZGL9MtP8F1g+laq+L9VrsugBN5tsTct3C5wWCg= +github.com/lightninglabs/lndclient v0.18.4-5 h1:KokX5ZlFuZEmtD7sHWg1cXzee0ZsnBWuSKV9/RcTEv4= +github.com/lightninglabs/lndclient v0.18.4-5/go.mod h1:tafbfrisn1Iwt3em3nYWdE06C8jpoZtpNyiSB485OCg= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd h1:D8aRocHpoCv43hL8egXEMYyPmyOiefFHZ66338KQB2s= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= @@ -496,8 +496,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= -github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241025090009-615f3d633e61 h1:EcBM2tz+iyspYRFaDVjUe5a2bkuBWFxOWD2mzdCraUc= -github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241025090009-615f3d633e61/go.mod h1:q2DlXwj6ev8TMbo+CvfJ3BIrqw42HFM/fSBoyCFrjdc= +github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241120143113-9246d5c51cd2 h1:zGnSH1gTpPA637465d5tp7VkdWw5sVyWZxxmfZ0rKo4= +github.com/lightningnetwork/lnd v0.18.3-beta.rc3.0.20241120143113-9246d5c51cd2/go.mod h1:nPRQzLla5uHPQFyyZn8r9Vgddkd23PBUDa9rggEPOfY= github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI= github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= diff --git a/psbt_channel_funder.go b/psbt_channel_funder.go index 42dde5888..7fc740e06 100644 --- a/psbt_channel_funder.go +++ b/psbt_channel_funder.go @@ -97,9 +97,8 @@ func (l *LndPbstChannelFunder) OpenChannel(ctx context.Context, // We'll map our high level params into a request for a: private, // taproot channel, that uses the PSBT funding flow. taprootCommitType := lnrpc.CommitmentType_SIMPLE_TAPROOT_OVERLAY - openChanStream, errChan, err := l.lnd.Client.OpenChannelStream( - ctx, route.NewVertex(&req.PeerPub), req.ChanAmt, req.PushAmt, - true, lndclient.WithCommitmentType(&taprootCommitType), + channelOpenOptions := []lndclient.OpenChannelOption{ + lndclient.WithCommitmentType(&taprootCommitType), lndclient.WithFundingShim(&lnrpc.FundingShim{ Shim: &lnrpc.FundingShim_PsbtShim{ PsbtShim: &lnrpc.PsbtShim{ @@ -110,6 +109,20 @@ func (l *LndPbstChannelFunder) OpenChannel(ctx context.Context, }, }), lndclient.WithRemoteReserve(CustomChannelRemoteReserve), + } + + // Limit the number of HTLCs that can be added to the channel by the + // remote party. + if req.RemoteMaxHtlc > 0 { + channelOpenOptions = append( + channelOpenOptions, + lndclient.WithRemoteMaxHtlc(req.RemoteMaxHtlc), + ) + } + + openChanStream, errChan, err := l.lnd.Client.OpenChannelStream( + ctx, route.NewVertex(&req.PeerPub), req.ChanAmt, req.PushAmt, + true, channelOpenOptions..., ) if err != nil { return nil, fmt.Errorf("unable to open channel with "+ @@ -143,6 +156,16 @@ func (l *LndPbstChannelFunder) OpenChannel(ctx context.Context, } } +// ChannelAcceptor is used to accept and potentially influence parameters of +// incoming channels. +func (l *LndPbstChannelFunder) ChannelAcceptor(ctx context.Context, + acceptor lndclient.AcceptorFunction) (chan error, error) { + + return l.lnd.Client.ChannelAcceptor( + ctx, tapchannel.DefaultTimeout/2, acceptor, + ) +} + // A compile-time check to ensure that LndPbstChannelFunder fully implements // the tapchannel.PsbtChannelFunder interface. var _ tapchannel.PsbtChannelFunder = (*LndPbstChannelFunder)(nil) diff --git a/sample-tapd.conf b/sample-tapd.conf index 25b63ec8e..a052cb796 100644 --- a/sample-tapd.conf +++ b/sample-tapd.conf @@ -328,8 +328,8 @@ ; universe.multiverse-caches.syncer-cache-pre-alloc-size=100000 ; The size of the root node page cache for all requests that aren't served by -; the syncer cache. (default: 10240) -; universe.multiverse-caches.root-node-page-cache-size=10240 +; the syncer cache. (default: 327680) +; universe.multiverse-caches.root-node-page-cache-size=327680 [address] diff --git a/server.go b/server.go index 1737ead5a..9c42c9518 100644 --- a/server.go +++ b/server.go @@ -760,8 +760,8 @@ func (s *Server) FetchLeavesFromView( // NOTE: This method is part of the lnwallet.AuxLeafStore interface. // nolint:lll func (s *Server) FetchLeavesFromCommit(chanState lnwl.AuxChanState, - com channeldb.ChannelCommitment, - keys lnwl.CommitmentKeyRing) lfn.Result[lnwl.CommitDiffAuxResult] { + com channeldb.ChannelCommitment, keys lnwl.CommitmentKeyRing, + whoseCommit lntypes.ChannelParty) lfn.Result[lnwl.CommitDiffAuxResult] { srvrLog.Debugf("FetchLeavesFromCommit called, ourBalance=%v, "+ "theirBalance=%v, numHtlcs=%d", com.LocalBalance, @@ -1159,7 +1159,8 @@ func (s *Server) ExtraBudgetForInputs( // // NOTE: This method is part of the sweep.AuxSweeper interface. func (s *Server) NotifyBroadcast(req *sweep.BumpRequest, - tx *wire.MsgTx, fee btcutil.Amount) error { + tx *wire.MsgTx, fee btcutil.Amount, + outpointToTxIndex map[wire.OutPoint]int) error { srvrLog.Tracef("NotifyBroadcast called, req=%v, tx=%v, fee=%v", spew.Sdump(req), spew.Sdump(tx), fee) diff --git a/tapcfg/server.go b/tapcfg/server.go index 65279b425..f668a2360 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -464,6 +464,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, DefaultCourierAddr: proofCourierAddr, AssetSyncer: addrBook, FeatureBits: lndFeatureBitsVerifier, + ErrChan: mainErrChan, }, ) auxTrafficShaper := tapchannel.NewAuxTrafficShaper( diff --git a/tapchannel/aux_funding_controller.go b/tapchannel/aux_funding_controller.go index e0c5c9e37..94d154dab 100644 --- a/tapchannel/aux_funding_controller.go +++ b/tapchannel/aux_funding_controller.go @@ -20,6 +20,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" + "github.com/lightninglabs/lndclient" "github.com/lightninglabs/taproot-assets/address" "github.com/lightninglabs/taproot-assets/asset" "github.com/lightninglabs/taproot-assets/commitment" @@ -52,6 +53,28 @@ const ( // ackTimeout is the amount of time we'll wait to receive the protocol // level ACK from the remote party before timing out. ackTimeout = time.Second * 30 + + // maxNumAssetIDs is the maximum number of fungible asset pieces (asset + // IDs) that can be committed to a single channel. The number needs to + // be limited to prevent the number of required HTLC signatures to be + // too large for a single CommitSig wire message to carry them. This + // value is tightly coupled with the number of HTLCs that can be added + // to a channel at the same time (maxNumHTLCs). The values were + // determined with the TestMaxCommitSigMsgSize test in + // aux_leaf_signer_test.go then a set was chosen that would allow for + // a decent number of HTLCs (and also a number that is divisible by two + // because each side will only be allowed to add half of the total). + maxNumAssetIDs = 3 + + // maxNumHTLCs is the maximum number of HTLCs there can be in an asset + // channel to avoid the number of signatures exceeding the maximum + // message size of a CommitSig message. See maxNumAssetIDs for more + // information. + maxNumHTLCs = 166 + + // maxNumHTLCsPerParty is the maximum number of HTLCs that can be added + // by a single party to a channel. + maxNumHTLCsPerParty = maxNumHTLCs / 2 ) // ErrorReporter is used to report an error back to the caller and/or peer that @@ -94,6 +117,11 @@ type OpenChanReq struct { // PushAmt is the amount of BTC to push to the remote peer. PushAmt btcutil.Amount + // RemoteMaxHtlc is the maximum number of HTLCs we allow the remote to + // add to the channel. If this is zero, then the default value defined + // by lnd (and dependent on the channel capacity) will be used. + RemoteMaxHtlc uint32 + // PeerPub is the identity public key of the remote peer we wish to // open the channel with. PeerPub btcec.PublicKey @@ -133,6 +161,11 @@ type PsbtChannelFunder interface { // process. Afterward, the funding transaction should be signed and // broadcast. OpenChannel(context.Context, OpenChanReq) (AssetChanIntent, error) + + // ChannelAcceptor is used to accept and potentially influence + // parameters of incoming channels. + ChannelAcceptor(ctx context.Context, + acceptor lndclient.AcceptorFunction) (chan error, error) } // TxPublisher is an interface used to publish transactions. @@ -217,6 +250,9 @@ type FundingControllerCfg struct { // FeatureBits is used to verify that the peer has the required feature // to fund asset channels. FeatureBits FeatureBitVerifer + + // ErrChan is used to report errors back to the main server. + ErrChan chan<- error } // bindFundingReq is a request to bind a pending channel ID to a complete aux @@ -293,6 +329,36 @@ func (f *FundingController) Start() error { f.Wg.Add(1) go f.chanFunder() + f.Wg.Add(1) + go func() { + defer f.Wg.Done() + + ctx, cancel := f.WithCtxQuitNoTimeout() + defer cancel() + + errChan, err := f.cfg.ChannelFunder.ChannelAcceptor( + ctx, f.channelAcceptor, + ) + if err != nil { + err = fmt.Errorf("unable to start channel acceptor: %w", + err) + f.cfg.ErrChan <- err + return + } + + // We'll accept channels for as long as the funding controller + // is running or until we receive an error. + select { + case err := <-errChan: + err = fmt.Errorf("channel acceptor error: %w", err) + f.cfg.ErrChan <- err + + case <-f.Quit: + log.Infof("Stopping channel acceptor, funding " + + "controller shutting down") + } + }() + return nil } @@ -1003,10 +1069,11 @@ func (f *FundingController) completeChannelFunding(ctx context.Context, // Now that we have the initial PSBT template, we can start the funding // flow with lnd. fundingReq := OpenChanReq{ - ChanAmt: 100_000, - PushAmt: fundingState.pushAmt, - PeerPub: fundingState.peerPub, - TempPID: fundingState.pid, + ChanAmt: 100_000, + PushAmt: fundingState.pushAmt, + PeerPub: fundingState.peerPub, + TempPID: fundingState.pid, + RemoteMaxHtlc: maxNumHTLCsPerParty, } assetChanIntent, err := f.cfg.ChannelFunder.OpenChannel(ctx, fundingReq) if err != nil { @@ -1430,6 +1497,26 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex, } }() + // We need to limit the number of different fungible assets (asset IDs) + // we allow to be commited to a single channel. This is to make sure we + // have a decent number of HTLCs available. See Godoc of maxNumAssetIDs + // for more information. + // + // TODO(guggero): This following code is obviously wrong and needs to be + // changed when we support committing fungible assets into a channel. To + // avoid this TODO from being overlooked, we add a dummy implementation + // with a condition that currently will never be true (since there's + // only a single vPacket being selected currently anyway). + assetIDSet := lfn.NewSet[asset.ID]() + for _, out := range fundingVpkt.VPacket.Outputs { + assetIDSet.Add(out.Asset.ID()) + } + if assetIDSet.Size() > maxNumAssetIDs { + return fmt.Errorf("too many different asset IDs in channel "+ + "funding, got %d, max is %d", len(assetIDSet.ToSlice()), + maxNumAssetIDs) + } + // 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 @@ -1677,6 +1764,43 @@ func (f *FundingController) chanFunder() { } } +// channelAcceptor is a callback that's called by the lnd client when a new +// channel is proposed. This function is responsible for deciding whether to +// accept the channel based on the channel parameters, and to also set some +// channel parameters for our own side. +func (f *FundingController) channelAcceptor(_ context.Context, + req *lndclient.AcceptorRequest) (*lndclient.AcceptorResponse, error) { + + // Avoid nil pointer dereference. + if req.CommitmentType == nil { + return nil, fmt.Errorf("commitment type is required") + } + + // Ignore any non-asset channels, just accept them. + if *req.CommitmentType != lnwallet.CommitmentTypeSimpleTaprootOverlay { + return &lndclient.AcceptorResponse{ + Accept: true, + }, nil + } + + // Reject custom channels that don't observe the max HTLC limit. + if req.MaxAcceptedHtlcs > maxNumHTLCsPerParty { + return &lndclient.AcceptorResponse{ + Accept: false, + Error: fmt.Sprintf("max accepted HTLCs must be at "+ + "most %d, got %d", maxNumHTLCsPerParty, + req.MaxAcceptedHtlcs), + }, nil + } + + // Everything looks good, we can now set our own max HTLC limit we'll + // observe for this channel. + return &lndclient.AcceptorResponse{ + Accept: true, + MaxHtlcCount: maxNumHTLCsPerParty, + }, nil +} + // validateProofs validates the inclusion/exclusion/split proofs and the // transfer witness of the given proofs. func (f *FundingController) validateProofs(proofs []*proof.Proof) error { diff --git a/tapchannel/aux_leaf_signer_test.go b/tapchannel/aux_leaf_signer_test.go new file mode 100644 index 000000000..a2fe08656 --- /dev/null +++ b/tapchannel/aux_leaf_signer_test.go @@ -0,0 +1,165 @@ +package tapchannel + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/txscript" + "github.com/lightninglabs/taproot-assets/asset" + cmsg "github.com/lightninglabs/taproot-assets/tapchannelmsg" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/tlv" + "github.com/stretchr/testify/require" +) + +// Some of these test values and functions are from lnd's lnwire/lnwire_test.go. +// We just need them to have some realistically sized values for the BTC-level +// Schnorr signatures. The actual values are not important for the test. +var ( + testSchnorrSigStr, _ = hex.DecodeString( + "04e7f9037658a92afeb4f25bae5339e3ddca81a353493827d26f16d92308" + + "e49e2a25e92208678a2df86970da91b03a8af8815a8a60498b35" + + "8daf560b347aa557", + ) + testSchnorrSig, _ = lnwire.NewSigFromSchnorrRawSignature( + testSchnorrSigStr, + ) +) + +func randPartialSigWithNonce() (*lnwire.PartialSigWithNonce, error) { + var sigBytes [32]byte + if _, err := rand.Read(sigBytes[:]); err != nil { + return nil, fmt.Errorf("unable to generate sig: %w", err) + } + + var s btcec.ModNScalar + s.SetByteSlice(sigBytes[:]) + + var nonce lnwire.Musig2Nonce + if _, err := rand.Read(nonce[:]); err != nil { + return nil, fmt.Errorf("unable to generate nonce: %w", err) + } + + return &lnwire.PartialSigWithNonce{ + PartialSig: lnwire.NewPartialSig(s), + Nonce: nonce, + }, nil +} + +func somePartialSigWithNonce(t *testing.T) lnwire.OptPartialSigWithNonceTLV { + sig, err := randPartialSigWithNonce() + if err != nil { + t.Fatal(err) + } + + return tlv.SomeRecordT(tlv.NewRecordT[ + lnwire.PartialSigWithNonceType, + lnwire.PartialSigWithNonce, + ](*sig)) +} + +// TestMaxCommitSigMsgSize attempts to find values for the max number of asset +// IDs we want to allow per channel and the resulting maximum number of HTLCs +// that channel can allow. The maximum number of different asset IDs that can be +// committed to a channel directly impacts the number of HTLCs that can be +// created on that channel, because we have a limited message size to exchange +// the second-stage HTLC signatures. The goal of this test is to find the right +// number of asset IDs we should allow per channel to still give us a reasonable +// amount of HTLCs. +func TestMaxCommitSigMsgSize(t *testing.T) { + // This test is only relevant once, to find the values we want to use + // for the maximum number of asset IDs and the resulting maximum number + // of HTLCs. We only need to re-run this if any of the parameters + // change. + t.Skip("Test for manual execution only") + + const ( + maxNumAssetIDs = 10 + startNumHTLCs = 5 + endNumHTLCs = input.MaxHTLCNumber + ) + + var buf bytes.Buffer + for numID := 0; numID <= maxNumAssetIDs; numID++ { + for htlcs := startNumHTLCs; htlcs <= endNumHTLCs; htlcs++ { + buf.Reset() + + msg := makeCommitSig(t, numID, htlcs) + err := msg.Encode(&buf, 0) + require.NoError(t, err) + + if buf.Len() > lnwire.MaxMsgBody { + t.Logf("Last valid commit sig msg size with: "+ + "numAssetIDs=%d, numHTLCs=%d", + numID, htlcs-1) + + break + } + + if htlcs == endNumHTLCs { + t.Logf("Last valid commit sig msg size with: "+ + "numAssetIDs=%d, numHTLCs=%d", + numID, htlcs) + } + } + } +} + +func makeCommitSig(t *testing.T, numAssetIDs, numHTLCs int) *lnwire.CommitSig { + var ( + msg = &lnwire.CommitSig{ + HtlcSigs: make([]lnwire.Sig, numHTLCs), + } + err error + ) + + // Static values that are always set for custom channels (which are + // Taproot channels, so have an all-zero legacy commit signature and a + // partial MuSig2 signature). + msg.PartialSig = somePartialSigWithNonce(t) + msg.CommitSig, err = lnwire.NewSigFromWireECDSA( + bytes.Repeat([]byte{0}, 64), + ) + require.NoError(t, err) + + assetSigs := make([][]*cmsg.AssetSig, numHTLCs) + for i := range numHTLCs { + msg.HtlcSigs[i] = testSchnorrSig + + assetSigs[i] = make([]*cmsg.AssetSig, numAssetIDs) + for j := range numAssetIDs { + var assetID asset.ID + + _, err := rand.Read(assetID[:]) + require.NoError(t, err) + + assetSigs[i][j] = cmsg.NewAssetSig( + assetID, testSchnorrSig, txscript.SigHashAll, + ) + } + } + + if numAssetIDs == 0 { + return msg + } + + commitSig := cmsg.NewCommitSig(assetSigs) + + var buf bytes.Buffer + err = commitSig.Encode(&buf) + require.NoError(t, err) + + msg.CustomRecords = lnwire.CustomRecords{ + // The actual record type is not important for this test, it + // just needs to be in the correct range to be encoded with the + // correct number of bytes in the compact size encoding. + lnwire.MinCustomRecordsTlvType + 123: buf.Bytes(), + } + + return msg +}