@@ -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.
37033877func 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.
38434023type testBatcherStore interface {
38444024 BatcherStore
0 commit comments