Skip to content

Commit 0190178

Browse files
committed
sweepbatcher/presigned: minRelayFee edge cases
Make sure that broadcasted tx has feeRate >= minRelayFee. Make sure that feeRate of broadcasted tx doesn't decrease.
1 parent 03f85c5 commit 0190178

File tree

2 files changed

+132
-8
lines changed

2 files changed

+132
-8
lines changed

sweepbatcher/presigned.go

+11-8
Original file line numberDiff line numberDiff line change
@@ -401,9 +401,16 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
401401
}
402402
}
403403

404+
// Determine the current minimum relay fee based on our chain backend.
405+
minRelayFee, err := b.wallet.MinRelayFee(ctx)
406+
if err != nil {
407+
return 0, fmt.Errorf("failed to get minRelayFee: %w", err),
408+
false
409+
}
410+
404411
// Cache current height and desired feerate of the batch.
405412
currentHeight := b.currentHeight
406-
feeRate := b.rbfCache.FeeRate
413+
feeRate := max(b.rbfCache.FeeRate, minRelayFee)
407414

408415
// Append this sweep to an array of sweeps. This is needed to keep the
409416
// order of sweeps stored, as iterating the sweeps map does not
@@ -441,13 +448,6 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
441448
batchAmt += sweep.value
442449
}
443450

444-
// Determine the current minimum relay fee based on our chain backend.
445-
minRelayFee, err := b.wallet.MinRelayFee(ctx)
446-
if err != nil {
447-
return 0, fmt.Errorf("failed to get minRelayFee: %w", err),
448-
false
449-
}
450-
451451
// Get a pre-signed transaction.
452452
const loadOnly = false
453453
signedTx, err := b.cfg.presignedHelper.SignTx(
@@ -501,6 +501,9 @@ func (b *batch) publishPresigned(ctx context.Context) (btcutil.Amount, error,
501501
b.batchTxid = &txHash
502502
b.batchPkScript = tx.TxOut[0].PkScript
503503

504+
// Update cached FeeRate not to broadcast a tx with lower feeRate.
505+
b.rbfCache.FeeRate = max(b.rbfCache.FeeRate, signedFeeRate)
506+
504507
return fee, nil, true
505508
}
506509

sweepbatcher/sweep_batcher_presigned_test.go

+121
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ func (h *mockPresignedHelper) SignTx(ctx context.Context,
159159
h.mu.Lock()
160160
defer h.mu.Unlock()
161161

162+
if feeRate < minRelayFee {
163+
return nil, fmt.Errorf("feeRate (%v) is below minRelayFee (%v)",
164+
feeRate, minRelayFee)
165+
}
166+
162167
// If all the inputs are online and loadOnly is not set, sign this exact
163168
// transaction.
164169
if offline := h.offlineInputs(tx); len(offline) == 0 && !loadOnly {
@@ -492,6 +497,118 @@ func testPresigned_input1_offline_then_input2(t *testing.T,
492497
require.NoError(t, err)
493498
}
494499

500+
// testPresigned_min_relay_fee tests that online and presigned transactions
501+
// comply with min_relay_fee.
502+
func testPresigned_min_relay_fee(t *testing.T,
503+
batcherStore testBatcherStore) {
504+
505+
defer test.Guard(t)()
506+
507+
lnd := test.NewMockLnd()
508+
ctx, cancel := context.WithCancel(context.Background())
509+
defer cancel()
510+
511+
const inputAmt = 1_000_000
512+
513+
customFeeRate := func(_ context.Context, _ lntypes.Hash,
514+
_ wire.OutPoint) (chainfee.SatPerKWeight, error) {
515+
516+
return chainfee.FeePerKwFloor, nil
517+
}
518+
519+
presignedHelper := newMockPresignedHelper()
520+
521+
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
522+
testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams,
523+
batcherStore, presignedHelper,
524+
WithCustomFeeRate(customFeeRate),
525+
WithPresignedHelper(presignedHelper))
526+
go func() {
527+
err := batcher.Run(ctx)
528+
checkBatcherError(t, err)
529+
}()
530+
531+
// Set high min_relay_fee.
532+
lnd.SetMinRelayFee(400)
533+
534+
// Create the first sweep.
535+
swapHash1 := lntypes.Hash{1, 1, 1}
536+
op1 := wire.OutPoint{
537+
Hash: chainhash.Hash{1, 1},
538+
Index: 1,
539+
}
540+
sweepReq1 := SweepRequest{
541+
SwapHash: swapHash1,
542+
Inputs: []Input{{
543+
Value: inputAmt,
544+
Outpoint: op1,
545+
}},
546+
Notifier: &dummyNotifier,
547+
}
548+
549+
// Enable the input and presign.
550+
presignedHelper.SetOutpointOnline(op1, true)
551+
err := batcher.PresignSweepsGroup(
552+
ctx, []Input{{Outpoint: op1, Value: inputAmt}},
553+
sweepTimeout, destAddr,
554+
)
555+
require.NoError(t, err)
556+
557+
// Deliver sweep request to batcher.
558+
require.NoError(t, batcher.AddSweep(ctx, &sweepReq1))
559+
560+
// Since a batch was created we check that it registered for its primary
561+
// sweep's spend.
562+
<-lnd.RegisterSpendChannel
563+
564+
// Wait for a transactions to be published.
565+
tx := <-lnd.TxPublishChannel
566+
gotFeeRate := presignedHelper.getTxFeerate(tx, inputAmt)
567+
require.Equal(t, chainfee.SatPerKWeight(402), gotFeeRate)
568+
569+
// Now decrease min_relay_fee and make sure fee rate doesn't decrease.
570+
// The only difference of tx2 is a higher lock_time.
571+
lnd.SetMinRelayFee(300)
572+
require.NoError(t, lnd.NotifyHeight(601))
573+
tx2 := <-lnd.TxPublishChannel
574+
require.Equal(t, tx.TxOut[0].Value, tx2.TxOut[0].Value)
575+
gotFeeRate = presignedHelper.getTxFeerate(tx2, inputAmt)
576+
require.Equal(t, chainfee.SatPerKWeight(402), gotFeeRate)
577+
require.Equal(t, uint32(601), tx2.LockTime)
578+
579+
// Set a higher min_relay_fee, turn off the client and try presigned tx.
580+
lnd.SetMinRelayFee(500)
581+
presignedHelper.SetOutpointOnline(op1, false)
582+
583+
// Check fee rate of the presigned tx broadcasted.
584+
require.NoError(t, lnd.NotifyHeight(602))
585+
tx = <-lnd.TxPublishChannel
586+
gotFeeRate = presignedHelper.getTxFeerate(tx, inputAmt)
587+
require.Equal(t, chainfee.SatPerKWeight(523), gotFeeRate)
588+
// LockTime of a presigned tx is 0.
589+
require.Equal(t, uint32(0), tx.LockTime)
590+
591+
// Now decrease min_relay_fee and make sure fee rate doesn't decrease.
592+
// It should re-broadcast the same presigned tx.
593+
lnd.SetMinRelayFee(450)
594+
require.NoError(t, lnd.NotifyHeight(603))
595+
tx2 = <-lnd.TxPublishChannel
596+
require.Equal(t, tx.TxHash(), tx2.TxHash())
597+
gotFeeRate = presignedHelper.getTxFeerate(tx2, inputAmt)
598+
require.Equal(t, chainfee.SatPerKWeight(523), gotFeeRate)
599+
// LockTime of a presigned tx is 0.
600+
require.Equal(t, uint32(0), tx2.LockTime)
601+
602+
// Even if the client is back online, fee rate doesn't decrease.
603+
presignedHelper.SetOutpointOnline(op1, true)
604+
require.NoError(t, lnd.NotifyHeight(604))
605+
tx3 := <-lnd.TxPublishChannel
606+
require.Equal(t, tx2.TxOut[0].Value, tx3.TxOut[0].Value)
607+
gotFeeRate = presignedHelper.getTxFeerate(tx3, inputAmt)
608+
require.Equal(t, chainfee.SatPerKWeight(523), gotFeeRate)
609+
require.Equal(t, uint32(604), tx3.LockTime)
610+
}
611+
495612
// testPresigned_two_inputs_one_goes_offline tests presigned mode for the
496613
// following scenario: two online inputs are added, then one of them goes
497614
// offline, then feerate grows and a presigned transaction is used.
@@ -1690,6 +1807,10 @@ func TestPresigned(t *testing.T) {
16901807
testPresigned_input1_offline_then_input2(t, NewStoreMock())
16911808
})
16921809

1810+
t.Run("min_relay_fee", func(t *testing.T) {
1811+
testPresigned_min_relay_fee(t, NewStoreMock())
1812+
})
1813+
16931814
t.Run("two_inputs_one_goes_offline", func(t *testing.T) {
16941815
testPresigned_two_inputs_one_goes_offline(t, NewStoreMock())
16951816
})

0 commit comments

Comments
 (0)