2
0
mirror of https://github.com/lightninglabs/loop synced 2024-11-02 09:40:39 +00:00

Merge pull request #783 from starius/sweepbatcher-WithNoBumping

sweepbatcher: add option WithNoBumping
This commit is contained in:
Boris Nagaev 2024-06-26 10:32:39 -03:00 committed by GitHub
commit 4e085f11a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 179 additions and 2 deletions

View File

@ -127,6 +127,12 @@ type batchConfig struct {
// batchPublishDelay is the delay between receiving a new block and // batchPublishDelay is the delay between receiving a new block and
// publishing the batch transaction. // publishing the batch transaction.
batchPublishDelay time.Duration batchPublishDelay time.Duration
// noBumping instructs sweepbatcher not to fee bump itself and rely on
// external source of fee rates (MinFeeRate). To change the fee rate,
// the caller has to update it in the source of SweepInfo (interface
// SweepFetcher) and re-add the sweep by calling AddSweep.
noBumping bool
} }
// rbfCache stores data related to our last fee bump. // rbfCache stores data related to our last fee bump.
@ -1079,7 +1085,7 @@ func (b *batch) updateRbfRate(ctx context.Context) error {
// Set the initial value for our fee rate. // Set the initial value for our fee rate.
b.rbfCache.FeeRate = rate b.rbfCache.FeeRate = rate
} else { } else if !b.cfg.noBumping {
// Bump the fee rate by the configured step. // Bump the fee rate by the configured step.
b.rbfCache.FeeRate += defaultFeeRateStep b.rbfCache.FeeRate += defaultFeeRateStep
} }

View File

@ -235,6 +235,34 @@ type Batcher struct {
// wg is a waitgroup that is used to wait for all the goroutines to // wg is a waitgroup that is used to wait for all the goroutines to
// exit. // exit.
wg sync.WaitGroup wg sync.WaitGroup
// noBumping instructs sweepbatcher not to fee bump itself and rely on
// external source of fee rates (MinFeeRate). To change the fee rate,
// the caller has to update it in the source of SweepInfo (interface
// SweepFetcher) and re-add the sweep by calling AddSweep.
noBumping bool
}
// BatcherConfig holds batcher configuration.
type BatcherConfig struct {
// noBumping instructs sweepbatcher not to fee bump itself and rely on
// external source of fee rates (MinFeeRate). To change the fee rate,
// the caller has to update it in the source of SweepInfo (interface
// SweepFetcher) and re-add the sweep by calling AddSweep.
noBumping bool
}
// BatcherOption configures batcher behaviour.
type BatcherOption func(*BatcherConfig)
// WithNoBumping instructs sweepbatcher not to fee bump itself and
// rely on external source of fee rates (MinFeeRate). To change the
// fee rate, the caller has to update it in the source of SweepInfo
// (interface SweepFetcher) and re-add the sweep by calling AddSweep.
func WithNoBumping() BatcherOption {
return func(cfg *BatcherConfig) {
cfg.noBumping = true
}
} }
// NewBatcher creates a new Batcher instance. // NewBatcher creates a new Batcher instance.
@ -242,7 +270,13 @@ func NewBatcher(wallet lndclient.WalletKitClient,
chainNotifier lndclient.ChainNotifierClient, chainNotifier lndclient.ChainNotifierClient,
signerClient lndclient.SignerClient, musig2ServerSigner MuSig2SignSweep, signerClient lndclient.SignerClient, musig2ServerSigner MuSig2SignSweep,
verifySchnorrSig VerifySchnorrSig, chainparams *chaincfg.Params, verifySchnorrSig VerifySchnorrSig, chainparams *chaincfg.Params,
store BatcherStore, sweepStore SweepFetcher) *Batcher { store BatcherStore, sweepStore SweepFetcher,
opts ...BatcherOption) *Batcher {
var cfg BatcherConfig
for _, opt := range opts {
opt(&cfg)
}
return &Batcher{ return &Batcher{
batches: make(map[int32]*batch), batches: make(map[int32]*batch),
@ -258,6 +292,7 @@ func NewBatcher(wallet lndclient.WalletKitClient,
chainParams: chainparams, chainParams: chainparams,
store: store, store: store,
sweepStore: sweepStore, sweepStore: sweepStore,
noBumping: cfg.noBumping,
} }
} }
@ -416,6 +451,7 @@ func (b *Batcher) handleSweep(ctx context.Context, sweep *sweep,
func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) { func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) {
cfg := batchConfig{ cfg := batchConfig{
maxTimeoutDistance: defaultMaxTimeoutDistance, maxTimeoutDistance: defaultMaxTimeoutDistance,
noBumping: b.noBumping,
} }
switch b.chainParams { switch b.chainParams {
@ -525,6 +561,7 @@ func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
cfg := batchConfig{ cfg := batchConfig{
maxTimeoutDistance: batch.cfg.maxTimeoutDistance, maxTimeoutDistance: batch.cfg.maxTimeoutDistance,
noBumping: b.noBumping,
} }
newBatch, err := NewBatchFromDB(cfg, batchKit) newBatch, err := NewBatchFromDB(cfg, batchKit)
@ -587,6 +624,7 @@ func (b *Batcher) FetchUnconfirmedBatches(ctx context.Context) ([]*batch,
bchCfg := batchConfig{ bchCfg := batchConfig{
maxTimeoutDistance: bch.MaxTimeoutDistance, maxTimeoutDistance: bch.MaxTimeoutDistance,
noBumping: b.noBumping,
} }
batch.cfg = &bchCfg batch.cfg = &bchCfg

View File

@ -8,6 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
@ -15,6 +16,7 @@ import (
"github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/test"
"github.com/lightninglabs/loop/utils" "github.com/lightninglabs/loop/utils"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -42,6 +44,20 @@ var destAddr = func() btcutil.Address {
return addr return addr
}() }()
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())
}
func testVerifySchnorrSig(pubKey *btcec.PublicKey, hash, sig []byte) error {
return nil
}
func testMuSig2SignSweep(ctx context.Context, func testMuSig2SignSweep(ctx context.Context,
protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash, protocolVersion loopdb.ProtocolVersion, swapHash lntypes.Hash,
paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte, paymentAddr [32]byte, nonce []byte, sweepTxPsbt []byte,
@ -245,6 +261,103 @@ func testSweepBatcherBatchCreation(t *testing.T, store testStore,
require.True(t, batcherStore.AssertSweepStored(sweepReq3.SwapHash)) require.True(t, batcherStore.AssertSweepStored(sweepReq3.SwapHash))
} }
// testFeeBumping tests that sweep is RBFed with slightly higher fee rate after
// each block unless WithNoBumping is passed.
func testFeeBumping(t *testing.T, store testStore,
batcherStore testBatcherStore, noFeeBumping bool) {
defer test.Guard(t)()
lnd := test.NewMockLnd()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sweepStore, err := NewSweepFetcherFromSwapStore(store, lnd.ChainParams)
require.NoError(t, err)
// Disable fee bumping, if requested.
var opts []BatcherOption
if noFeeBumping {
opts = append(opts, WithNoBumping())
}
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams,
batcherStore, sweepStore, opts...)
go func() {
err := batcher.Run(ctx)
checkBatcherError(t, err)
}()
// Create a sweep request.
sweepReq1 := SweepRequest{
SwapHash: lntypes.Hash{1, 1, 1},
Value: 1_000_000,
Outpoint: wire.OutPoint{
Hash: chainhash.Hash{1, 1},
Index: 1,
},
Notifier: &SpendNotifier{
SpendChan: make(chan *SpendDetail, ntfnBufferSize),
SpendErrChan: make(chan error, ntfnBufferSize),
QuitChan: make(chan bool, ntfnBufferSize),
},
}
swap1 := &loopdb.LoopOutContract{
SwapContract: loopdb.SwapContract{
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,
},
},
},
DestAddr: destAddr,
SwapInvoice: swapInvoice,
SweepConfTarget: 111,
}
err = store.CreateLoopOut(ctx, sweepReq1.SwapHash, swap1)
require.NoError(t, err)
store.AssertLoopOutStored()
// Deliver sweep request to batcher.
require.NoError(t, batcher.AddSweep(&sweepReq1))
// Since a batch was created we check that it registered for its primary
// sweep's spend.
<-lnd.RegisterSpendChannel
// Wait for tx to be published.
tx1 := <-lnd.TxPublishChannel
out1 := tx1.TxOut[0].Value
// Tick tock next block.
err = lnd.NotifyHeight(601)
require.NoError(t, err)
// Wait for another sweep tx to be published.
tx2 := <-lnd.TxPublishChannel
out2 := tx2.TxOut[0].Value
if noFeeBumping {
// Expect output to stay the same.
require.Equal(t, out1, out2, "expected out to stay the same")
} else {
// Expect output to drop.
require.Greater(t, out1, out2, "expected out to drop")
}
}
// testSweepBatcherSimpleLifecycle tests the simple lifecycle of the batches // testSweepBatcherSimpleLifecycle tests the simple lifecycle of the batches
// that are created and run by the batcher. // that are created and run by the batcher.
func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore, func testSweepBatcherSimpleLifecycle(t *testing.T, store testStore,
@ -1837,6 +1950,26 @@ func TestSweepBatcherBatchCreation(t *testing.T) {
runTests(t, testSweepBatcherBatchCreation) runTests(t, testSweepBatcherBatchCreation)
} }
// TestFeeBumping tests that sweep is RBFed with slightly higher fee rate after
// each block unless WithNoBumping is passed.
func TestFeeBumping(t *testing.T) {
t.Run("regular", func(t *testing.T) {
runTests(t, func(t *testing.T, store testStore,
batcherStore testBatcherStore) {
testFeeBumping(t, store, batcherStore, false)
})
})
t.Run("WithNoBumping", func(t *testing.T) {
runTests(t, func(t *testing.T, store testStore,
batcherStore testBatcherStore) {
testFeeBumping(t, store, batcherStore, true)
})
})
}
// TestSweepBatcherSimpleLifecycle tests the simple lifecycle of the batches // TestSweepBatcherSimpleLifecycle tests the simple lifecycle of the batches
// that are created and run by the batcher. // that are created and run by the batcher.
func TestSweepBatcherSimpleLifecycle(t *testing.T) { func TestSweepBatcherSimpleLifecycle(t *testing.T) {