Skip to content

Commit 2c32575

Browse files
authored
Merge pull request #833 from starius/sweepbatcher-feerate-grows
sweepbatcher: make sure feerate does not decrease
2 parents f0a25f9 + 864d7f2 commit 2c32575

File tree

2 files changed

+189
-1
lines changed

2 files changed

+189
-1
lines changed

sweepbatcher/sweep_batch.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,18 @@ func (b *batch) addSweep(ctx context.Context, sweep *sweep) (bool, error) {
470470
// batch's confirmation target and fee rate.
471471
if b.primarySweepID == sweep.swapHash {
472472
b.cfg.batchConfTarget = sweep.confTarget
473-
b.rbfCache.FeeRate = sweep.minFeeRate
474473
b.rbfCache.SkipNextBump = true
475474
}
476475

476+
// Update batch's fee rate to be greater than or equal to
477+
// minFeeRate of the sweep. Make sure batch's fee rate does not
478+
// decrease (otherwise it won't pass RBF rules and won't be
479+
// broadcasted) and that it is not lower that minFeeRate of
480+
// other sweeps (so it is applied).
481+
if b.rbfCache.FeeRate < sweep.minFeeRate {
482+
b.rbfCache.FeeRate = sweep.minFeeRate
483+
}
484+
477485
return true, nil
478486
}
479487

sweepbatcher/sweep_batcher_test.go

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3698,6 +3698,180 @@ func testWithMixedBatchCoopFailedOnly(t *testing.T, store testStore,
36983698
wantWeight, wantWitnessSizes)
36993699
}
37003700

3701+
// testFeeRateGrows tests that fee rate of a batch does not decrease and is at
3702+
// least as high as the highest fee rate of sweeps.
3703+
func testFeeRateGrows(t *testing.T, store testStore,
3704+
batcherStore testBatcherStore) {
3705+
3706+
defer test.Guard(t)()
3707+
3708+
lnd := test.NewMockLnd()
3709+
ctx, cancel := context.WithCancel(context.Background())
3710+
defer cancel()
3711+
3712+
sweepStore, err := NewSweepFetcherFromSwapStore(store, lnd.ChainParams)
3713+
require.NoError(t, err)
3714+
3715+
// Create a map to store fee rates.
3716+
swap2feeRate := map[lntypes.Hash]chainfee.SatPerKWeight{}
3717+
var swap2feeRateMu sync.Mutex
3718+
setFeeRate := func(swapHash lntypes.Hash, rate chainfee.SatPerKWeight) {
3719+
swap2feeRateMu.Lock()
3720+
defer swap2feeRateMu.Unlock()
3721+
3722+
swap2feeRate[swapHash] = rate
3723+
}
3724+
3725+
customFeeRate := func(ctx context.Context,
3726+
swapHash lntypes.Hash) (chainfee.SatPerKWeight, error) {
3727+
3728+
swap2feeRateMu.Lock()
3729+
defer swap2feeRateMu.Unlock()
3730+
3731+
return swap2feeRate[swapHash], nil
3732+
}
3733+
3734+
const (
3735+
feeRateLow = chainfee.SatPerKWeight(10_000)
3736+
feeRateMedium = chainfee.SatPerKWeight(30_000)
3737+
feeRateHigh = chainfee.SatPerKWeight(50_000)
3738+
)
3739+
3740+
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
3741+
testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams,
3742+
batcherStore, sweepStore, WithCustomFeeRate(customFeeRate))
3743+
3744+
go func() {
3745+
err := batcher.Run(ctx)
3746+
checkBatcherError(t, err)
3747+
}()
3748+
3749+
// Create the first sweep.
3750+
swapHash1 := lntypes.Hash{1, 1, 1}
3751+
setFeeRate(swapHash1, feeRateMedium)
3752+
sweepReq1 := SweepRequest{
3753+
SwapHash: swapHash1,
3754+
Value: 1_000_000,
3755+
Outpoint: wire.OutPoint{
3756+
Hash: chainhash.Hash{1, 1},
3757+
Index: 1,
3758+
},
3759+
Notifier: &dummyNotifier,
3760+
}
3761+
3762+
swap1 := &loopdb.LoopOutContract{
3763+
SwapContract: loopdb.SwapContract{
3764+
CltvExpiry: 111,
3765+
AmountRequested: 1_000_000,
3766+
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
3767+
HtlcKeys: htlcKeys,
3768+
3769+
// Make preimage unique to pass SQL constraints.
3770+
Preimage: lntypes.Preimage{1},
3771+
},
3772+
3773+
DestAddr: destAddr,
3774+
SwapInvoice: swapInvoice,
3775+
SweepConfTarget: 111,
3776+
}
3777+
3778+
err = store.CreateLoopOut(ctx, swapHash1, swap1)
3779+
require.NoError(t, err)
3780+
store.AssertLoopOutStored()
3781+
3782+
// Deliver sweep request to batcher.
3783+
require.NoError(t, batcher.AddSweep(&sweepReq1))
3784+
3785+
// Since a batch was created we check that it registered for its primary
3786+
// sweep's spend.
3787+
<-lnd.RegisterSpendChannel
3788+
3789+
// Wait for tx to be published.
3790+
<-lnd.TxPublishChannel
3791+
3792+
// Make sure the fee rate is feeRateMedium.
3793+
batch := getOnlyBatch(batcher)
3794+
require.Len(t, batch.sweeps, 1)
3795+
require.Equal(t, feeRateMedium, batch.rbfCache.FeeRate)
3796+
3797+
// Now decrease the fee of sweep1.
3798+
setFeeRate(swapHash1, feeRateLow)
3799+
require.NoError(t, batcher.AddSweep(&sweepReq1))
3800+
3801+
// Tick tock next block.
3802+
err = lnd.NotifyHeight(601)
3803+
require.NoError(t, err)
3804+
3805+
// Wait for tx to be published.
3806+
<-lnd.TxPublishChannel
3807+
3808+
// Make sure the fee rate is still feeRateMedium.
3809+
require.Equal(t, feeRateMedium, batch.rbfCache.FeeRate)
3810+
3811+
// Add sweep2, with feeRateMedium.
3812+
swapHash2 := lntypes.Hash{2, 2, 2}
3813+
setFeeRate(swapHash2, feeRateMedium)
3814+
sweepReq2 := SweepRequest{
3815+
SwapHash: swapHash2,
3816+
Value: 1_000_000,
3817+
Outpoint: wire.OutPoint{
3818+
Hash: chainhash.Hash{2, 2},
3819+
Index: 1,
3820+
},
3821+
Notifier: &dummyNotifier,
3822+
}
3823+
3824+
swap2 := &loopdb.LoopOutContract{
3825+
SwapContract: loopdb.SwapContract{
3826+
CltvExpiry: 111,
3827+
AmountRequested: 1_000_000,
3828+
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
3829+
HtlcKeys: htlcKeys,
3830+
3831+
// Make preimage unique to pass SQL constraints.
3832+
Preimage: lntypes.Preimage{2},
3833+
},
3834+
3835+
DestAddr: destAddr,
3836+
SwapInvoice: swapInvoice,
3837+
SweepConfTarget: 111,
3838+
}
3839+
3840+
err = store.CreateLoopOut(ctx, swapHash2, swap2)
3841+
require.NoError(t, err)
3842+
store.AssertLoopOutStored()
3843+
3844+
// Deliver sweep request to batcher.
3845+
require.NoError(t, batcher.AddSweep(&sweepReq2))
3846+
3847+
// Tick tock next block.
3848+
err = lnd.NotifyHeight(602)
3849+
require.NoError(t, err)
3850+
3851+
// Wait for tx to be published.
3852+
<-lnd.TxPublishChannel
3853+
3854+
// Make sure the fee rate is still feeRateMedium.
3855+
require.Len(t, batch.sweeps, 2)
3856+
require.Equal(t, feeRateMedium, batch.rbfCache.FeeRate)
3857+
3858+
// Now update fee rate of second sweep (which is not primary) to
3859+
// feeRateHigh. Fee rate of sweep 1 is still feeRateLow.
3860+
setFeeRate(swapHash2, feeRateHigh)
3861+
require.NoError(t, batcher.AddSweep(&sweepReq1))
3862+
require.NoError(t, batcher.AddSweep(&sweepReq2))
3863+
3864+
// Tick tock next block.
3865+
err = lnd.NotifyHeight(603)
3866+
require.NoError(t, err)
3867+
3868+
// Wait for tx to be published.
3869+
<-lnd.TxPublishChannel
3870+
3871+
// Make sure the fee rate increased to feeRateHigh.
3872+
require.Equal(t, feeRateHigh, batch.rbfCache.FeeRate)
3873+
}
3874+
37013875
// TestSweepBatcherBatchCreation tests that sweep requests enter the expected
37023876
// batch based on their timeout distance.
37033877
func TestSweepBatcherBatchCreation(t *testing.T) {
@@ -3839,6 +4013,12 @@ func TestWithMixedBatchCoopFailedOnly(t *testing.T) {
38394013
runTests(t, testWithMixedBatchCoopFailedOnly)
38404014
}
38414015

4016+
// TestFeeRateGrows tests that fee rate of a batch does not decrease and is at
4017+
// least as high as the highest fee rate of sweeps.
4018+
func TestFeeRateGrows(t *testing.T) {
4019+
runTests(t, testFeeRateGrows)
4020+
}
4021+
38424022
// testBatcherStore is BatcherStore used in tests.
38434023
type testBatcherStore interface {
38444024
BatcherStore

0 commit comments

Comments
 (0)