Skip to content

Commit fd971d4

Browse files
committed
sweepbatcher: add field SweepInfo.MinFeeRate
MinFeeRate is minimum fee rate that must be used by a batch of the sweep. If it is specified, confTarget is ignored. This is useful for external source of fees.
1 parent 4e085f1 commit fd971d4

File tree

3 files changed

+90
-23
lines changed

3 files changed

+90
-23
lines changed

sweepbatcher/sweep_batch.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ type sweep struct {
9797
// notifier is a collection of channels used to communicate the status
9898
// of the sweep back to the swap that requested it.
9999
notifier *SpendNotifier
100+
101+
// minFeeRate is minimum fee rate that must be used by a batch of
102+
// the sweep. If it is specified, confTarget is ignored.
103+
minFeeRate chainfee.SatPerKWeight
100104
}
101105

102106
// batchState is the state of the batch.
@@ -399,9 +403,10 @@ func (b *batch) addSweep(ctx context.Context, sweep *sweep) (bool, error) {
399403
b.sweeps[sweep.swapHash] = *sweep
400404

401405
// If this is the primary sweep, we also need to update the
402-
// batch's confirmation target.
406+
// batch's confirmation target and fee rate.
403407
if b.primarySweepID == sweep.swapHash {
404408
b.cfg.batchConfTarget = sweep.confTarget
409+
b.rbfCache.FeeRate = sweep.minFeeRate
405410
}
406411

407412
return true, nil
@@ -443,6 +448,7 @@ func (b *batch) addSweep(ctx context.Context, sweep *sweep) (bool, error) {
443448
if b.primarySweepID == lntypes.ZeroHash {
444449
b.primarySweepID = sweep.swapHash
445450
b.cfg.batchConfTarget = sweep.confTarget
451+
b.rbfCache.FeeRate = sweep.minFeeRate
446452

447453
// We also need to start the spend monitor for this new primary
448454
// sweep.
@@ -456,6 +462,12 @@ func (b *batch) addSweep(ctx context.Context, sweep *sweep) (bool, error) {
456462
b.log.Infof("adding sweep %x", sweep.swapHash[:6])
457463
b.sweeps[sweep.swapHash] = *sweep
458464

465+
// Update FeeRate. Max(sweep.minFeeRate) for all the sweeps of
466+
// the batch is the basis for fee bumps.
467+
if b.rbfCache.FeeRate < sweep.minFeeRate {
468+
b.rbfCache.FeeRate = sweep.minFeeRate
469+
}
470+
459471
return true, b.persistSweep(ctx, *sweep, false)
460472
}
461473

sweepbatcher/sweep_batcher.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ type SweepInfo struct {
120120

121121
// DestAddr is the destination address of the sweep.
122122
DestAddr btcutil.Address
123+
124+
// MinFeeRate is minimum fee rate that must be used by a batch of
125+
// the sweep. If it is specified, confTarget is ignored.
126+
MinFeeRate chainfee.SatPerKWeight
123127
}
124128

125129
// SweepFetcher is used to get details of a sweep.
@@ -523,18 +527,26 @@ func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
523527

524528
sweeps := make(map[lntypes.Hash]sweep)
525529

530+
// Collect feeRate from sweeps and stored batch.
531+
feeRate := batch.rbfCache.FeeRate
532+
526533
for _, dbSweep := range dbSweeps {
527534
sweep, err := b.convertSweep(ctx, dbSweep)
528535
if err != nil {
529536
return err
530537
}
531538

532539
sweeps[sweep.swapHash] = *sweep
540+
541+
// Set minFeeRate to max(sweep.minFeeRate) for all sweeps.
542+
if feeRate < sweep.minFeeRate {
543+
feeRate = sweep.minFeeRate
544+
}
533545
}
534546

535547
rbfCache := rbfCache{
536548
LastHeight: batch.rbfCache.LastHeight,
537-
FeeRate: batch.rbfCache.FeeRate,
549+
FeeRate: feeRate,
538550
}
539551

540552
logger := batchPrefixLogger(fmt.Sprintf("%d", batch.id))
@@ -758,6 +770,7 @@ func (b *Batcher) convertSweep(ctx context.Context, dbSweep *dbSweep) (
758770
protocolVersion: s.ProtocolVersion,
759771
isExternalAddr: s.IsExternalAddr,
760772
destAddr: s.DestAddr,
773+
minFeeRate: s.MinFeeRate,
761774
}, nil
762775
}
763776

@@ -855,5 +868,6 @@ func (b *Batcher) fetchSweep(ctx context.Context,
855868
protocolVersion: s.ProtocolVersion,
856869
isExternalAddr: s.IsExternalAddr,
857870
destAddr: s.DestAddr,
871+
minFeeRate: s.MinFeeRate,
858872
}, nil
859873
}

sweepbatcher/sweep_batcher_test.go

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/lightningnetwork/lnd/chainntnfs"
1919
"github.com/lightningnetwork/lnd/keychain"
2020
"github.com/lightningnetwork/lnd/lntypes"
21+
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
2122
"github.com/stretchr/testify/require"
2223
)
2324

@@ -1746,20 +1747,64 @@ func testSweepFetcher(t *testing.T, store testStore,
17461747
)
17471748
require.NoError(t, err)
17481749

1750+
swapHash := lntypes.Hash{1, 1, 1}
1751+
1752+
// Provide min fee rate for the sweep.
1753+
feeRate := chainfee.SatPerKWeight(30000)
1754+
amt := btcutil.Amount(1_000_000)
1755+
weight := lntypes.WeightUnit(445) // Weight for 1-to-1 tx.
1756+
bumpedFee := feeRate + 100
1757+
expectedFee := bumpedFee.FeeForWeight(weight)
1758+
1759+
swap := &loopdb.LoopOutContract{
1760+
SwapContract: loopdb.SwapContract{
1761+
CltvExpiry: 222,
1762+
AmountRequested: amt,
1763+
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
1764+
HtlcKeys: loopdb.HtlcKeys{
1765+
SenderScriptKey: senderKey,
1766+
ReceiverScriptKey: receiverKey,
1767+
SenderInternalPubKey: senderKey,
1768+
ReceiverInternalPubKey: receiverKey,
1769+
},
1770+
},
1771+
DestAddr: destAddr,
1772+
SwapInvoice: swapInvoice,
1773+
SweepConfTarget: 321,
1774+
}
1775+
1776+
htlc, err := utils.GetHtlc(
1777+
swapHash, &swap.SwapContract, lnd.ChainParams,
1778+
)
1779+
require.NoError(t, err)
1780+
1781+
sweepInfo := &SweepInfo{
1782+
ConfTarget: 123,
1783+
Timeout: 111,
1784+
SwapInvoicePaymentAddr: *swapPaymentAddr,
1785+
MinFeeRate: feeRate,
1786+
ProtocolVersion: loopdb.ProtocolVersionMuSig2,
1787+
HTLCKeys: loopdb.HtlcKeys{
1788+
SenderScriptKey: senderKey,
1789+
ReceiverScriptKey: receiverKey,
1790+
SenderInternalPubKey: senderKey,
1791+
ReceiverInternalPubKey: receiverKey,
1792+
},
1793+
HTLC: *htlc,
1794+
HTLCSuccessEstimator: htlc.AddSuccessToEstimator,
1795+
DestAddr: destAddr,
1796+
}
1797+
17491798
sweepFetcher := &sweepFetcherMock{
17501799
store: map[lntypes.Hash]*SweepInfo{
1751-
{1, 1, 1}: {
1752-
ConfTarget: 123,
1753-
Timeout: 111,
1754-
SwapInvoicePaymentAddr: *swapPaymentAddr,
1755-
},
1800+
swapHash: sweepInfo,
17561801
},
17571802
}
17581803

17591804
// Create a sweep request.
17601805
sweepReq := SweepRequest{
1761-
SwapHash: lntypes.Hash{1, 1, 1},
1762-
Value: 111,
1806+
SwapHash: swapHash,
1807+
Value: amt,
17631808
Outpoint: wire.OutPoint{
17641809
Hash: chainhash.Hash{1, 1},
17651810
Index: 1,
@@ -1770,23 +1815,13 @@ func testSweepFetcher(t *testing.T, store testStore,
17701815
// Create a swap in the DB. It is needed to satisfy SQL constraints in
17711816
// case of SQL test. The data is not actually used, since we pass sweep
17721817
// fetcher, so put different conf target to make sure it is not used.
1773-
swap := &loopdb.LoopOutContract{
1774-
SwapContract: loopdb.SwapContract{
1775-
CltvExpiry: 222,
1776-
AmountRequested: 222,
1777-
},
1778-
1779-
DestAddr: destAddr,
1780-
SwapInvoice: swapInvoice,
1781-
SweepConfTarget: 321,
1782-
}
1783-
err = store.CreateLoopOut(ctx, sweepReq.SwapHash, swap)
1818+
err = store.CreateLoopOut(ctx, swapHash, swap)
17841819
require.NoError(t, err)
17851820
store.AssertLoopOutStored()
17861821

17871822
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
1788-
testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore,
1789-
sweepFetcher)
1823+
testMuSig2SignSweep, testVerifySchnorrSig, lnd.ChainParams,
1824+
batcherStore, sweepFetcher)
17901825

17911826
var wg sync.WaitGroup
17921827
wg.Add(1)
@@ -1811,7 +1846,7 @@ func testSweepFetcher(t *testing.T, store testStore,
18111846
// batch.
18121847
require.Eventually(t, func() bool {
18131848
// Make sure that the sweep was stored
1814-
if !batcherStore.AssertSweepStored(sweepReq.SwapHash) {
1849+
if !batcherStore.AssertSweepStored(swapHash) {
18151850
return false
18161851
}
18171852

@@ -1832,6 +1867,12 @@ func testSweepFetcher(t *testing.T, store testStore,
18321867
return batch.cfg.batchConfTarget == 123
18331868
}, test.Timeout, eventuallyCheckFrequency)
18341869

1870+
// Get the published transaction and check the fee rate.
1871+
tx := <-lnd.TxPublishChannel
1872+
out := btcutil.Amount(tx.TxOut[0].Value)
1873+
gotFee := amt - out
1874+
require.Equal(t, expectedFee, gotFee, "fees don't match")
1875+
18351876
// Make sure we have stored the batch.
18361877
batches, err := batcherStore.FetchUnconfirmedSweepBatches(ctx)
18371878
require.NoError(t, err)

0 commit comments

Comments
 (0)