Merge pull request #784 from starius/sweepbatcher-custom-musig2

sweepbatcher: add option WithCustomSignMuSig2
master
Boris Nagaev 5 days ago committed by GitHub
commit 2024791e15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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)
@ -727,10 +736,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 +771,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 +868,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.
@ -896,19 +908,18 @@ 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
}
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)
@ -942,119 +953,84 @@ 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 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
}
var (
signers [][]byte
muSig2Version input.MuSig2Version
)
// Sanity check that we have all the nonces.
if !haveAllNonces {
return fmt.Errorf("invalid MuSig2 session: " +
"nonces missing")
// 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
}
var digest [32]byte
copy(digest[:], sigHash)
// 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 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 err
}
if !haveAllSigs {
return fmt.Errorf("failed to combine signatures")
return nil, fmt.Errorf("customMuSig2Signer failed: %w",
err)
}
// To be sure that we're good, parse and validate that the
@ -1064,15 +1040,88 @@ func (b *batch) coopSignBatchTx(ctx context.Context, packet *psbt.Packet,
htlcScript.TaprootKey, sigHash, finalSig,
)
if err != nil {
return err
return nil, fmt.Errorf("verifySchnorrSig failed: %w",
err)
}
packet.UnsignedTx.TxIn[i].Witness = wire.TxWitness{
finalSig,
}
return finalSig, nil
}
return 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
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)
}
// 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
}
var serverPublicNonce [musig2.PubNonceSize]byte
copy(serverPublicNonce[:], serverNonce)
// 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
}
// Sanity check that we have all the nonces.
if !haveAllNonces {
return nil, fmt.Errorf("invalid MuSig2 session: " +
"nonces missing")
}
// 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

@ -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

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"os"
"sync"
"testing"
"time"
@ -12,11 +13,12 @@ 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"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/stretchr/testify/require"
@ -45,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
@ -68,6 +77,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),
@ -112,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)
@ -134,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)
@ -160,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{
@ -176,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)
@ -190,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 {
@ -212,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)
@ -236,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.
@ -298,11 +343,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{
@ -310,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,
@ -374,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)
@ -396,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,
@ -434,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)
@ -443,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{
@ -514,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)
@ -537,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,
@ -561,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},
@ -588,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},
@ -608,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
@ -663,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.
@ -695,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 {
@ -732,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)
@ -754,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)
@ -777,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))
@ -796,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)
@ -821,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{
@ -836,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)
@ -862,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.
@ -908,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)
@ -930,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)
@ -956,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)
@ -984,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)
@ -1013,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)
@ -1041,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)
@ -1069,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)
@ -1095,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))
@ -1106,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
@ -1118,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
@ -1130,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 {
@ -1150,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.
@ -1253,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)
@ -1283,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)
@ -1300,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 {
@ -1422,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)
@ -1460,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,
},
}
@ -1484,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,
},
}
@ -1500,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 {
@ -1525,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)
@ -1569,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()
@ -1590,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)
@ -1620,6 +1790,8 @@ func testRestoringPreservesConfTarget(t *testing.T, store testStore,
SwapContract: loopdb.SwapContract{
CltvExpiry: 111,
AmountRequested: 111,
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
HtlcKeys: htlcKeys,
},
DestAddr: destAddr,
@ -1638,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 {
@ -1677,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() {
@ -1714,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()
@ -1761,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,
@ -1784,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{
@ -1901,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)
@ -1985,6 +2153,84 @@ 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: htlcKeys,
},
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 +2316,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
@ -2155,6 +2406,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()

Loading…
Cancel
Save