From 822e4746c126fd06441a146e7e619e0ef1089c4a Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sun, 23 Jun 2024 12:54:49 -0300 Subject: [PATCH 1/7] sweepbatcher: wrap error from MuSig2CreateSession Make it more clear in the logs, what is the source of the error. --- sweepbatcher/sweep_batch.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sweepbatcher/sweep_batch.go b/sweepbatcher/sweep_batch.go index eed0afd..39c8181 100644 --- a/sweepbatcher/sweep_batch.go +++ b/sweepbatcher/sweep_batch.go @@ -996,7 +996,8 @@ func (b *batch) coopSignBatchTx(ctx context.Context, packet *psbt.Packet, ), ) if err != nil { - return err + return fmt.Errorf("signerClient.MuSig2CreateSession "+ + "failed: %w", err) } // With the session active, we can now send the server our From 3d5b7db1f7f9d97ffcc3ee8352f2169542635671 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sun, 23 Jun 2024 13:34:03 -0300 Subject: [PATCH 2/7] sweepbatcher: factor out method musig2sign It is long and is going to be even longer and hard to keep it inside a loop. --- sweepbatcher/sweep_batch.go | 228 +++++++++++++++++++----------------- 1 file changed, 118 insertions(+), 110 deletions(-) diff --git a/sweepbatcher/sweep_batch.go b/sweepbatcher/sweep_batch.go index 39c8181..6c85cb7 100644 --- a/sweepbatcher/sweep_batch.go +++ b/sweepbatcher/sweep_batch.go @@ -896,11 +896,9 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount, return fee, err, false } - prevOutputFetcher := txscript.NewMultiPrevOutFetcher(prevOuts) - // Attempt to cooperatively sign the batch tx with the server. err = b.coopSignBatchTx( - ctx, packet, sweeps, prevOutputFetcher, prevOuts, psbtBuf, + ctx, packet, sweeps, prevOuts, psbtBuf.Bytes(), ) if err != nil { return fee, err, false @@ -942,138 +940,148 @@ func (b *batch) debugLogTx(msg string, tx *wire.MsgTx) { // coopSignBatchTx collects the necessary signatures from the server in order // to cooperatively sweep the funds. func (b *batch) coopSignBatchTx(ctx context.Context, packet *psbt.Packet, - sweeps []sweep, prevOutputFetcher *txscript.MultiPrevOutFetcher, - prevOuts map[wire.OutPoint]*wire.TxOut, psbtBuf bytes.Buffer) error { + sweeps []sweep, prevOuts map[wire.OutPoint]*wire.TxOut, + psbt []byte) error { for i, sweep := range sweeps { sweep := sweep - sigHashes := txscript.NewTxSigHashes( - packet.UnsignedTx, prevOutputFetcher, - ) - - sigHash, err := txscript.CalcTaprootSignatureHash( - sigHashes, txscript.SigHashDefault, packet.UnsignedTx, - i, prevOutputFetcher, + finalSig, err := b.musig2sign( + ctx, i, sweep, packet.UnsignedTx, prevOuts, psbt, ) if err != nil { return err } - var ( - signers [][]byte - muSig2Version input.MuSig2Version - ) - - // Depending on the MuSig2 version we either pass 32 byte - // Schnorr public keys or normal 33 byte public keys. - if sweep.protocolVersion >= loopdb.ProtocolVersionMuSig2 { - muSig2Version = input.MuSig2Version100RC2 - signers = [][]byte{ - sweep.htlcKeys.SenderInternalPubKey[:], - sweep.htlcKeys.ReceiverInternalPubKey[:], - } - } else { - muSig2Version = input.MuSig2Version040 - signers = [][]byte{ - sweep.htlcKeys.SenderInternalPubKey[1:], - sweep.htlcKeys.ReceiverInternalPubKey[1:], - } + packet.UnsignedTx.TxIn[i].Witness = wire.TxWitness{ + finalSig, } + } - htlcScript, ok := sweep.htlc.HtlcScript.(*swap.HtlcScriptV3) - if !ok { - return fmt.Errorf("invalid htlc script version") - } + return nil +} - // Now we're creating a local MuSig2 session using the receiver - // key's key locator and the htlc's root hash. - musig2SessionInfo, err := b.signerClient.MuSig2CreateSession( - ctx, muSig2Version, - &sweep.htlcKeys.ClientScriptKeyLocator, signers, - lndclient.MuSig2TaprootTweakOpt( - htlcScript.RootHash[:], false, - ), - ) - if err != nil { - return fmt.Errorf("signerClient.MuSig2CreateSession "+ - "failed: %w", err) - } +// musig2sign signs one sweep using musig2. +func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep, + unsignedTx *wire.MsgTx, prevOuts map[wire.OutPoint]*wire.TxOut, + psbt []byte) ([]byte, error) { - // With the session active, we can now send the server our - // public nonce and the sig hash, so that it can create it's own - // MuSig2 session and return the server side nonce and partial - // signature. - serverNonce, serverSig, err := b.muSig2SignSweep( - ctx, sweep.protocolVersion, sweep.swapHash, - sweep.swapInvoicePaymentAddr, - musig2SessionInfo.PublicNonce[:], psbtBuf.Bytes(), - prevOuts, - ) - if err != nil { - return err - } + prevOutputFetcher := txscript.NewMultiPrevOutFetcher(prevOuts) - var serverPublicNonce [musig2.PubNonceSize]byte - copy(serverPublicNonce[:], serverNonce) + sigHashes := txscript.NewTxSigHashes(unsignedTx, prevOutputFetcher) - // Register the server's nonce before attempting to create our - // partial signature. - haveAllNonces, err := b.signerClient.MuSig2RegisterNonces( - ctx, musig2SessionInfo.SessionID, - [][musig2.PubNonceSize]byte{serverPublicNonce}, - ) - if err != nil { - return err - } + sigHash, err := txscript.CalcTaprootSignatureHash( + sigHashes, txscript.SigHashDefault, unsignedTx, inputIndex, + prevOutputFetcher, + ) + if err != nil { + return nil, err + } - // Sanity check that we have all the nonces. - if !haveAllNonces { - return fmt.Errorf("invalid MuSig2 session: " + - "nonces missing") + var ( + signers [][]byte + muSig2Version input.MuSig2Version + ) + + // Depending on the MuSig2 version we either pass 32 byte + // Schnorr public keys or normal 33 byte public keys. + if sweep.protocolVersion >= loopdb.ProtocolVersionMuSig2 { + muSig2Version = input.MuSig2Version100RC2 + signers = [][]byte{ + sweep.htlcKeys.SenderInternalPubKey[:], + sweep.htlcKeys.ReceiverInternalPubKey[:], + } + } else { + muSig2Version = input.MuSig2Version040 + signers = [][]byte{ + sweep.htlcKeys.SenderInternalPubKey[1:], + sweep.htlcKeys.ReceiverInternalPubKey[1:], } + } - var digest [32]byte - copy(digest[:], sigHash) + htlcScript, ok := sweep.htlc.HtlcScript.(*swap.HtlcScriptV3) + if !ok { + return nil, fmt.Errorf("invalid htlc script version") + } - // Since our MuSig2 session has all nonces, we can now create - // the local partial signature by signing the sig hash. - _, err = b.signerClient.MuSig2Sign( - ctx, musig2SessionInfo.SessionID, digest, false, - ) - if err != nil { - return err - } + // Now we're creating a local MuSig2 session using the receiver key's + // key locator and the htlc's root hash. + keyLocator := &sweep.htlcKeys.ClientScriptKeyLocator + musig2SessionInfo, err := b.signerClient.MuSig2CreateSession( + ctx, muSig2Version, keyLocator, signers, + lndclient.MuSig2TaprootTweakOpt(htlcScript.RootHash[:], false), + ) + if err != nil { + return nil, fmt.Errorf("signerClient.MuSig2CreateSession "+ + "failed: %w", err) + } - // Now combine the partial signatures to use the final combined - // signature in the sweep transaction's witness. - haveAllSigs, finalSig, err := b.signerClient.MuSig2CombineSig( - ctx, musig2SessionInfo.SessionID, [][]byte{serverSig}, - ) - if err != nil { - return err - } + // With the session active, we can now send the server our + // public nonce and the sig hash, so that it can create it's own + // MuSig2 session and return the server side nonce and partial + // signature. + serverNonce, serverSig, err := b.muSig2SignSweep( + ctx, sweep.protocolVersion, sweep.swapHash, + sweep.swapInvoicePaymentAddr, + musig2SessionInfo.PublicNonce[:], psbt, prevOuts, + ) + if err != nil { + return nil, err + } - if !haveAllSigs { - return fmt.Errorf("failed to combine signatures") - } + var serverPublicNonce [musig2.PubNonceSize]byte + copy(serverPublicNonce[:], serverNonce) - // To be sure that we're good, parse and validate that the - // combined signature is indeed valid for the sig hash and the - // internal pubkey. - err = b.verifySchnorrSig( - htlcScript.TaprootKey, sigHash, finalSig, - ) - if err != nil { - return err - } + // Register the server's nonce before attempting to create our + // partial signature. + haveAllNonces, err := b.signerClient.MuSig2RegisterNonces( + ctx, musig2SessionInfo.SessionID, + [][musig2.PubNonceSize]byte{serverPublicNonce}, + ) + if err != nil { + return nil, err + } - packet.UnsignedTx.TxIn[i].Witness = wire.TxWitness{ - finalSig, - } + // Sanity check that we have all the nonces. + if !haveAllNonces { + return nil, fmt.Errorf("invalid MuSig2 session: " + + "nonces missing") } - return nil + var digest [32]byte + copy(digest[:], sigHash) + + // Since our MuSig2 session has all nonces, we can now create + // the local partial signature by signing the sig hash. + _, err = b.signerClient.MuSig2Sign( + ctx, musig2SessionInfo.SessionID, digest, false, + ) + if err != nil { + return nil, err + } + + // Now combine the partial signatures to use the final combined + // signature in the sweep transaction's witness. + haveAllSigs, finalSig, err := b.signerClient.MuSig2CombineSig( + ctx, musig2SessionInfo.SessionID, [][]byte{serverSig}, + ) + if err != nil { + return nil, err + } + + if !haveAllSigs { + return nil, fmt.Errorf("failed to combine signatures") + } + + // To be sure that we're good, parse and validate that the + // combined signature is indeed valid for the sig hash and the + // internal pubkey. + err = b.verifySchnorrSig(htlcScript.TaprootKey, sigHash, finalSig) + if err != nil { + return nil, err + } + + return finalSig, nil } // updateRbfRate updates the fee rate we should use for the new batch From 38e86a1b0f28dc645a21d3016415da9cd619de3a Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Mon, 24 Jun 2024 21:48:22 -0300 Subject: [PATCH 3/7] sweepbatcher: log tx weight in addition to feerate Also log fee before applying clampBatchFee. This is useful to debug fee ralated issues. --- sweepbatcher/sweep_batch.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/sweepbatcher/sweep_batch.go b/sweepbatcher/sweep_batch.go index 6c85cb7..1455de1 100644 --- a/sweepbatcher/sweep_batch.go +++ b/sweepbatcher/sweep_batch.go @@ -727,10 +727,11 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) { weightEstimate.AddP2TROutput() - fee = b.rbfCache.FeeRate.FeeForWeight(weightEstimate.Weight()) + weight := weightEstimate.Weight() + feeForWeight := b.rbfCache.FeeRate.FeeForWeight(weight) // Clamp the calculated fee to the max allowed fee amount for the batch. - fee = clampBatchFee(fee, batchAmt) + fee = clampBatchFee(feeForWeight, batchAmt) // Add the batch transaction output, which excludes the fees paid to // miners. @@ -761,8 +762,9 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) { } b.log.Infof("attempting to publish non-coop tx=%v with feerate=%v, "+ - "totalfee=%v, sweeps=%d, destAddr=%s", batchTx.TxHash(), - b.rbfCache.FeeRate, fee, len(batchTx.TxIn), address) + "weight=%v, feeForWeight=%v, fee=%v, sweeps=%d, destAddr=%s", + batchTx.TxHash(), b.rbfCache.FeeRate, weight, feeForWeight, fee, + len(batchTx.TxIn), address) b.debugLogTx("serialized non-coop sweep", batchTx) @@ -857,10 +859,11 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount, weightEstimate.AddP2TROutput() - fee = b.rbfCache.FeeRate.FeeForWeight(weightEstimate.Weight()) + weight := weightEstimate.Weight() + feeForWeight := b.rbfCache.FeeRate.FeeForWeight(weight) // Clamp the calculated fee to the max allowed fee amount for the batch. - fee = clampBatchFee(fee, batchAmt) + fee = clampBatchFee(feeForWeight, batchAmt) // Add the batch transaction output, which excludes the fees paid to // miners. @@ -905,8 +908,9 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount, } b.log.Infof("attempting to publish coop tx=%v with feerate=%v, "+ - "totalfee=%v, sweeps=%d, destAddr=%s", batchTx.TxHash(), - b.rbfCache.FeeRate, fee, len(batchTx.TxIn), address) + "weight=%v, feeForWeight=%v, fee=%v, sweeps=%d, destAddr=%s", + batchTx.TxHash(), b.rbfCache.FeeRate, weight, feeForWeight, fee, + len(batchTx.TxIn), address) b.debugLogTx("serialized coop sweep", batchTx) From aa6d74864797867973a8559debee8203298a6b5a Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Mon, 24 Jun 2024 23:18:16 -0300 Subject: [PATCH 4/7] sweepbatcher: add option WithCustomSignMuSig2 It is needed to provide a custom MuSig2 signer. --- sweepbatcher/sweep_batch.go | 44 +++++++++++-- sweepbatcher/sweep_batcher.go | 67 +++++++++++++++---- sweepbatcher/sweep_batcher_test.go | 101 +++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 18 deletions(-) diff --git a/sweepbatcher/sweep_batch.go b/sweepbatcher/sweep_batch.go index 1455de1..c824619 100644 --- a/sweepbatcher/sweep_batch.go +++ b/sweepbatcher/sweep_batch.go @@ -137,6 +137,12 @@ type batchConfig struct { // the caller has to update it in the source of SweepInfo (interface // SweepFetcher) and re-add the sweep by calling AddSweep. noBumping bool + + // customMuSig2Signer is a custom signer. If it is set, it is used to + // create musig2 signatures instead of musig2SignSweep and signerClient. + // Note that musig2SignSweep must be nil in this case, however signer + // client must still be provided, as it is used for non-coop spendings. + customMuSig2Signer SignMuSig2 } // rbfCache stores data related to our last fee bump. @@ -503,9 +509,12 @@ func (b *batch) Run(ctx context.Context) error { close(b.finished) }() - if b.muSig2SignSweep == nil { + if b.muSig2SignSweep == nil && b.cfg.customMuSig2Signer == nil { return fmt.Errorf("no musig2 signer available") } + if b.muSig2SignSweep != nil && b.cfg.customMuSig2Signer != nil { + return fmt.Errorf("both musig2 signers provided") + } blockChan, blockErrChan, err := b.chainNotifier.RegisterBlockEpochNtfn(runCtx) @@ -1008,6 +1017,36 @@ func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep, return nil, fmt.Errorf("invalid htlc script version") } + var digest [32]byte + copy(digest[:], sigHash) + + // If a custom signer is installed, use it instead of b.signerClient + // and b.muSig2SignSweep. + if b.cfg.customMuSig2Signer != nil { + // Produce a signature. + finalSig, err := b.cfg.customMuSig2Signer( + ctx, muSig2Version, sweep.swapHash, + htlcScript.RootHash, digest, + ) + if err != nil { + return nil, fmt.Errorf("customMuSig2Signer failed: %w", + err) + } + + // To be sure that we're good, parse and validate that the + // combined signature is indeed valid for the sig hash and the + // internal pubkey. + err = b.verifySchnorrSig( + htlcScript.TaprootKey, sigHash, finalSig, + ) + if err != nil { + return nil, fmt.Errorf("verifySchnorrSig failed: %w", + err) + } + + return finalSig, nil + } + // Now we're creating a local MuSig2 session using the receiver key's // key locator and the htlc's root hash. keyLocator := &sweep.htlcKeys.ClientScriptKeyLocator @@ -1052,9 +1091,6 @@ func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep, "nonces missing") } - var digest [32]byte - copy(digest[:], sigHash) - // Since our MuSig2 session has all nonces, we can now create // the local partial signature by signing the sig hash. _, err = b.signerClient.MuSig2Sign( diff --git a/sweepbatcher/sweep_batcher.go b/sweepbatcher/sweep_batcher.go index 799961a..ee01c0f 100644 --- a/sweepbatcher/sweep_batcher.go +++ b/sweepbatcher/sweep_batcher.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop/loopdb" @@ -140,6 +141,12 @@ type MuSig2SignSweep func(ctx context.Context, prevoutMap map[wire.OutPoint]*wire.TxOut) ( []byte, []byte, error) +// SignMuSig2 is a function that can be used to sign a sweep transaction in a +// custom way. +type SignMuSig2 func(ctx context.Context, muSig2Version input.MuSig2Version, + swapHash lntypes.Hash, rootHash chainhash.Hash, sigHash [32]byte, +) ([]byte, error) + // VerifySchnorrSig is a function that can be used to verify a schnorr // signature. type VerifySchnorrSig func(pubKey *btcec.PublicKey, hash, sig []byte) error @@ -245,6 +252,12 @@ type Batcher struct { // the caller has to update it in the source of SweepInfo (interface // SweepFetcher) and re-add the sweep by calling AddSweep. noBumping bool + + // customMuSig2Signer is a custom signer. If it is set, it is used to + // create musig2 signatures instead of musig2SignSweep and signerClient. + // Note that musig2SignSweep must be nil in this case, however signer + // client must still be provided, as it is used for non-coop spendings. + customMuSig2Signer SignMuSig2 } // BatcherConfig holds batcher configuration. @@ -254,6 +267,12 @@ type BatcherConfig struct { // the caller has to update it in the source of SweepInfo (interface // SweepFetcher) and re-add the sweep by calling AddSweep. noBumping bool + + // customMuSig2Signer is a custom signer. If it is set, it is used to + // create musig2 signatures instead of musig2SignSweep and signerClient. + // Note that musig2SignSweep must be nil in this case, however signer + // client must still be provided, as it is used for non-coop spendings. + customMuSig2Signer SignMuSig2 } // BatcherOption configures batcher behaviour. @@ -269,6 +288,17 @@ func WithNoBumping() BatcherOption { } } +// WithCustomSignMuSig2 instructs sweepbatcher to use a custom function to +// produce MuSig2 signatures. If it is set, it is used to create +// musig2 signatures instead of musig2SignSweep and signerClient. Note +// that musig2SignSweep must be nil in this case, however signerClient +// must still be provided, as it is used for non-coop spendings. +func WithCustomSignMuSig2(customMuSig2Signer SignMuSig2) BatcherOption { + return func(cfg *BatcherConfig) { + cfg.customMuSig2Signer = customMuSig2Signer + } +} + // NewBatcher creates a new Batcher instance. func NewBatcher(wallet lndclient.WalletKitClient, chainNotifier lndclient.ChainNotifierClient, @@ -282,21 +312,27 @@ func NewBatcher(wallet lndclient.WalletKitClient, opt(&cfg) } + if cfg.customMuSig2Signer != nil && musig2ServerSigner != nil { + panic("customMuSig2Signer must not be used with " + + "musig2ServerSigner") + } + return &Batcher{ - batches: make(map[int32]*batch), - sweepReqs: make(chan SweepRequest), - errChan: make(chan error, 1), - quit: make(chan struct{}), - initDone: make(chan struct{}), - wallet: wallet, - chainNotifier: chainNotifier, - signerClient: signerClient, - musig2ServerSign: musig2ServerSigner, - VerifySchnorrSig: verifySchnorrSig, - chainParams: chainparams, - store: store, - sweepStore: sweepStore, - noBumping: cfg.noBumping, + batches: make(map[int32]*batch), + sweepReqs: make(chan SweepRequest), + errChan: make(chan error, 1), + quit: make(chan struct{}), + initDone: make(chan struct{}), + wallet: wallet, + chainNotifier: chainNotifier, + signerClient: signerClient, + musig2ServerSign: musig2ServerSigner, + VerifySchnorrSig: verifySchnorrSig, + chainParams: chainparams, + store: store, + sweepStore: sweepStore, + noBumping: cfg.noBumping, + customMuSig2Signer: cfg.customMuSig2Signer, } } @@ -456,6 +492,7 @@ func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) { cfg := batchConfig{ maxTimeoutDistance: defaultMaxTimeoutDistance, noBumping: b.noBumping, + customMuSig2Signer: b.customMuSig2Signer, } switch b.chainParams { @@ -574,6 +611,7 @@ func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error { cfg := batchConfig{ maxTimeoutDistance: batch.cfg.maxTimeoutDistance, noBumping: b.noBumping, + customMuSig2Signer: b.customMuSig2Signer, } newBatch, err := NewBatchFromDB(cfg, batchKit) @@ -637,6 +675,7 @@ func (b *Batcher) FetchUnconfirmedBatches(ctx context.Context) ([]*batch, bchCfg := batchConfig{ maxTimeoutDistance: bch.MaxTimeoutDistance, noBumping: b.noBumping, + customMuSig2Signer: b.customMuSig2Signer, } batch.cfg = &bchCfg diff --git a/sweepbatcher/sweep_batcher_test.go b/sweepbatcher/sweep_batcher_test.go index dadfc6a..f10ede6 100644 --- a/sweepbatcher/sweep_batcher_test.go +++ b/sweepbatcher/sweep_batcher_test.go @@ -16,6 +16,7 @@ import ( "github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/utils" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -68,6 +69,18 @@ func testMuSig2SignSweep(ctx context.Context, return nil, nil, nil } +var customSignature = func() []byte { + sig := [64]byte{10, 20, 30} + return sig[:] +}() + +func testSignMuSig2func(ctx context.Context, muSig2Version input.MuSig2Version, + swapHash lntypes.Hash, rootHash chainhash.Hash, + sigHash [32]byte) ([]byte, error) { + + return customSignature, nil +} + var dummyNotifier = SpendNotifier{ SpendChan: make(chan *SpendDetail, ntfnBufferSize), SpendErrChan: make(chan error, ntfnBufferSize), @@ -1985,6 +1998,89 @@ func testSweepBatcherCloseDuringAdding(t *testing.T, store testStore, <-registrationChan } +// testCustomSignMuSig2 tests the operation with custom musig2 signer. +func testCustomSignMuSig2(t *testing.T, store testStore, + batcherStore testBatcherStore) { + + defer test.Guard(t)() + + lnd := test.NewMockLnd() + ctx, cancel := context.WithCancel(context.Background()) + + sweepStore, err := NewSweepFetcherFromSwapStore(store, lnd.ChainParams) + require.NoError(t, err) + + // Use custom MuSig2 signer function. + batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, + nil, testVerifySchnorrSig, lnd.ChainParams, batcherStore, + sweepStore, WithCustomSignMuSig2(testSignMuSig2func)) + + var wg sync.WaitGroup + wg.Add(1) + + var runErr error + go func() { + defer wg.Done() + runErr = batcher.Run(ctx) + }() + + // Wait for the batcher to be initialized. + <-batcher.initDone + + // Create a sweep request. + sweepReq := SweepRequest{ + SwapHash: lntypes.Hash{1, 1, 1}, + Value: 111, + Outpoint: wire.OutPoint{ + Hash: chainhash.Hash{1, 1}, + Index: 1, + }, + Notifier: &dummyNotifier, + } + + swap := &loopdb.LoopOutContract{ + SwapContract: loopdb.SwapContract{ + CltvExpiry: 111, + AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: loopdb.HtlcKeys{ + SenderScriptKey: senderKey, + ReceiverScriptKey: receiverKey, + SenderInternalPubKey: senderKey, + ReceiverInternalPubKey: receiverKey, + }, + }, + + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, + } + + err = store.CreateLoopOut(ctx, sweepReq.SwapHash, swap) + require.NoError(t, err) + store.AssertLoopOutStored() + + // Deliver sweep request to batcher. + require.NoError(t, batcher.AddSweep(&sweepReq)) + + // Since a batch was created we check that it registered for its primary + // sweep's spend. + <-lnd.RegisterSpendChannel + + // Wait for tx to be published. + tx := <-lnd.TxPublishChannel + + // Check the signature. + gotSig := tx.TxIn[0].Witness[0] + require.Equal(t, customSignature, gotSig, "signatures don't match") + + // Now make the batcher quit by canceling the context. + cancel() + wg.Wait() + + checkBatcherError(t, runErr) +} + // TestSweepBatcherBatchCreation tests that sweep requests enter the expected // batch based on their timeout distance. func TestSweepBatcherBatchCreation(t *testing.T) { @@ -2070,6 +2166,11 @@ func TestSweepBatcherCloseDuringAdding(t *testing.T) { runTests(t, testSweepBatcherCloseDuringAdding) } +// TestCustomSignMuSig2 tests the operation with custom musig2 signer. +func TestCustomSignMuSig2(t *testing.T) { + runTests(t, testCustomSignMuSig2) +} + // testBatcherStore is BatcherStore used in tests. type testBatcherStore interface { BatcherStore From 569c35da20de30f5435393aef471eb3ca856ecf9 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 25 Jun 2024 01:10:12 -0300 Subject: [PATCH 5/7] sweepbatcher: enable all logs in tests --- sweepbatcher/sweep_batcher_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sweepbatcher/sweep_batcher_test.go b/sweepbatcher/sweep_batcher_test.go index f10ede6..dcbde18 100644 --- a/sweepbatcher/sweep_batcher_test.go +++ b/sweepbatcher/sweep_batcher_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "os" "sync" "testing" "time" @@ -12,6 +13,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btclog" "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/utils" @@ -2256,6 +2258,10 @@ func (s *loopdbStore) AssertLoopOutStored() { func runTests(t *testing.T, testFn func(t *testing.T, store testStore, batcherStore testBatcherStore)) { + logger := btclog.NewBackend(os.Stdout).Logger("SWEEP") + logger.SetLevel(btclog.LevelTrace) + UseLogger(logger) + t.Run("mocks", func(t *testing.T) { store := loopdb.NewStoreMock(t) batcherStore := NewStoreMock() From 909bf5e26414068faf7bced24a547e9855bc16a0 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 26 Jun 2024 12:07:23 -0300 Subject: [PATCH 6/7] sweepbatcher/testFeeBumping: reuse dummyNotifier --- sweepbatcher/sweep_batcher_test.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sweepbatcher/sweep_batcher_test.go b/sweepbatcher/sweep_batcher_test.go index dcbde18..b67f37e 100644 --- a/sweepbatcher/sweep_batcher_test.go +++ b/sweepbatcher/sweep_batcher_test.go @@ -313,11 +313,7 @@ func testFeeBumping(t *testing.T, store testStore, Hash: chainhash.Hash{1, 1}, Index: 1, }, - Notifier: &SpendNotifier{ - SpendChan: make(chan *SpendDetail, ntfnBufferSize), - SpendErrChan: make(chan error, ntfnBufferSize), - QuitChan: make(chan bool, ntfnBufferSize), - }, + Notifier: &dummyNotifier, } swap1 := &loopdb.LoopOutContract{ From c06b6190af88cde9418dde1e47a816da0f6bdb1c Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 26 Jun 2024 23:18:13 -0300 Subject: [PATCH 7/7] sweepbatcher/test: test tx is signed and published Also fix the tests that used to fail to sign and broadcast transactions. --- sweepbatcher/sweep_batcher_test.go | 344 +++++++++++++++++++++-------- 1 file changed, 248 insertions(+), 96 deletions(-) diff --git a/sweepbatcher/sweep_batcher_test.go b/sweepbatcher/sweep_batcher_test.go index b67f37e..82c8258 100644 --- a/sweepbatcher/sweep_batcher_test.go +++ b/sweepbatcher/sweep_batcher_test.go @@ -19,7 +19,6 @@ import ( "github.com/lightninglabs/loop/utils" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/input" - "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/stretchr/testify/require" @@ -48,15 +47,22 @@ var destAddr = func() btcutil.Address { return addr }() -var senderKey, receiverKey [33]byte +var htlcKeys = func() loopdb.HtlcKeys { + var senderKey, receiverKey [33]byte -func init() { // Generate keys. _, senderPubKey := test.CreateKey(1) copy(senderKey[:], senderPubKey.SerializeCompressed()) _, receiverPubKey := test.CreateKey(2) copy(receiverKey[:], receiverPubKey.SerializeCompressed()) -} + + return loopdb.HtlcKeys{ + SenderScriptKey: senderKey, + ReceiverScriptKey: receiverKey, + SenderInternalPubKey: senderKey, + ReceiverInternalPubKey: receiverKey, + } +}() func testVerifySchnorrSig(pubKey *btcec.PublicKey, hash, sig []byte) error { return nil @@ -127,8 +133,8 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) go func() { err := batcher.Run(ctx) checkBatcherError(t, err) @@ -149,10 +155,13 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1) @@ -175,6 +184,9 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, return len(batcher.batches) == 1 }, test.Timeout, eventuallyCheckFrequency) + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Create a second sweep request that has a timeout distance less than // our configured threshold. sweepReq2 := SweepRequest{ @@ -191,12 +203,16 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance - 1, AmountRequested: 222, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{2}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq2.SwapHash, swap2) @@ -205,6 +221,13 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, require.NoError(t, batcher.AddSweep(&sweepReq2)) + // Tick tock next block. + err = lnd.NotifyHeight(601) + require.NoError(t, err) + + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Batcher should not create a second batch as timeout distance is small // enough. require.Eventually(t, func() bool { @@ -227,12 +250,16 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance + 1, AmountRequested: 333, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{3}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq3.SwapHash, swap3) @@ -251,6 +278,9 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore, // primary sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + require.Eventually(t, func() bool { // Verify that each batch has the correct number of sweeps // in it. @@ -321,16 +351,7 @@ func testFeeBumping(t *testing.T, store testStore, CltvExpiry: 111, AmountRequested: 1_000_000, ProtocolVersion: loopdb.ProtocolVersionMuSig2, - HtlcKeys: loopdb.HtlcKeys{ - SenderScriptKey: senderKey, - ReceiverScriptKey: receiverKey, - SenderInternalPubKey: senderKey, - ReceiverInternalPubKey: receiverKey, - ClientScriptKeyLocator: keychain.KeyLocator{ - Family: 1, - Index: 2, - }, - }, + HtlcKeys: htlcKeys, }, DestAddr: destAddr, @@ -385,8 +406,8 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) go func() { err := batcher.Run(ctx) checkBatcherError(t, err) @@ -407,7 +428,10 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, + DestAddr: destAddr, SwapInvoice: swapInvoice, SweepConfTarget: 111, @@ -445,6 +469,9 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore, // The primary sweep id should be that of the first inserted sweep. require.Equal(t, batch.primarySweepID, sweepReq1.SwapHash) + // Wait for tx to be published. + <-lnd.TxPublishChannel + err = lnd.NotifyHeight(601) require.NoError(t, err) @@ -454,6 +481,9 @@ func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore, return batch.currentHeight == 601 }, test.Timeout, eventuallyCheckFrequency) + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Create the spending tx that will trigger the spend monitor of the // batch. spendingTx := &wire.MsgTx{ @@ -525,8 +555,8 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) go func() { err := batcher.Run(ctx) checkBatcherError(t, err) @@ -548,6 +578,8 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, DestAddr: destAddr, SwapInvoice: swapInvoice, @@ -572,6 +604,8 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 222, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{2}, @@ -599,6 +633,8 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 333, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{3}, @@ -619,10 +655,29 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, // registered. <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + + // Add the second sweep. require.NoError(t, batcher.AddSweep(&sweepReq2)) + // Add next block to trigger batch publishing. + err = lnd.NotifyHeight(601) + require.NoError(t, err) + + // Wait for tx to be published. + <-lnd.TxPublishChannel + + // Add the third sweep. require.NoError(t, batcher.AddSweep(&sweepReq3)) + // Add next block to trigger batch publishing. + err = lnd.NotifyHeight(602) + require.NoError(t, err) + + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Batcher should create a batch for the sweeps. require.Eventually(t, func() bool { return len(batcher.batches) == 1 @@ -674,7 +729,7 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, SpendingTx: spendingTx, SpenderTxHash: &spendingTxHash, SpenderInputIndex: 0, - SpendingHeight: 601, + SpendingHeight: 603, } // Send the spending notification to the mock channel. @@ -706,6 +761,15 @@ func testSweepBatcherSweepReentry(t *testing.T, store testStore, Tx: spendingTx, } + // Wait for tx to be published. + // Here is a race condition, which is unlikely to cause a crash: if we + // wait for publish tx before sending a conf notification (previous + // action), then conf notification can go to the second batch (since + // the mock does not have a way to direct a notification to proper + // subscriber) and the first batch does not exit, waiting for the + // confirmation forever. + <-lnd.TxPublishChannel + // Eventually the batch receives the confirmation notification, // gracefully exits and the batcher deletes it. require.Eventually(t, func() bool { @@ -743,8 +807,8 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) go func() { err := batcher.Run(ctx) checkBatcherError(t, err) @@ -765,10 +829,13 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, - IsExternalAddr: true, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + IsExternalAddr: true, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1) @@ -788,6 +855,9 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Insert the same swap twice, this should be a noop. require.NoError(t, batcher.AddSweep(&sweepReq1)) @@ -807,13 +877,16 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance - 1, AmountRequested: 222, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{2}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, - IsExternalAddr: true, + IsExternalAddr: true, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq2.SwapHash, swap2) @@ -832,6 +905,9 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for second batch to be published. + <-lnd.TxPublishChannel + // Create a third sweep request that has more timeout distance than // the default. sweepReq3 := SweepRequest{ @@ -847,14 +923,17 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, swap3 := &loopdb.LoopOutContract{ SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance + 1, - AmountRequested: 333, + AmountRequested: 222, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{3}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, - IsExternalAddr: true, + IsExternalAddr: true, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq3.SwapHash, swap3) @@ -873,6 +952,9 @@ func testSweepBatcherNonWalletAddr(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx to be published for 3rd batch. + <-lnd.TxPublishChannel + require.Eventually(t, func() bool { // Verify that each batch has the correct number of sweeps // in it. @@ -919,8 +1001,8 @@ func testSweepBatcherComposite(t *testing.T, store testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) go func() { err := batcher.Run(ctx) checkBatcherError(t, err) @@ -941,10 +1023,12 @@ func testSweepBatcherComposite(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, - - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1) @@ -967,12 +1051,15 @@ func testSweepBatcherComposite(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance - 1, AmountRequested: 222, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{2}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq2.SwapHash, swap2) @@ -995,13 +1082,16 @@ func testSweepBatcherComposite(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance - 3, AmountRequested: 333, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{3}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, - IsExternalAddr: true, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, + IsExternalAddr: true, } err = store.CreateLoopOut(ctx, sweepReq3.SwapHash, swap3) @@ -1024,12 +1114,15 @@ func testSweepBatcherComposite(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance + 1, AmountRequested: 444, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{4}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq4.SwapHash, swap4) @@ -1052,12 +1145,15 @@ func testSweepBatcherComposite(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance + 5, AmountRequested: 555, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{5}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq5.SwapHash, swap5) @@ -1080,13 +1176,16 @@ func testSweepBatcherComposite(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111 + defaultMaxTimeoutDistance + 6, AmountRequested: 666, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, // Make preimage unique to pass SQL constraints. Preimage: lntypes.Preimage{6}, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, - IsExternalAddr: true, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, + IsExternalAddr: true, } err = store.CreateLoopOut(ctx, sweepReq6.SwapHash, swap6) @@ -1106,6 +1205,9 @@ func testSweepBatcherComposite(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Insert the same swap twice, this should be a noop. require.NoError(t, batcher.AddSweep(&sweepReq1)) @@ -1117,6 +1219,14 @@ func testSweepBatcherComposite(t *testing.T, store testStore, return len(batcher.batches) == 1 }, test.Timeout, eventuallyCheckFrequency) + // Publish a block to trigger batch 1 republishing. + err = lnd.NotifyHeight(601) + require.NoError(t, err) + + // Wait for tx for the first batch to be published (2 sweeps). + tx := <-lnd.TxPublishChannel + require.Equal(t, 2, len(tx.TxIn)) + require.NoError(t, batcher.AddSweep(&sweepReq3)) // Batcher should create a second batch as this sweep pays to a non @@ -1129,6 +1239,10 @@ func testSweepBatcherComposite(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx for the second batch to be published (1 sweep). + tx = <-lnd.TxPublishChannel + require.Equal(t, 1, len(tx.TxIn)) + require.NoError(t, batcher.AddSweep(&sweepReq4)) // Batcher should create a third batch as timeout distance is greater @@ -1141,8 +1255,21 @@ func testSweepBatcherComposite(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx for the third batch to be published (1 sweep). + tx = <-lnd.TxPublishChannel + require.Equal(t, 1, len(tx.TxIn)) + require.NoError(t, batcher.AddSweep(&sweepReq5)) + // Publish a block to trigger batch 3 republishing. + err = lnd.NotifyHeight(601) + require.NoError(t, err) + + // Wait for 3 txs for the 3 batches. + <-lnd.TxPublishChannel + <-lnd.TxPublishChannel + <-lnd.TxPublishChannel + // Batcher should not create a fourth batch as timeout distance is small // enough for it to join the last batch. require.Eventually(t, func() bool { @@ -1161,6 +1288,10 @@ func testSweepBatcherComposite(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx for the 4th batch to be published (1 sweep). + tx = <-lnd.TxPublishChannel + require.Equal(t, 1, len(tx.TxIn)) + require.Eventually(t, func() bool { // Verify that each batch has the correct number of sweeps in // it. @@ -1264,8 +1395,8 @@ func testRestoringEmptyBatch(t *testing.T, store testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) var wg sync.WaitGroup wg.Add(1) @@ -1294,10 +1425,12 @@ func testRestoringEmptyBatch(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, - - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, } err = store.CreateLoopOut(ctx, sweepReq.SwapHash, swap) @@ -1311,6 +1444,9 @@ func testRestoringEmptyBatch(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Once batcher receives sweep request it will eventually spin up a // batch. require.Eventually(t, func() bool { @@ -1433,8 +1569,8 @@ func testHandleSweepTwice(t *testing.T, backend testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) var wg sync.WaitGroup wg.Add(1) @@ -1471,9 +1607,12 @@ func testHandleSweepTwice(t *testing.T, backend testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: shortCltv, AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, }, } @@ -1495,9 +1634,12 @@ func testHandleSweepTwice(t *testing.T, backend testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: longCltv, AmountRequested: 222, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, }, } @@ -1511,11 +1653,17 @@ func testHandleSweepTwice(t *testing.T, backend testStore, // primary sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Deliver the second sweep. It will go to a separate batch, // since CltvExpiry values are distant enough. require.NoError(t, batcher.AddSweep(&sweepReq2)) <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Once batcher receives sweep request it will eventually spin up // batches. require.Eventually(t, func() bool { @@ -1536,9 +1684,12 @@ func testHandleSweepTwice(t *testing.T, backend testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: shortCltv, AmountRequested: 222, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, - DestAddr: destAddr, - SwapInvoice: swapInvoice, + DestAddr: destAddr, + SwapInvoice: swapInvoice, + SweepConfTarget: 111, }, } store.putLoopOutSwap(sweepReq2.SwapHash, loopOut2) @@ -1580,6 +1731,14 @@ func testHandleSweepTwice(t *testing.T, backend testStore, require.Equal(t, 1, len(batch.sweeps)) } + // Publish a block to trigger batch 2 republishing. + err = lnd.NotifyHeight(601) + require.NoError(t, err) + + // Wait for txs to be published. + <-lnd.TxPublishChannel + <-lnd.TxPublishChannel + // Now make the batcher quit by canceling the context. cancel() wg.Wait() @@ -1601,8 +1760,8 @@ func testRestoringPreservesConfTarget(t *testing.T, store testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) var wg sync.WaitGroup wg.Add(1) @@ -1631,6 +1790,8 @@ func testRestoringPreservesConfTarget(t *testing.T, store testStore, SwapContract: loopdb.SwapContract{ CltvExpiry: 111, AmountRequested: 111, + ProtocolVersion: loopdb.ProtocolVersionMuSig2, + HtlcKeys: htlcKeys, }, DestAddr: destAddr, @@ -1649,6 +1810,9 @@ func testRestoringPreservesConfTarget(t *testing.T, store testStore, // sweep's spend. <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Once batcher receives sweep request it will eventually spin up a // batch. require.Eventually(t, func() bool { @@ -1688,8 +1852,8 @@ func testRestoringPreservesConfTarget(t *testing.T, store testStore, // Now launch it again. batcher = NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) ctx, cancel = context.WithCancel(context.Background()) wg.Add(1) go func() { @@ -1725,6 +1889,9 @@ func testRestoringPreservesConfTarget(t *testing.T, store testStore, // Expect registration for spend notification. <-lnd.RegisterSpendChannel + // Wait for tx to be published. + <-lnd.TxPublishChannel + // Now make the batcher quit by canceling the context. cancel() wg.Wait() @@ -1772,12 +1939,7 @@ func testSweepFetcher(t *testing.T, store testStore, CltvExpiry: 222, AmountRequested: amt, ProtocolVersion: loopdb.ProtocolVersionMuSig2, - HtlcKeys: loopdb.HtlcKeys{ - SenderScriptKey: senderKey, - ReceiverScriptKey: receiverKey, - SenderInternalPubKey: senderKey, - ReceiverInternalPubKey: receiverKey, - }, + HtlcKeys: htlcKeys, }, DestAddr: destAddr, SwapInvoice: swapInvoice, @@ -1795,15 +1957,10 @@ func testSweepFetcher(t *testing.T, store testStore, SwapInvoicePaymentAddr: *swapPaymentAddr, MinFeeRate: feeRate, ProtocolVersion: loopdb.ProtocolVersionMuSig2, - HTLCKeys: loopdb.HtlcKeys{ - SenderScriptKey: senderKey, - ReceiverScriptKey: receiverKey, - SenderInternalPubKey: senderKey, - ReceiverInternalPubKey: receiverKey, - }, - HTLC: *htlc, - HTLCSuccessEstimator: htlc.AddSuccessToEstimator, - DestAddr: destAddr, + HTLCKeys: htlcKeys, + HTLC: *htlc, + HTLCSuccessEstimator: htlc.AddSuccessToEstimator, + DestAddr: destAddr, } sweepFetcher := &sweepFetcherMock{ @@ -1912,8 +2069,8 @@ func testSweepBatcherCloseDuringAdding(t *testing.T, store testStore, require.NoError(t, err) batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, - testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, - sweepStore) + testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams, + batcherStore, sweepStore) go func() { err := batcher.Run(ctx) checkBatcherError(t, err) @@ -2041,12 +2198,7 @@ func testCustomSignMuSig2(t *testing.T, store testStore, CltvExpiry: 111, AmountRequested: 111, ProtocolVersion: loopdb.ProtocolVersionMuSig2, - HtlcKeys: loopdb.HtlcKeys{ - SenderScriptKey: senderKey, - ReceiverScriptKey: receiverKey, - SenderInternalPubKey: senderKey, - ReceiverInternalPubKey: receiverKey, - }, + HtlcKeys: htlcKeys, }, DestAddr: destAddr,