Skip to content

Commit 109d86f

Browse files
authored
Merge pull request #1031 from lightninglabs/minter_unit_race_fixes
tapgarden: improve fault injection in unit tests
2 parents 7b8ba2d + f16c98f commit 109d86f

File tree

2 files changed

+41
-24
lines changed

2 files changed

+41
-24
lines changed

tapgarden/mock.go

+35-15
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,12 @@ type MockChainBridge struct {
310310

311311
NewBlocks chan int32
312312

313-
ReqCount int
313+
ReqCount atomic.Int32
314314
ConfReqs map[int]*chainntnfs.ConfirmationEvent
315315

316316
failFeeEstimates atomic.Bool
317-
emptyConf bool
318-
errConf bool
317+
errConf atomic.Int32
318+
emptyConf atomic.Int32
319319
confErr chan error
320320
}
321321

@@ -334,19 +334,30 @@ func (m *MockChainBridge) FailFeeEstimatesOnce() {
334334
m.failFeeEstimates.Store(true)
335335
}
336336

337-
func (m *MockChainBridge) FailConf(enable bool) {
338-
m.errConf = enable
337+
// FailConfOnce updates the ChainBridge such that the next call to
338+
// RegisterConfirmationNtfn will fail by returning an error on the error channel
339+
// returned from RegisterConfirmationNtfn.
340+
func (m *MockChainBridge) FailConfOnce() {
341+
// Store the incremented request count so we never store 0 as a value.
342+
m.errConf.Store(m.ReqCount.Load() + 1)
339343
}
340-
func (m *MockChainBridge) EmptyConf(enable bool) {
341-
m.emptyConf = enable
344+
345+
// EmptyConfOnce updates the ChainBridge such that the next confirmation event
346+
// sent via SendConfNtfn will have an empty confirmation.
347+
func (m *MockChainBridge) EmptyConfOnce() {
348+
// Store the incremented request count so we never store 0 as a value.
349+
m.emptyConf.Store(m.ReqCount.Load() + 1)
342350
}
343351

344352
func (m *MockChainBridge) SendConfNtfn(reqNo int, blockHash *chainhash.Hash,
345353
blockHeight, blockIndex int, block *wire.MsgBlock,
346354
tx *wire.MsgTx) {
347355

356+
// Compare to the incremented request count since we incremented it
357+
// when storing the request number.
348358
req := m.ConfReqs[reqNo]
349-
if m.emptyConf {
359+
if m.emptyConf.Load() == int32(reqNo)+1 {
360+
m.emptyConf.Store(0)
350361
req.Confirmed <- nil
351362
return
352363
}
@@ -371,7 +382,7 @@ func (m *MockChainBridge) RegisterConfirmationsNtfn(ctx context.Context,
371382
}
372383

373384
defer func() {
374-
m.ReqCount++
385+
m.ReqCount.Add(1)
375386
}()
376387

377388
req := &chainntnfs.ConfirmationEvent{
@@ -380,15 +391,18 @@ func (m *MockChainBridge) RegisterConfirmationsNtfn(ctx context.Context,
380391
}
381392
m.confErr = make(chan error, 1)
382393

383-
m.ConfReqs[m.ReqCount] = req
394+
currentReqCount := m.ReqCount.Load()
395+
m.ConfReqs[int(currentReqCount)] = req
384396

385397
select {
386-
case m.ConfReqSignal <- m.ReqCount:
398+
case m.ConfReqSignal <- int(currentReqCount):
387399
case <-ctx.Done():
388400
}
389401

390-
if m.errConf {
391-
m.confErr <- fmt.Errorf("confirmation error")
402+
// Compare to the incremented request count since we incremented it
403+
// when storing the request number.
404+
if m.errConf.CompareAndSwap(currentReqCount+1, 0) {
405+
m.confErr <- fmt.Errorf("confirmation registration error")
392406
}
393407

394408
return req, m.confErr, nil
@@ -661,7 +675,7 @@ func (m *MockKeyRing) IsLocalKey(context.Context, keychain.KeyDescriptor) bool {
661675

662676
type MockGenSigner struct {
663677
KeyRing *MockKeyRing
664-
FailSigning bool
678+
failSigning atomic.Bool
665679
}
666680

667681
func NewMockGenSigner(keyRing *MockKeyRing) *MockGenSigner {
@@ -670,11 +684,17 @@ func NewMockGenSigner(keyRing *MockKeyRing) *MockGenSigner {
670684
}
671685
}
672686

687+
// FailSigningOnce updates the GenSigner such that the next call to
688+
// SignVirtualTx will fail by returning an error.
689+
func (m *MockGenSigner) FailSigningOnce() {
690+
m.failSigning.Store(true)
691+
}
692+
673693
func (m *MockGenSigner) SignVirtualTx(signDesc *lndclient.SignDescriptor,
674694
virtualTx *wire.MsgTx, prevOut *wire.TxOut) (*schnorr.Signature,
675695
error) {
676696

677-
if m.FailSigning {
697+
if m.failSigning.CompareAndSwap(true, false) {
678698
return nil, fmt.Errorf("failed to sign virtual tx")
679699
}
680700

tapgarden/planter_test.go

+6-9
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ func (t *mintingTestHarness) assertSeedlingsMatchSprouts(
930930
)
931931
require.NoError(t, err)
932932

933-
// Filter out any cancelled batches.
933+
// Filter out any cancelled or frozen batches.
934934
isCommittedBatch := func(batch *tapgarden.MintingBatch) bool {
935935
return batchCommittedStates.Contains(batch.State())
936936
}
@@ -947,7 +947,7 @@ func (t *mintingTestHarness) assertSeedlingsMatchSprouts(
947947
)
948948

949949
// The amount of assets committed to in the Taproot Asset commitment
950-
// should match up
950+
// should match up.
951951
dbAssets := pendingBatch.RootAssetCommitment.CommittedAssets()
952952
require.Len(t, dbAssets, len(seedlings))
953953

@@ -1147,7 +1147,7 @@ func testBasicAssetCreation(t *mintingTestHarness) {
11471147
t.assertNumCaretakersActive(1)
11481148

11491149
// We'll now force yet another restart to ensure correctness of the
1150-
// state machine, we expect the PSBT packet to still be funded.
1150+
// state machine. We expect the PSBT packet to still be funded.
11511151
t.refreshChainPlanter()
11521152
batch = t.fetchSingleBatch(nil)
11531153
t.assertBatchGenesisTx(batch.GenesisPacket)
@@ -1457,7 +1457,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
14571457
// Queue another batch, reset fee estimation behavior, and set TX
14581458
// confirmation registration to fail.
14591459
t.queueInitialBatch(numSeedlings)
1460-
t.chain.FailConf(true)
1460+
t.chain.FailConfOnce()
14611461

14621462
// Finalize the pending batch to start a caretaker, and progress the
14631463
// caretaker to TX confirmation. The finalize call should report no
@@ -1483,8 +1483,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
14831483
// Queue another batch, set TX confirmation to succeed, and set the
14841484
// confirmation event to be empty.
14851485
t.queueInitialBatch(numSeedlings)
1486-
t.chain.FailConf(false)
1487-
t.chain.EmptyConf(true)
1486+
t.chain.EmptyConfOnce()
14881487

14891488
// Start a new caretaker that should reach TX broadcast.
14901489
t.finalizeBatch(&wg, respChan, nil)
@@ -1515,7 +1514,6 @@ func testFinalizeBatch(t *mintingTestHarness) {
15151514

15161515
// Queue another batch and drive the caretaker to a successful minting.
15171516
t.queueInitialBatch(numSeedlings)
1518-
t.chain.EmptyConf(false)
15191517

15201518
// Use a custom feerate and verify that the TX uses that feerate.
15211519
manualFeeRate := chainfee.FeePerKwFloor * 2
@@ -1915,7 +1913,7 @@ func testFundSealOnRestart(t *mintingTestHarness) {
19151913

19161914
// Allow batch funding to succeed, but set group key signing to fail so
19171915
// that batch sealing fails.
1918-
t.genSigner.FailSigning = true
1916+
t.genSigner.FailSigningOnce()
19191917
failedBatchCount++
19201918

19211919
// Create a seedling with emission enabled, to ensure that batch sealing
@@ -1942,7 +1940,6 @@ func testFundSealOnRestart(t *mintingTestHarness) {
19421940

19431941
// Allow batch sealing to succeed. The planter should now be able to
19441942
// start a caretaker for the batch on restart.
1945-
t.genSigner.FailSigning = false
19461943
t.queueSeedlingsInBatch(false, seedlings...)
19471944
batchCount++
19481945

0 commit comments

Comments
 (0)