Skip to content

Commit 40ad1ce

Browse files
committed
sweepbatcher: load from DB preserves confTarget
It used to be set to default (defaultBatchConfTarget = 12) which could in theory affect fee rate if updateRbfRate() and publish() were not called before the batch was saved. (Unlikely scenario.)
1 parent d38b7c5 commit 40ad1ce

File tree

3 files changed

+139
-5
lines changed

3 files changed

+139
-5
lines changed

sweepbatcher/sweep_batch.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,22 @@ func NewBatch(cfg batchConfig, bk batchKit) *batch {
316316
}
317317

318318
// NewBatchFromDB creates a new batch that already existed in storage.
319-
func NewBatchFromDB(cfg batchConfig, bk batchKit) *batch {
319+
func NewBatchFromDB(cfg batchConfig, bk batchKit) (*batch, error) {
320+
// Make sure the batch is not empty.
321+
if len(bk.sweeps) == 0 {
322+
// This should never happen, as this precondition is already
323+
// ensured in spinUpBatchFromDB.
324+
return nil, fmt.Errorf("empty batch is not allowed")
325+
}
326+
327+
// Assign batchConfTarget to primary sweep's confTarget.
328+
for _, sweep := range bk.sweeps {
329+
if sweep.swapHash == bk.primaryID {
330+
cfg.batchConfTarget = sweep.confTarget
331+
break
332+
}
333+
}
334+
320335
return &batch{
321336
id: bk.id,
322337
state: bk.state,
@@ -343,7 +358,7 @@ func NewBatchFromDB(cfg batchConfig, bk batchKit) *batch {
343358
store: bk.store,
344359
log: bk.log,
345360
cfg: &cfg,
346-
}
361+
}, nil
347362
}
348363

349364
// addSweep tries to add a sweep to the batch. If this is the first sweep being

sweepbatcher/sweep_batcher.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,10 @@ func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
491491
batchConfTarget: defaultBatchConfTarget,
492492
}
493493

494-
newBatch := NewBatchFromDB(cfg, batchKit)
494+
newBatch, err := NewBatchFromDB(cfg, batchKit)
495+
if err != nil {
496+
return fmt.Errorf("failed in NewBatchFromDB: %w", err)
497+
}
495498

496499
// We add the batch to our map of batches and start it.
497500
b.batches[batch.id] = newBatch

sweepbatcher/sweep_batcher_test.go

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ func TestRestoringEmptyBatch(t *testing.T) {
11091109
require.NoError(t, err)
11101110
require.Len(t, batches, 1)
11111111

1112-
// Now make it quit by canceling the context.
1112+
// Now make the batcher quit by canceling the context.
11131113
cancel()
11141114
wg.Wait()
11151115

@@ -1297,9 +1297,125 @@ func TestHandleSweepTwice(t *testing.T) {
12971297
require.Equal(t, 1, len(batcher.batches[0].sweeps))
12981298
require.Equal(t, 1, len(batcher.batches[1].sweeps))
12991299

1300-
// Now make it quit by canceling the context.
1300+
// Now make the batcher quit by canceling the context.
13011301
cancel()
13021302
wg.Wait()
13031303

13041304
checkBatcherError(t, runErr)
13051305
}
1306+
1307+
// TestRestoringPreservesConfTarget tests that after the batch is written to DB
1308+
// and loaded back, its batchConfTarget value is preserved.
1309+
func TestRestoringPreservesConfTarget(t *testing.T) {
1310+
defer test.Guard(t)()
1311+
1312+
lnd := test.NewMockLnd()
1313+
ctx, cancel := context.WithCancel(context.Background())
1314+
1315+
store := loopdb.NewStoreMock(t)
1316+
1317+
batcherStore := NewStoreMock(store)
1318+
1319+
batcher := NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
1320+
testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, store)
1321+
1322+
var wg sync.WaitGroup
1323+
wg.Add(1)
1324+
1325+
var runErr error
1326+
go func() {
1327+
defer wg.Done()
1328+
runErr = batcher.Run(ctx)
1329+
}()
1330+
1331+
// Wait for the batcher to be initialized.
1332+
<-batcher.initDone
1333+
1334+
// Create a sweep request.
1335+
sweepReq := SweepRequest{
1336+
SwapHash: lntypes.Hash{1, 1, 1},
1337+
Value: 111,
1338+
Outpoint: wire.OutPoint{
1339+
Hash: chainhash.Hash{1, 1},
1340+
Index: 1,
1341+
},
1342+
Notifier: &dummyNotifier,
1343+
}
1344+
1345+
swap := &loopdb.LoopOutContract{
1346+
SwapContract: loopdb.SwapContract{
1347+
CltvExpiry: 111,
1348+
AmountRequested: 111,
1349+
},
1350+
1351+
SwapInvoice: swapInvoice,
1352+
SweepConfTarget: 123,
1353+
}
1354+
1355+
err := store.CreateLoopOut(ctx, sweepReq.SwapHash, swap)
1356+
require.NoError(t, err)
1357+
store.AssertLoopOutStored()
1358+
1359+
// Deliver sweep request to batcher.
1360+
require.NoError(t, batcher.AddSweep(&sweepReq))
1361+
1362+
// Since a batch was created we check that it registered for its primary
1363+
// sweep's spend.
1364+
<-lnd.RegisterSpendChannel
1365+
1366+
// Once batcher receives sweep request it will eventually spin up a
1367+
// batch.
1368+
require.Eventually(t, func() bool {
1369+
// Make sure that the sweep was stored and we have exactly one
1370+
// active batch, with one sweep and proper batchConfTarget.
1371+
return batcherStore.AssertSweepStored(sweepReq.SwapHash) &&
1372+
len(batcher.batches) == 1 &&
1373+
len(batcher.batches[0].sweeps) == 1 &&
1374+
batcher.batches[0].cfg.batchConfTarget == 123
1375+
}, test.Timeout, eventuallyCheckFrequency)
1376+
1377+
// Make sure we have stored the batch.
1378+
batches, err := batcherStore.FetchUnconfirmedSweepBatches(ctx)
1379+
require.NoError(t, err)
1380+
require.Len(t, batches, 1)
1381+
1382+
// Now make the batcher quit by canceling the context.
1383+
cancel()
1384+
wg.Wait()
1385+
1386+
// Make sure the batcher exited without an error.
1387+
checkBatcherError(t, runErr)
1388+
1389+
// Now launch it again.
1390+
batcher = NewBatcher(lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
1391+
testMuSig2SignSweep, nil, lnd.ChainParams, batcherStore, store)
1392+
ctx, cancel = context.WithCancel(context.Background())
1393+
wg.Add(1)
1394+
go func() {
1395+
defer wg.Done()
1396+
runErr = batcher.Run(ctx)
1397+
}()
1398+
1399+
// Wait for the batcher to be initialized.
1400+
<-batcher.initDone
1401+
1402+
// Wait for batch to load.
1403+
require.Eventually(t, func() bool {
1404+
return batcherStore.AssertSweepStored(sweepReq.SwapHash) &&
1405+
len(batcher.batches) == 1 &&
1406+
len(batcher.batches[0].sweeps) == 1
1407+
}, test.Timeout, eventuallyCheckFrequency)
1408+
1409+
// Make sure batchConfTarget was preserved.
1410+
require.Equal(t, 123, int(batcher.batches[0].cfg.batchConfTarget))
1411+
1412+
// Expect registration for spend notification.
1413+
<-lnd.RegisterSpendChannel
1414+
1415+
// Now make the batcher quit by canceling the context.
1416+
cancel()
1417+
wg.Wait()
1418+
1419+
// Make sure the batcher exited without an error.
1420+
checkBatcherError(t, runErr)
1421+
}

0 commit comments

Comments
 (0)