• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 16252769597

13 Jul 2025 07:40PM UTC coverage: 67.347% (+0.02%) from 67.325%
16252769597

Pull #9878

github

web-flow
Merge a9739b254 into 6b326152d
Pull Request #9878: chainntfns: add option to send all confirmations

190 of 202 new or added lines in 3 files covered. (94.06%)

48 existing lines in 19 files now uncovered.

135386 of 201028 relevant lines covered (67.35%)

21747.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.09
/chainntnfs/test/test_interface.go
1
//go:build dev
2
// +build dev
3

4
package chainntnfstest
5

6
import (
7
        "bytes"
8
        "fmt"
9
        "log"
10
        "sync"
11
        "testing"
12
        "time"
13

14
        "github.com/btcsuite/btcd/btcutil"
15
        "github.com/btcsuite/btcd/chaincfg/chainhash"
16
        "github.com/btcsuite/btcd/integration/rpctest"
17
        "github.com/btcsuite/btcd/rpcclient"
18
        "github.com/btcsuite/btcd/wire"
19
        "github.com/btcsuite/btcwallet/chain"
20
        _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required to auto-register the boltdb walletdb implementation.
21
        "github.com/lightninglabs/neutrino"
22
        "github.com/lightningnetwork/lnd/blockcache"
23
        "github.com/lightningnetwork/lnd/chainntnfs"
24
        "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify"
25
        "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
26
        "github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
27
        "github.com/lightningnetwork/lnd/channeldb"
28
        "github.com/lightningnetwork/lnd/lntest/unittest"
29
        "github.com/lightningnetwork/lnd/lntest/wait"
30
        "github.com/lightningnetwork/lnd/lnutils"
31
        "github.com/stretchr/testify/require"
32
)
33

34
// assertTxIndexInBlock verifies that the transaction at the given index in the
35
// block matches the expected txid.
36
func assertTxIndexInBlock(t *testing.T, miner *rpctest.Harness,
37
        blockHash *chainhash.Hash, txIndex uint32,
38
        expectedTxid *chainhash.Hash) {
88✔
39

88✔
40
        t.Helper()
88✔
41

88✔
42
        msgBlock, err := miner.Client.GetBlock(blockHash)
88✔
43
        require.NoError(t, err, "unable to fetch block")
88✔
44

88✔
45
        block := btcutil.NewBlock(msgBlock)
88✔
46
        specifiedTxHash, err := block.TxHash(int(txIndex))
88✔
47
        require.NoError(t, err, "unable to index into block")
88✔
48

88✔
49
        require.True(t, specifiedTxHash.IsEqual(expectedTxid))
88✔
50
}
88✔
51

52
func testSingleConfirmationNotification(miner *rpctest.Harness,
53
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
54

8✔
55
        // We'd like to test the case of being notified once a txid reaches
8✔
56
        // a *single* confirmation.
8✔
57
        //
8✔
58
        // So first, let's send some coins to "ourself", obtaining a txid.
8✔
59
        // We're spending from a coinbase output here, so we use the dedicated
8✔
60
        // function.
8✔
61
        txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
62
        require.NoError(t, err, "unable to create test tx")
8✔
63
        err = chainntnfs.WaitForMempoolTx(miner, txid)
8✔
64
        require.NoError(t, err, "tx not relayed to miner")
8✔
65

8✔
66
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
67
        require.NoError(t, err, "unable to get current height")
8✔
68

8✔
69
        // Now that we have a txid, register a confirmation notification with
8✔
70
        // the chainntfn source.
8✔
71
        numConfs := uint32(1)
8✔
72
        var confIntent *chainntnfs.ConfirmationEvent
8✔
73
        if scriptDispatch {
12✔
74
                confIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
75
                        nil, pkScript, numConfs, uint32(currentHeight),
4✔
76
                )
4✔
77
        } else {
8✔
78
                confIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
79
                        txid, pkScript, numConfs, uint32(currentHeight),
4✔
80
                )
4✔
81
        }
4✔
82
        require.NoError(t, err, "unable to register ntfn")
8✔
83

8✔
84
        // Now generate a single block, the transaction should be included which
8✔
85
        // should trigger a notification event.
8✔
86
        blockHash, err := miner.Client.Generate(1)
8✔
87
        require.NoError(t, err, "unable to generate single block")
8✔
88

8✔
89
        // Assert the above tx is mined in the block.
8✔
90
        block, err := miner.Client.GetBlock(blockHash[0])
8✔
91
        require.NoError(t, err)
8✔
92
        require.Len(t, block.Transactions, 2, "block does not contain tx")
8✔
93

8✔
94
        select {
8✔
95
        case confInfo := <-confIntent.Confirmed:
8✔
96
                if !confInfo.BlockHash.IsEqual(blockHash[0]) {
8✔
97
                        t.Fatalf("mismatched block hashes: expected %v, got %v",
×
98
                                blockHash[0], confInfo.BlockHash)
×
99
                }
×
100

101
                // Finally, we'll verify that the tx index returned is the exact
102
                // same as the tx index of the transaction within the block
103
                // itself.
104
                assertTxIndexInBlock(
8✔
105
                        t, miner, blockHash[0], confInfo.TxIndex, txid,
8✔
106
                )
8✔
107
                require.Zero(t, confInfo.NumConfsLeft)
8✔
108

UNCOV
109
        case <-time.After(20 * time.Second):
×
110
                t.Fatalf("confirmation notification never received")
×
111
        }
112
}
113

114
func testMultiConfirmationNotification(miner *rpctest.Harness,
115
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
116

8✔
117
        // We'd like to test the case of being notified once a txid reaches
8✔
118
        // N confirmations, where N > 1.
8✔
119
        //
8✔
120
        // Again, we'll begin by creating a fresh transaction, so we can obtain
8✔
121
        // a fresh txid.
8✔
122
        txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
123
        require.NoError(t, err, "unable to create test addr")
8✔
124
        if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
8✔
125
                t.Fatalf("tx not relayed to miner: %v", err)
×
126
        }
×
127

128
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
129
        require.NoError(t, err, "unable to get current height")
8✔
130

8✔
131
        numConfs := uint32(6)
8✔
132
        var confIntent *chainntnfs.ConfirmationEvent
8✔
133
        expectedBlockHeight := currentHeight + 1
8✔
134

8✔
135
        // We wish to receive all confirmations for the target transaction.
8✔
136
        if scriptDispatch {
12✔
137
                confIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
138
                        nil, pkScript, numConfs, uint32(currentHeight),
4✔
139
                        chainntnfs.WithIntermediateConfirmations(),
4✔
140
                )
4✔
141
        } else {
8✔
142
                confIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
143
                        txid, pkScript, numConfs, uint32(currentHeight),
4✔
144
                        chainntnfs.WithIntermediateConfirmations(),
4✔
145
                )
4✔
146
        }
4✔
147
        require.NoError(t, err, "unable to register ntfn")
8✔
148

8✔
149
        // Now generate a six blocks. The transaction should be included in the
8✔
150
        // first block, which will be built upon by the other 5 blocks.
8✔
151
        blockHashes, err := miner.Client.Generate(6)
8✔
152
        require.NoError(t, err)
8✔
153

8✔
154
        // TODO(roasbeef): reduce all timeouts after neutrino sync tightended
8✔
155
        // up
8✔
156

8✔
157
        // Since we have registered for all confirmation events, we should
8✔
158
        // receive a confirmation notification for every confirmation of the
8✔
159
        // targeted transaction.
8✔
160
        for i := uint32(1); i <= numConfs; i++ {
56✔
161
                select {
48✔
162
                case txConf := <-confIntent.Confirmed:
48✔
163
                        // we'll verify that the tx index returned is the exact
48✔
164
                        // same as the tx index of the transaction within the
48✔
165
                        // block itself.
48✔
166
                        assertTxIndexInBlock(
48✔
167
                                t, miner, blockHashes[0], txConf.TxIndex, txid,
48✔
168
                        )
48✔
169

48✔
170
                        // We'll also ensure that the block height has been set
48✔
171
                        // properly.
48✔
172
                        require.EqualValues(
48✔
173
                                t, txConf.BlockHeight, expectedBlockHeight,
48✔
174
                        )
48✔
175
                        require.Equal(t, numConfs-i, txConf.NumConfsLeft)
48✔
176

NEW
177
                case <-time.After(wait.DefaultTimeout):
×
NEW
178
                        t.Fatalf("confirmation notification never received")
×
179
                }
180
        }
181
}
182

183
func testBatchConfirmationNotification(miner *rpctest.Harness,
184
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
185

8✔
186
        // We'd like to test a case of serving notifications to multiple
8✔
187
        // clients, each requesting to be notified once a txid receives
8✔
188
        // various numbers of confirmations.
8✔
189
        confSpread := [6]uint32{1, 2, 3, 6, 20, 22}
8✔
190
        confIntents := make([]*chainntnfs.ConfirmationEvent, len(confSpread))
8✔
191

8✔
192
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
193
        require.NoError(t, err, "unable to get current height")
8✔
194

8✔
195
        // Create a new txid spending miner coins for each confirmation entry
8✔
196
        // in confSpread, we collect each conf intent into a slice so we can
8✔
197
        // verify they're each notified at the proper number of confirmations
8✔
198
        // below.
8✔
199
        for i, numConfs := range confSpread {
56✔
200
                // All the clients with an even index will ask for the block
48✔
201
                // along side the conf ntfn.
48✔
202
                var opts []chainntnfs.NotifierOption
48✔
203
                if i%2 == 0 {
72✔
204
                        opts = append(opts, chainntnfs.WithIncludeBlock())
24✔
205
                }
24✔
206

207
                txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
48✔
208
                if err != nil {
48✔
209
                        t.Fatalf("unable to create test addr: %v", err)
×
210
                }
×
211
                var confIntent *chainntnfs.ConfirmationEvent
48✔
212
                if scriptDispatch {
72✔
213
                        confIntent, err = notifier.RegisterConfirmationsNtfn(
24✔
214
                                nil, pkScript, numConfs, uint32(currentHeight),
24✔
215
                                opts...,
24✔
216
                        )
24✔
217
                } else {
48✔
218
                        confIntent, err = notifier.RegisterConfirmationsNtfn(
24✔
219
                                txid, pkScript, numConfs, uint32(currentHeight),
24✔
220
                                opts...,
24✔
221
                        )
24✔
222
                }
24✔
223
                if err != nil {
48✔
224
                        t.Fatalf("unable to register ntfn: %v", err)
×
225
                }
×
226
                confIntents[i] = confIntent
48✔
227
                if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
48✔
228
                        t.Fatalf("tx not relayed to miner: %v", err)
×
229
                }
×
230

231
        }
232

233
        initialConfHeight := uint32(currentHeight + 1)
8✔
234

8✔
235
        // Now, for each confirmation intent, generate the delta number of blocks
8✔
236
        // needed to trigger the confirmation notification. A goroutine is
8✔
237
        // spawned in order to verify the proper notification is triggered.
8✔
238
        for i, numConfs := range confSpread {
56✔
239
                var blocksToGen uint32
48✔
240

48✔
241
                // If this is the last instance, manually index to generate the
48✔
242
                // proper block delta in order to avoid a panic.
48✔
243
                if i == len(confSpread)-1 {
56✔
244
                        blocksToGen = confSpread[len(confSpread)-1] - confSpread[len(confSpread)-2]
8✔
245
                } else {
48✔
246
                        blocksToGen = confSpread[i+1] - confSpread[i]
40✔
247
                }
40✔
248

249
                // Generate the number of blocks necessary to trigger this
250
                // current confirmation notification.
251
                if _, err := miner.Client.Generate(blocksToGen); err != nil {
48✔
252
                        t.Fatalf("unable to generate single block: %v", err)
×
253
                }
×
254

255
                select {
48✔
256
                case conf := <-confIntents[i].Confirmed:
48✔
257
                        // All of the notifications above were originally
48✔
258
                        // confirmed in the same block. The returned
48✔
259
                        // notification should list the initial confirmation
48✔
260
                        // height rather than the height they were _fully_
48✔
261
                        // confirmed.
48✔
262
                        if conf.BlockHeight != initialConfHeight {
48✔
263
                                t.Fatalf("notification has incorrect initial "+
×
264
                                        "conf height: expected %v, got %v",
×
265
                                        initialConfHeight, conf.BlockHeight)
×
266
                        }
×
267

268
                        // If this is an even client index, then we expect the
269
                        // block to be populated. Otherwise, it should be
270
                        // empty.
271
                        expectBlock := i%2 == 0
48✔
272
                        require.Equal(t, expectBlock, conf.Block != nil)
48✔
273
                        require.Zero(t, conf.NumConfsLeft)
48✔
274

275
                case <-time.After(20 * time.Second):
×
276
                        t.Fatalf("confirmation notification never received: %v", numConfs)
×
277
                }
278
        }
279
}
280

281
func checkNotificationFields(ntfn *chainntnfs.SpendDetail,
282
        outpoint *wire.OutPoint, spenderSha *chainhash.Hash,
283
        height int32, t *testing.T) {
96✔
284

96✔
285
        t.Helper()
96✔
286

96✔
287
        if *ntfn.SpentOutPoint != *outpoint {
96✔
288
                t.Fatalf("ntfn includes wrong output, reports "+
×
289
                        "%v instead of %v",
×
290
                        ntfn.SpentOutPoint, outpoint)
×
291
        }
×
292
        if !bytes.Equal(ntfn.SpenderTxHash[:], spenderSha[:]) {
96✔
293
                t.Fatalf("ntfn includes wrong spender tx sha, "+
×
294
                        "reports %v instead of %v",
×
295
                        ntfn.SpenderTxHash[:], spenderSha[:])
×
296
        }
×
297
        if ntfn.SpenderInputIndex != 0 {
96✔
298
                t.Fatalf("ntfn includes wrong spending input "+
×
299
                        "index, reports %v, should be %v",
×
300
                        ntfn.SpenderInputIndex, 0)
×
301
        }
×
302
        if ntfn.SpendingHeight != height {
96✔
303
                t.Fatalf("ntfn has wrong spending height: "+
×
304
                        "expected %v, got %v", height,
×
305
                        ntfn.SpendingHeight)
×
306
        }
×
307
}
308

309
func testSpendNotification(miner *rpctest.Harness,
310
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
311

8✔
312
        // We'd like to test the spend notifications for all ChainNotifier
8✔
313
        // concrete implementations.
8✔
314
        //
8✔
315
        // To do so, we first create a new output to our test target address.
8✔
316
        outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, miner)
8✔
317

8✔
318
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
319
        require.NoError(t, err, "unable to get current height")
8✔
320

8✔
321
        // Now that we have an output index and the pkScript, register for a
8✔
322
        // spentness notification for the newly created output with multiple
8✔
323
        // clients in order to ensure the implementation can support
8✔
324
        // multi-client spend notifications.
8✔
325
        const numClients = 5
8✔
326
        spendClients := make([]*chainntnfs.SpendEvent, numClients)
8✔
327
        for i := 0; i < numClients; i++ {
48✔
328
                var spentIntent *chainntnfs.SpendEvent
40✔
329
                if scriptDispatch {
60✔
330
                        spentIntent, err = notifier.RegisterSpendNtfn(
20✔
331
                                nil, output.PkScript, uint32(currentHeight),
20✔
332
                        )
20✔
333
                } else {
40✔
334
                        spentIntent, err = notifier.RegisterSpendNtfn(
20✔
335
                                outpoint, output.PkScript, uint32(currentHeight),
20✔
336
                        )
20✔
337
                }
20✔
338
                if err != nil {
40✔
339
                        t.Fatalf("unable to register for spend ntfn: %v", err)
×
340
                }
×
341

342
                spendClients[i] = spentIntent
40✔
343
        }
344

345
        // Next, create a new transaction spending that output.
346
        spendingTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
8✔
347

8✔
348
        // Broadcast our spending transaction.
8✔
349
        spenderSha, err := miner.Client.SendRawTransaction(spendingTx, true)
8✔
350
        require.NoError(t, err, "unable to broadcast tx")
8✔
351

8✔
352
        if err := chainntnfs.WaitForMempoolTx(miner, spenderSha); err != nil {
8✔
353
                t.Fatalf("tx not relayed to miner: %v", err)
×
354
        }
×
355

356
        // Make sure notifications are not yet sent. We launch a go routine for
357
        // all the spend clients, such that we can wait for them all in
358
        // parallel.
359
        mempoolSpendTimeout := 2 * chainntnfs.TrickleInterval
8✔
360
        mempoolSpends := make(chan *chainntnfs.SpendDetail, numClients)
8✔
361
        for _, c := range spendClients {
48✔
362
                go func(client *chainntnfs.SpendEvent) {
80✔
363
                        select {
40✔
364
                        case s := <-client.Spend:
×
365
                                mempoolSpends <- s
×
366
                        case <-time.After(mempoolSpendTimeout):
40✔
367
                        }
368
                }(c)
369
        }
370

371
        select {
8✔
372
        case <-mempoolSpends:
×
373
                t.Fatalf("did not expect to get notification before " +
×
374
                        "block was mined")
×
375
        case <-time.After(mempoolSpendTimeout):
8✔
376
        }
377

378
        // Make sure registering a client after the tx is in the mempool still
379
        // doesn't trigger a notification.
380
        var spentIntent *chainntnfs.SpendEvent
8✔
381
        if scriptDispatch {
12✔
382
                spentIntent, err = notifier.RegisterSpendNtfn(
4✔
383
                        nil, output.PkScript, uint32(currentHeight),
4✔
384
                )
4✔
385
        } else {
8✔
386
                spentIntent, err = notifier.RegisterSpendNtfn(
4✔
387
                        outpoint, output.PkScript, uint32(currentHeight),
4✔
388
                )
4✔
389
        }
4✔
390
        require.NoError(t, err, "unable to register for spend ntfn")
8✔
391

8✔
392
        select {
8✔
393
        case <-spentIntent.Spend:
×
394
                t.Fatalf("did not expect to get notification before " +
×
395
                        "block was mined")
×
396
        case <-time.After(mempoolSpendTimeout):
8✔
397
        }
398
        spendClients = append(spendClients, spentIntent)
8✔
399

8✔
400
        // Now we mine a single block, which should include our spend. The
8✔
401
        // notification should also be sent off.
8✔
402
        if _, err := miner.Client.Generate(1); err != nil {
8✔
403
                t.Fatalf("unable to generate single block: %v", err)
×
404
        }
×
405

406
        _, currentHeight, err = miner.Client.GetBestBlock()
8✔
407
        require.NoError(t, err, "unable to get current height")
8✔
408

8✔
409
        for _, c := range spendClients {
56✔
410
                select {
48✔
411
                case ntfn := <-c.Spend:
48✔
412
                        // We've received the spend nftn. So now verify all the
48✔
413
                        // fields have been set properly.
48✔
414
                        checkNotificationFields(ntfn, outpoint, spenderSha,
48✔
415
                                currentHeight, t)
48✔
416
                case <-time.After(30 * time.Second):
×
417
                        t.Fatalf("spend ntfn never received")
×
418
                }
419
        }
420
}
421

422
func testBlockEpochNotification(miner *rpctest.Harness,
423
        notifier chainntnfs.TestChainNotifier, t *testing.T) {
4✔
424

4✔
425
        // We'd like to test the case of multiple registered clients receiving
4✔
426
        // block epoch notifications.
4✔
427
        const numBlocks = 10
4✔
428
        const numNtfns = numBlocks + 1
4✔
429
        const numClients = 5
4✔
430
        var wg sync.WaitGroup
4✔
431

4✔
432
        // Create numClients clients which will listen for block notifications. We
4✔
433
        // expect each client to receive 11 notifications, one for the current
4✔
434
        // tip of the chain, and one for each of the ten blocks we generate
4✔
435
        // below. So we'll use a WaitGroup to synchronize the test.
4✔
436
        clientErrors := make(chan error, numClients)
4✔
437
        for i := 0; i < numClients; i++ {
24✔
438
                epochClient, err := notifier.RegisterBlockEpochNtfn(nil)
20✔
439
                if err != nil {
20✔
440
                        t.Fatalf("unable to register for epoch notification")
×
441
                }
×
442

443
                wg.Add(numNtfns)
20✔
444
                go func() {
40✔
445
                        for i := 0; i < numNtfns; i++ {
240✔
446
                                // Ensure that each block epoch has a header,
220✔
447
                                // and that header matches the contained header
220✔
448
                                // hash.
220✔
449
                                blockEpoch := <-epochClient.Epochs
220✔
450
                                if blockEpoch.BlockHeader == nil {
220✔
451
                                        t.Logf("%d", i)
×
452
                                        clientErrors <- fmt.Errorf("block " +
×
453
                                                "header is nil")
×
454
                                        return
×
455
                                }
×
456
                                if blockEpoch.BlockHeader.BlockHash() !=
220✔
457
                                        *blockEpoch.Hash {
220✔
458

×
459
                                        clientErrors <- fmt.Errorf("block " +
×
460
                                                "header hash mismatch")
×
461
                                        return
×
462
                                }
×
463

464
                                wg.Done()
220✔
465
                        }
466
                }()
467
        }
468

469
        epochsSent := make(chan struct{})
4✔
470
        go func() {
8✔
471
                wg.Wait()
4✔
472
                close(epochsSent)
4✔
473
        }()
4✔
474

475
        // Now generate 10 blocks, the clients above should each receive 10
476
        // notifications, thereby unblocking the goroutine above.
477
        if _, err := miner.Client.Generate(numBlocks); err != nil {
4✔
478
                t.Fatalf("unable to generate blocks: %v", err)
×
479
        }
×
480

481
        select {
4✔
482
        case err := <-clientErrors:
×
483
                t.Fatalf("block epoch case failed: %v", err)
×
484
        case <-epochsSent:
4✔
485
        case <-time.After(30 * time.Second):
×
486
                t.Fatalf("all notifications not sent")
×
487
        }
488
}
489

490
func testMultiClientConfirmationNotification(miner *rpctest.Harness,
491
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
492

8✔
493
        // We'd like to test the case of a multiple clients registered to
8✔
494
        // receive a confirmation notification for the same transaction.
8✔
495
        txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
496
        require.NoError(t, err, "unable to create test tx")
8✔
497
        if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
8✔
498
                t.Fatalf("tx not relayed to miner: %v", err)
×
499
        }
×
500

501
        var wg sync.WaitGroup
8✔
502
        const (
8✔
503
                numConfsClients = 5
8✔
504
                numConfs        = 1
8✔
505
        )
8✔
506

8✔
507
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
508
        require.NoError(t, err, "unable to get current height")
8✔
509

8✔
510
        // Register for a conf notification for the above generated txid with
8✔
511
        // numConfsClients distinct clients.
8✔
512
        for i := 0; i < numConfsClients; i++ {
48✔
513
                var confClient *chainntnfs.ConfirmationEvent
40✔
514
                if scriptDispatch {
60✔
515
                        confClient, err = notifier.RegisterConfirmationsNtfn(
20✔
516
                                nil, pkScript, numConfs, uint32(currentHeight),
20✔
517
                        )
20✔
518
                } else {
40✔
519
                        confClient, err = notifier.RegisterConfirmationsNtfn(
20✔
520
                                txid, pkScript, numConfs, uint32(currentHeight),
20✔
521
                        )
20✔
522
                }
20✔
523
                if err != nil {
40✔
524
                        t.Fatalf("unable to register for confirmation: %v", err)
×
525
                }
×
526

527
                wg.Add(1)
40✔
528
                go func() {
80✔
529
                        <-confClient.Confirmed
40✔
530
                        wg.Done()
40✔
531
                }()
40✔
532
        }
533

534
        confsSent := make(chan struct{})
8✔
535
        go func() {
16✔
536
                wg.Wait()
8✔
537
                close(confsSent)
8✔
538
        }()
8✔
539

540
        // Finally, generate a single block which should trigger the unblocking
541
        // of all numConfsClients blocked on the channel read above.
542
        if _, err := miner.Client.Generate(1); err != nil {
8✔
543
                t.Fatalf("unable to generate block: %v", err)
×
544
        }
×
545

546
        select {
8✔
547
        case <-confsSent:
8✔
548
        case <-time.After(30 * time.Second):
×
549
                t.Fatalf("all confirmation notifications not sent")
×
550
        }
551
}
552

553
// Tests the case in which a confirmation notification is requested for a
554
// transaction that has already been included in a block. In this case, the
555
// confirmation notification should be dispatched immediately.
556
func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness,
557
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
558

8✔
559
        // First, let's send some coins to "ourself", obtaining a txid.  We're
8✔
560
        // spending from a coinbase output here, so we use the dedicated
8✔
561
        // function.
8✔
562
        txid3, pkScript3, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
563
        require.NoError(t, err, "unable to create test tx")
8✔
564
        if err := chainntnfs.WaitForMempoolTx(miner, txid3); err != nil {
8✔
565
                t.Fatalf("tx not relayed to miner: %v", err)
×
566
        }
×
567

568
        // Generate another block containing tx 3, but we won't register conf
569
        // notifications for this tx until much later. The notifier must check
570
        // older blocks when the confirmation event is registered below to ensure
571
        // that the TXID hasn't already been included in the chain, otherwise the
572
        // notification will never be sent.
573
        _, err = miner.Client.Generate(1)
8✔
574
        require.NoError(t, err, "unable to generate block")
8✔
575

8✔
576
        txid1, pkScript1, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
577
        require.NoError(t, err, "unable to create test tx")
8✔
578
        if err := chainntnfs.WaitForMempoolTx(miner, txid1); err != nil {
8✔
579
                t.Fatalf("tx not relayed to miner: %v", err)
×
580
        }
×
581

582
        txid2, pkScript2, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
583
        require.NoError(t, err, "unable to create test tx")
8✔
584
        if err := chainntnfs.WaitForMempoolTx(miner, txid2); err != nil {
8✔
585
                t.Fatalf("tx not relayed to miner: %v", err)
×
586
        }
×
587

588
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
589
        require.NoError(t, err, "unable to get current height")
8✔
590

8✔
591
        // Now generate another block containing txs 1 & 2.
8✔
592
        blockHash, err := miner.Client.Generate(1)
8✔
593
        require.NoError(t, err, "unable to generate block")
8✔
594

8✔
595
        // Register a confirmation notification with the chainntfn source for tx2,
8✔
596
        // which is included in the last block. The height hint is the height before
8✔
597
        // the block is included. This notification should fire immediately since
8✔
598
        // only 1 confirmation is required.
8✔
599
        var ntfn1 *chainntnfs.ConfirmationEvent
8✔
600
        if scriptDispatch {
12✔
601
                ntfn1, err = notifier.RegisterConfirmationsNtfn(
4✔
602
                        nil, pkScript1, 1, uint32(currentHeight),
4✔
603
                        chainntnfs.WithIncludeBlock(),
4✔
604
                )
4✔
605
        } else {
8✔
606
                ntfn1, err = notifier.RegisterConfirmationsNtfn(
4✔
607
                        txid1, pkScript1, 1, uint32(currentHeight),
4✔
608
                )
4✔
609
        }
4✔
610
        require.NoError(t, err, "unable to register ntfn")
8✔
611

8✔
612
        expectedBlockHeight := currentHeight + 1
8✔
613

8✔
614
        select {
8✔
615
        case confInfo := <-ntfn1.Confirmed:
8✔
616
                // Finally, we'll verify that the tx index returned is the exact
8✔
617
                // same as the tx index of the transaction within the block
8✔
618
                // itself.
8✔
619
                assertTxIndexInBlock(
8✔
620
                        t, miner, blockHash[0], confInfo.TxIndex, txid1,
8✔
621
                )
8✔
622

8✔
623
                // We'll also ensure that the block height has been set
8✔
624
                // properly.
8✔
625
                require.EqualValues(
8✔
626
                        t, confInfo.BlockHeight, expectedBlockHeight,
8✔
627
                )
8✔
628

8✔
629
                // Ensure that if this was a script dispatch, the block is set
8✔
630
                // as well.
8✔
631
                if scriptDispatch {
12✔
632
                        require.NotNil(t, confInfo.Block)
4✔
633
                }
4✔
634

635
                require.Zero(t, confInfo.NumConfsLeft)
8✔
636
                break
8✔
637

638
        case <-time.After(20 * time.Second):
×
639
                t.Fatalf("confirmation notification never received")
×
640
        }
641

642
        // Register a confirmation notification for tx2, requiring 3 confirmations.
643
        // This transaction is only partially confirmed, so the notification should
644
        // not fire yet.
645
        var ntfn2 *chainntnfs.ConfirmationEvent
8✔
646

8✔
647
        // We wish to receive all confirmations for tx2.
8✔
648
        if scriptDispatch {
12✔
649
                ntfn2, err = notifier.RegisterConfirmationsNtfn(
4✔
650
                        nil, pkScript2, 3, uint32(currentHeight),
4✔
651
                        chainntnfs.WithIncludeBlock(),
4✔
652
                        chainntnfs.WithIntermediateConfirmations(),
4✔
653
                )
4✔
654
        } else {
8✔
655
                ntfn2, err = notifier.RegisterConfirmationsNtfn(
4✔
656
                        txid2, pkScript2, 3, uint32(currentHeight),
4✔
657
                        chainntnfs.WithIntermediateConfirmations(),
4✔
658
                )
4✔
659
        }
4✔
660
        require.NoError(t, err, "unable to register ntfn")
8✔
661

8✔
662
        // Since we have registered for all confirmation events, we should
8✔
663
        // receive a confirmation notification for every confirmation of tx2.
8✔
664
        //
8✔
665
        // Verify that if blocks have already been mined before registering
8✔
666
        // for the notification, we receive all confirmation events starting
8✔
667
        // from the block where the transaction was first confirmed.
8✔
668
        select {
8✔
669
        case txConf := <-ntfn2.Confirmed:
8✔
670
                // we'll verify that the tx index returned is the exact
8✔
671
                // same as the tx index of the transaction within the
8✔
672
                // block itself.
8✔
673
                assertTxIndexInBlock(
8✔
674
                        t, miner, blockHash[0], txConf.TxIndex, txid2,
8✔
675
                )
8✔
676

8✔
677
                // We'll also ensure that the block height has been set
8✔
678
                // properly.
8✔
679
                require.EqualValues(
8✔
680
                        t, txConf.BlockHeight, expectedBlockHeight,
8✔
681
                )
8✔
682

8✔
683
                // Ensure that if this was a script dispatch, the block
8✔
684
                // is set as well.
8✔
685
                if scriptDispatch {
12✔
686
                        require.NotNil(t, txConf.Block)
4✔
687
                }
4✔
688

689
                require.EqualValues(t, 2, txConf.NumConfsLeft)
8✔
690

NEW
691
        case <-time.After(wait.DefaultTimeout):
×
NEW
692
                t.Fatalf("confirmation notification never received")
×
693
        }
694

695
        // Fully confirm tx3.
696
        _, err = miner.Client.Generate(2)
8✔
697
        require.NoError(t, err, "unable to generate block")
8✔
698

8✔
699
        // Verify the remaining intermediate confirmations and the final
8✔
700
        // confirmation for tx2.
8✔
701
        for i := uint32(2); i <= 3; i++ {
24✔
702
                select {
16✔
703
                case txConf := <-ntfn2.Confirmed:
16✔
704
                        // we'll verify that the tx index returned is the exact
16✔
705
                        // same as the tx index of the transaction within the
16✔
706
                        // block itself.
16✔
707
                        assertTxIndexInBlock(
16✔
708
                                t, miner, blockHash[0], txConf.TxIndex, txid2,
16✔
709
                        )
16✔
710

16✔
711
                        // We'll also ensure that the block height has been set
16✔
712
                        // properly.
16✔
713
                        require.EqualValues(
16✔
714
                                t, txConf.BlockHeight, expectedBlockHeight,
16✔
715
                        )
16✔
716

16✔
717
                        // Ensure that if this was a script dispatch, the block
16✔
718
                        // is set as well.
16✔
719
                        if scriptDispatch {
24✔
720
                                require.NotNil(t, txConf.Block)
8✔
721
                        }
8✔
722

723
                        require.Equal(t, 3-i, txConf.NumConfsLeft)
16✔
724

16✔
725
                        continue
16✔
726

NEW
727
                case <-time.After(wait.DefaultTimeout):
×
NEW
728
                        t.Fatalf("confirmation notification never received")
×
729
                }
730
        }
731

732
        select {
8✔
733
        case <-ntfn1.Confirmed:
×
734
                t.Fatalf("received multiple confirmations for tx")
×
735
        case <-time.After(1 * time.Second):
8✔
736
        }
737

738
        // Finally register a confirmation notification for tx3, requiring 1
739
        // confirmation. Ensure that conf notifications do not refire on txs
740
        // 1 or 2.
741
        var ntfn3 *chainntnfs.ConfirmationEvent
8✔
742
        if scriptDispatch {
12✔
743
                ntfn3, err = notifier.RegisterConfirmationsNtfn(
4✔
744
                        nil, pkScript3, 1, uint32(currentHeight-1),
4✔
745
                        chainntnfs.WithIncludeBlock(),
4✔
746
                )
4✔
747
        } else {
8✔
748
                ntfn3, err = notifier.RegisterConfirmationsNtfn(
4✔
749
                        txid3, pkScript3, 1, uint32(currentHeight-1),
4✔
750
                )
4✔
751
        }
4✔
752
        require.NoError(t, err, "unable to register ntfn")
8✔
753

8✔
754
        // We'll also register for a confirmation notification with the pkscript
8✔
755
        // of a different transaction. This notification shouldn't fire since we
8✔
756
        // match on both txid and pkscript.
8✔
757
        var ntfn4 *chainntnfs.ConfirmationEvent
8✔
758
        ntfn4, err = notifier.RegisterConfirmationsNtfn(
8✔
759
                txid3, pkScript2, 1, uint32(currentHeight-1),
8✔
760
        )
8✔
761
        require.NoError(t, err, "unable to register ntfn")
8✔
762

8✔
763
        select {
8✔
764
        case confInfo := <-ntfn3.Confirmed:
8✔
765
                if scriptDispatch {
12✔
766
                        require.NotNil(t, confInfo.Block)
4✔
767
                }
4✔
768
                require.Zero(t, confInfo.NumConfsLeft)
8✔
769

770
        case <-time.After(10 * time.Second):
×
771
                t.Fatalf("confirmation notification never received")
×
772
        }
773

774
        select {
8✔
775
        case <-ntfn4.Confirmed:
×
776
                t.Fatalf("confirmation notification received")
×
777
        case <-time.After(5 * time.Second):
8✔
778
        }
779

780
        time.Sleep(1 * time.Second)
8✔
781

8✔
782
        select {
8✔
783
        case <-ntfn1.Confirmed:
×
784
                t.Fatalf("received multiple confirmations for tx")
×
785
        default:
8✔
786
        }
787

788
        select {
8✔
789
        case <-ntfn2.Confirmed:
×
790
                t.Fatalf("received multiple confirmations for tx")
×
791
        default:
8✔
792
        }
793
}
794

795
// Test the case of a notification consumer having forget or being delayed in
796
// checking for a confirmation. This should not cause the notifier to stop
797
// working
798
func testLazyNtfnConsumer(miner *rpctest.Harness,
799
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
800

8✔
801
        // Create a transaction to be notified about. We'll register for
8✔
802
        // notifications on this transaction but won't be prompt in checking them
8✔
803
        txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
804
        require.NoError(t, err, "unable to create test tx")
8✔
805
        if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
8✔
806
                t.Fatalf("tx not relayed to miner: %v", err)
×
807
        }
×
808

809
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
810
        require.NoError(t, err, "unable to get current height")
8✔
811

8✔
812
        numConfs := uint32(3)
8✔
813

8✔
814
        // Add a block right before registering, this makes race conditions
8✔
815
        // between the historical dispatcher and the normal dispatcher more obvious
8✔
816
        if _, err := miner.Client.Generate(1); err != nil {
8✔
817
                t.Fatalf("unable to generate blocks: %v", err)
×
818
        }
×
819

820
        var firstConfIntent *chainntnfs.ConfirmationEvent
8✔
821

8✔
822
        // We wish to receive all confirmations.
8✔
823
        if scriptDispatch {
12✔
824
                firstConfIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
825
                        nil, pkScript, numConfs, uint32(currentHeight),
4✔
826
                        chainntnfs.WithIntermediateConfirmations(),
4✔
827
                )
4✔
828
        } else {
8✔
829
                firstConfIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
830
                        txid, pkScript, numConfs, uint32(currentHeight),
4✔
831
                        chainntnfs.WithIntermediateConfirmations(),
4✔
832
                )
4✔
833
        }
4✔
834
        require.NoError(t, err, "unable to register ntfn")
8✔
835

8✔
836
        // Generate another 2 blocks, this should dispatch the confirm
8✔
837
        // notification twice for firstConfIntent since we're registered to
8✔
838
        // receive all confirmations.
8✔
839
        if _, err := miner.Client.Generate(2); err != nil {
8✔
840
                t.Fatalf("unable to generate blocks: %v", err)
×
841
        }
×
842

843
        // Now make another transaction, just because we haven't checked to see
844
        // if the first transaction has confirmed doesn't mean that we shouldn't
845
        // be able to see if this transaction confirms first
846
        txid, pkScript, err = chainntnfs.GetTestTxidAndScript(miner)
8✔
847
        require.NoError(t, err, "unable to create test tx")
8✔
848
        if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
8✔
849
                t.Fatalf("tx not relayed to miner: %v", err)
×
850
        }
×
851

852
        _, currentHeight, err = miner.Client.GetBestBlock()
8✔
853
        require.NoError(t, err, "unable to get current height")
8✔
854

8✔
855
        numConfs = 1
8✔
856
        var secondConfIntent *chainntnfs.ConfirmationEvent
8✔
857
        if scriptDispatch {
12✔
858
                secondConfIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
859
                        nil, pkScript, numConfs, uint32(currentHeight),
4✔
860
                )
4✔
861
        } else {
8✔
862
                secondConfIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
863
                        txid, pkScript, numConfs, uint32(currentHeight),
4✔
864
                )
4✔
865
        }
4✔
866
        require.NoError(t, err, "unable to register ntfn")
8✔
867

8✔
868
        if _, err := miner.Client.Generate(1); err != nil {
8✔
869
                t.Fatalf("unable to generate blocks: %v", err)
×
870
        }
×
871

872
        select {
8✔
873
        case txConf := <-secondConfIntent.Confirmed:
8✔
874
                // Successfully receive the second notification.
8✔
875
                require.Zero(t, txConf.NumConfsLeft)
8✔
876
                break
8✔
877

878
        case <-time.After(30 * time.Second):
×
879
                t.Fatalf("Second confirmation notification never received")
×
880
        }
881

882
        // Make sure the first tx confirmed successfully
883
        // Since we have registered for all confirmation events in
884
        // firstConfIntent, we should receive a confirmation notification for
885
        // every confirmation.
886
        for i := uint32(1); i <= 3; i++ {
32✔
887
                select {
24✔
888
                case txConf := <-firstConfIntent.Confirmed:
24✔
889
                        require.EqualValues(t, 3-i, txConf.NumConfsLeft)
24✔
890

NEW
891
                case <-time.After(wait.DefaultTimeout):
×
NEW
892
                        t.Fatalf("First confirmation notification never " +
×
NEW
893
                                "received")
×
894
                }
895
        }
896
}
897

898
// Tests the case in which a spend notification is requested for a spend that
899
// has already been included in a block. In this case, the spend notification
900
// should be dispatched immediately.
901
func testSpendBeforeNtfnRegistration(miner *rpctest.Harness,
902
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
903

8✔
904
        // We'd like to test the spend notifications for all ChainNotifier
8✔
905
        // concrete implementations.
8✔
906
        //
8✔
907
        // To do so, we first create a new output to our test target address.
8✔
908
        outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, miner)
8✔
909

8✔
910
        _, heightHint, err := miner.Client.GetBestBlock()
8✔
911
        require.NoError(t, err, "unable to get current height")
8✔
912

8✔
913
        // We'll then spend this output and broadcast the spend transaction.
8✔
914
        spendingTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
8✔
915
        spenderSha, err := miner.Client.SendRawTransaction(spendingTx, true)
8✔
916
        require.NoError(t, err, "unable to broadcast tx")
8✔
917
        if err := chainntnfs.WaitForMempoolTx(miner, spenderSha); err != nil {
8✔
918
                t.Fatalf("tx not relayed to miner: %v", err)
×
919
        }
×
920

921
        // We create an epoch client we can use to make sure the notifier is
922
        // caught up to the mining node's chain.
923
        epochClient, err := notifier.RegisterBlockEpochNtfn(nil)
8✔
924
        require.NoError(t, err, "unable to register for block epoch")
8✔
925

8✔
926
        // Now we mine an additional block, which should include our spend.
8✔
927
        if _, err := miner.Client.Generate(1); err != nil {
8✔
928
                t.Fatalf("unable to generate single block: %v", err)
×
929
        }
×
930
        _, spendHeight, err := miner.Client.GetBestBlock()
8✔
931
        require.NoError(t, err, "unable to get current height")
8✔
932

8✔
933
        // checkSpends registers two clients to be notified of a spend that has
8✔
934
        // already happened. The notifier should dispatch a spend notification
8✔
935
        // immediately.
8✔
936
        checkSpends := func() {
24✔
937
                t.Helper()
16✔
938

16✔
939
                const numClients = 2
16✔
940
                spendClients := make([]*chainntnfs.SpendEvent, numClients)
16✔
941
                for i := 0; i < numClients; i++ {
48✔
942
                        var spentIntent *chainntnfs.SpendEvent
32✔
943
                        if scriptDispatch {
48✔
944
                                spentIntent, err = notifier.RegisterSpendNtfn(
16✔
945
                                        nil, output.PkScript, uint32(heightHint),
16✔
946
                                )
16✔
947
                        } else {
32✔
948
                                spentIntent, err = notifier.RegisterSpendNtfn(
16✔
949
                                        outpoint, output.PkScript,
16✔
950
                                        uint32(heightHint),
16✔
951
                                )
16✔
952
                        }
16✔
953
                        if err != nil {
32✔
954
                                t.Fatalf("unable to register for spend ntfn: %v",
×
955
                                        err)
×
956
                        }
×
957

958
                        spendClients[i] = spentIntent
32✔
959
                }
960

961
                for _, client := range spendClients {
48✔
962
                        select {
32✔
963
                        case ntfn := <-client.Spend:
32✔
964
                                // We've received the spend nftn. So now verify
32✔
965
                                // all the fields have been set properly.
32✔
966
                                checkNotificationFields(
32✔
967
                                        ntfn, outpoint, spenderSha, spendHeight, t,
32✔
968
                                )
32✔
969
                        case <-time.After(30 * time.Second):
×
970
                                t.Fatalf("spend ntfn never received")
×
971
                        }
972
                }
973
        }
974

975
        // Wait for the notifier to have caught up to the mined block.
976
        select {
8✔
977
        case _, ok := <-epochClient.Epochs:
8✔
978
                if !ok {
8✔
979
                        t.Fatalf("epoch channel was closed")
×
980
                }
×
981
        case <-time.After(15 * time.Second):
×
982
                t.Fatalf("did not receive block epoch")
×
983
        }
984

985
        // Check that the spend clients gets immediately notified for the spend
986
        // in the previous block.
987
        checkSpends()
8✔
988

8✔
989
        // Bury the spend even deeper, and do the same check.
8✔
990
        const numBlocks = 10
8✔
991
        if _, err := miner.Client.Generate(numBlocks); err != nil {
8✔
992
                t.Fatalf("unable to generate single block: %v", err)
×
993
        }
×
994

995
        // Wait for the notifier to have caught up with the new blocks.
996
        for i := 0; i < numBlocks; i++ {
88✔
997
                select {
80✔
998
                case _, ok := <-epochClient.Epochs:
80✔
999
                        if !ok {
80✔
1000
                                t.Fatalf("epoch channel was closed")
×
1001
                        }
×
1002
                case <-time.After(15 * time.Second):
×
1003
                        t.Fatalf("did not receive block epoch")
×
1004
                }
1005
        }
1006

1007
        // The clients should still be notified immediately.
1008
        checkSpends()
8✔
1009
}
1010

1011
func testCancelSpendNtfn(node *rpctest.Harness,
1012
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
1013

8✔
1014
        // We'd like to test that once a spend notification is registered, it
8✔
1015
        // can be canceled before the notification is dispatched.
8✔
1016

8✔
1017
        // First, we'll start by creating a new output that we can spend
8✔
1018
        // ourselves.
8✔
1019
        outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, node)
8✔
1020

8✔
1021
        _, currentHeight, err := node.Client.GetBestBlock()
8✔
1022
        require.NoError(t, err, "unable to get current height")
8✔
1023

8✔
1024
        // Create two clients that each registered to the spend notification.
8✔
1025
        // We'll cancel the notification for the first client and leave the
8✔
1026
        // notification for the second client enabled.
8✔
1027
        const numClients = 2
8✔
1028
        spendClients := make([]*chainntnfs.SpendEvent, numClients)
8✔
1029
        for i := 0; i < numClients; i++ {
24✔
1030
                var spentIntent *chainntnfs.SpendEvent
16✔
1031
                if scriptDispatch {
24✔
1032
                        spentIntent, err = notifier.RegisterSpendNtfn(
8✔
1033
                                nil, output.PkScript, uint32(currentHeight),
8✔
1034
                        )
8✔
1035
                } else {
16✔
1036
                        spentIntent, err = notifier.RegisterSpendNtfn(
8✔
1037
                                outpoint, output.PkScript, uint32(currentHeight),
8✔
1038
                        )
8✔
1039
                }
8✔
1040
                if err != nil {
16✔
1041
                        t.Fatalf("unable to register for spend ntfn: %v", err)
×
1042
                }
×
1043

1044
                spendClients[i] = spentIntent
16✔
1045
        }
1046

1047
        // Next, create a new transaction spending that output.
1048
        spendingTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
8✔
1049

8✔
1050
        // Before we broadcast the spending transaction, we'll cancel the
8✔
1051
        // notification of the first client.
8✔
1052
        spendClients[1].Cancel()
8✔
1053

8✔
1054
        // Broadcast our spending transaction.
8✔
1055
        spenderSha, err := node.Client.SendRawTransaction(spendingTx, true)
8✔
1056
        require.NoError(t, err, "unable to broadcast tx")
8✔
1057

8✔
1058
        if err := chainntnfs.WaitForMempoolTx(node, spenderSha); err != nil {
8✔
1059
                t.Fatalf("tx not relayed to miner: %v", err)
×
1060
        }
×
1061

1062
        // Now we mine a single block, which should include our spend. The
1063
        // notification should also be sent off.
1064
        if _, err := node.Client.Generate(1); err != nil {
8✔
1065
                t.Fatalf("unable to generate single block: %v", err)
×
1066
        }
×
1067

1068
        // The spend notification for the first client should have been
1069
        // dispatched.
1070
        select {
8✔
1071
        case ntfn := <-spendClients[0].Spend:
8✔
1072
                // We've received the spend nftn. So now verify all the
8✔
1073
                // fields have been set properly.
8✔
1074
                if *ntfn.SpentOutPoint != *outpoint {
8✔
1075
                        t.Fatalf("ntfn includes wrong output, reports "+
×
1076
                                "%v instead of %v",
×
1077
                                ntfn.SpentOutPoint, outpoint)
×
1078
                }
×
1079
                if !bytes.Equal(ntfn.SpenderTxHash[:], spenderSha[:]) {
8✔
1080
                        t.Fatalf("ntfn includes wrong spender tx sha, "+
×
1081
                                "reports %v instead of %v",
×
1082
                                ntfn.SpenderTxHash[:], spenderSha[:])
×
1083
                }
×
1084
                if ntfn.SpenderInputIndex != 0 {
8✔
1085
                        t.Fatalf("ntfn includes wrong spending input "+
×
1086
                                "index, reports %v, should be %v",
×
1087
                                ntfn.SpenderInputIndex, 0)
×
1088
                }
×
1089
        case <-time.After(20 * time.Second):
×
1090
                t.Fatalf("spend ntfn never received")
×
1091
        }
1092

1093
        // However, the spend notification of the second client should NOT have
1094
        // been dispatched.
1095
        select {
8✔
1096
        case _, ok := <-spendClients[1].Spend:
8✔
1097
                if ok {
8✔
1098
                        t.Fatalf("spend ntfn should have been canceled")
×
1099
                }
×
1100
        case <-time.After(20 * time.Second):
×
1101
                t.Fatalf("spend ntfn never canceled")
×
1102
        }
1103
}
1104

1105
func testCancelEpochNtfn(node *rpctest.Harness,
1106
        notifier chainntnfs.TestChainNotifier, t *testing.T) {
4✔
1107

4✔
1108
        // We'd like to ensure that once a client cancels their block epoch
4✔
1109
        // notifications, no further notifications are sent over the channel
4✔
1110
        // if/when new blocks come in.
4✔
1111
        const numClients = 2
4✔
1112

4✔
1113
        epochClients := make([]*chainntnfs.BlockEpochEvent, numClients)
4✔
1114
        for i := 0; i < numClients; i++ {
12✔
1115
                epochClient, err := notifier.RegisterBlockEpochNtfn(nil)
8✔
1116
                if err != nil {
8✔
1117
                        t.Fatalf("unable to register for epoch notification")
×
1118
                }
×
1119
                epochClients[i] = epochClient
8✔
1120
        }
1121

1122
        // Now before we mine any blocks, cancel the notification for the first
1123
        // epoch client.
1124
        epochClients[0].Cancel()
4✔
1125

4✔
1126
        // Now mine a single block, this should trigger the logic to dispatch
4✔
1127
        // epoch notifications.
4✔
1128
        if _, err := node.Client.Generate(1); err != nil {
4✔
1129
                t.Fatalf("unable to generate blocks: %v", err)
×
1130
        }
×
1131

1132
        // The epoch notification for the first client shouldn't have been
1133
        // dispatched.
1134
        select {
4✔
1135
        case _, ok := <-epochClients[0].Epochs:
4✔
1136
                if ok {
4✔
1137
                        t.Fatalf("epoch notification should have been canceled")
×
1138
                }
×
1139
        case <-time.After(2 * time.Second):
×
1140
                t.Fatalf("epoch notification not sent")
×
1141
        }
1142

1143
        // However, the epoch notification for the second client should have
1144
        // been dispatched as normal.
1145
        select {
4✔
1146
        case _, ok := <-epochClients[1].Epochs:
4✔
1147
                if !ok {
4✔
1148
                        t.Fatalf("epoch was canceled")
×
1149
                }
×
1150
        case <-time.After(20 * time.Second):
×
1151
                t.Fatalf("epoch notification not sent")
×
1152
        }
1153
}
1154

1155
func testReorgConf(miner *rpctest.Harness,
1156
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
1157

8✔
1158
        // Set up a new miner that we can use to cause a reorg.
8✔
1159
        miner2 := unittest.NewMiner(
8✔
1160
                t, unittest.NetParams, []string{"--txindex"}, false, 0,
8✔
1161
        )
8✔
1162

8✔
1163
        // We start by connecting the new miner to our original miner,
8✔
1164
        // such that it will sync to our original chain.
8✔
1165
        if err := rpctest.ConnectNode(miner, miner2); err != nil {
8✔
1166
                t.Fatalf("unable to connect harnesses: %v", err)
×
1167
        }
×
1168
        nodeSlice := []*rpctest.Harness{miner, miner2}
8✔
1169
        if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil {
8✔
1170
                t.Fatalf("unable to join node on blocks: %v", err)
×
1171
        }
×
1172

1173
        // The two should be on the same blockheight.
1174
        _, nodeHeight1, err := miner.Client.GetBestBlock()
8✔
1175
        if err != nil {
8✔
1176
                t.Fatalf("unable to get current blockheight %v", err)
×
1177
        }
×
1178

1179
        _, nodeHeight2, err := miner2.Client.GetBestBlock()
8✔
1180
        if err != nil {
8✔
1181
                t.Fatalf("unable to get current blockheight %v", err)
×
1182
        }
×
1183

1184
        if nodeHeight1 != nodeHeight2 {
8✔
1185
                t.Fatalf("expected both miners to be on the same height: %v vs %v",
×
1186
                        nodeHeight1, nodeHeight2)
×
1187
        }
×
1188

1189
        // We disconnect the two nodes, such that we can start mining on them
1190
        // individually without the other one learning about the new blocks.
1191
        err = miner.Client.AddNode(miner2.P2PAddress(), rpcclient.ANRemove)
8✔
1192
        require.NoError(t, err, "unable to remove node")
8✔
1193

8✔
1194
        txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
1195
        require.NoError(t, err, "unable to create test tx")
8✔
1196
        if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
8✔
1197
                t.Fatalf("tx not relayed to miner: %v", err)
×
1198
        }
×
1199

1200
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
1201
        require.NoError(t, err, "unable to get current height")
8✔
1202

8✔
1203
        // Now that we have a txid, register a confirmation notification with
8✔
1204
        // the chainntfn source.
8✔
1205
        numConfs := uint32(2)
8✔
1206
        var confIntent *chainntnfs.ConfirmationEvent
8✔
1207
        if scriptDispatch {
12✔
1208
                confIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
1209
                        nil, pkScript, numConfs, uint32(currentHeight),
4✔
1210
                )
4✔
1211
        } else {
8✔
1212
                confIntent, err = notifier.RegisterConfirmationsNtfn(
4✔
1213
                        txid, pkScript, numConfs, uint32(currentHeight),
4✔
1214
                )
4✔
1215
        }
4✔
1216
        require.NoError(t, err, "unable to register ntfn")
8✔
1217

8✔
1218
        // Now generate a single block, the transaction should be included.
8✔
1219
        _, err = miner.Client.Generate(1)
8✔
1220
        require.NoError(t, err, "unable to generate single block")
8✔
1221

8✔
1222
        // Transaction only has one confirmation, and the notification is registered
8✔
1223
        // with 2 confirmations, so we should not be notified yet.
8✔
1224
        select {
8✔
1225
        case <-confIntent.Confirmed:
×
1226
                t.Fatal("tx was confirmed unexpectedly")
×
1227
        case <-time.After(1 * time.Second):
8✔
1228
        }
1229

1230
        // Reorganize transaction out of the chain by generating a longer fork
1231
        // from the other miner. The transaction is not included in this fork.
1232
        _, err = miner2.Client.Generate(2)
8✔
1233
        require.NoError(t, err)
8✔
1234

8✔
1235
        // Reconnect nodes to reach consensus on the longest chain. miner2's chain
8✔
1236
        // should win and become active on miner1.
8✔
1237
        if err := rpctest.ConnectNode(miner, miner2); err != nil {
8✔
1238
                t.Fatalf("unable to connect harnesses: %v", err)
×
1239
        }
×
1240
        nodeSlice = []*rpctest.Harness{miner, miner2}
8✔
1241
        if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil {
8✔
1242
                t.Fatalf("unable to join node on blocks: %v", err)
×
1243
        }
×
1244

1245
        _, nodeHeight1, err = miner.Client.GetBestBlock()
8✔
1246
        if err != nil {
8✔
1247
                t.Fatalf("unable to get current blockheight %v", err)
×
1248
        }
×
1249

1250
        _, nodeHeight2, err = miner2.Client.GetBestBlock()
8✔
1251
        if err != nil {
8✔
1252
                t.Fatalf("unable to get current blockheight %v", err)
×
1253
        }
×
1254

1255
        if nodeHeight1 != nodeHeight2 {
8✔
1256
                t.Fatalf("expected both miners to be on the same height: %v vs %v",
×
1257
                        nodeHeight1, nodeHeight2)
×
1258
        }
×
1259

1260
        // Even though there is one block above the height of the block that the
1261
        // transaction was included in, it is not the active chain so the
1262
        // notification should not be sent.
1263
        select {
8✔
1264
        case <-confIntent.Confirmed:
×
1265
                t.Fatal("tx was confirmed unexpectedly")
×
1266
        case <-time.After(1 * time.Second):
8✔
1267
        }
1268

1269
        // Now confirm the transaction on the longest chain and verify that we
1270
        // receive the notification.
1271
        tx, err := miner.Client.GetRawTransaction(txid)
8✔
1272
        require.NoError(t, err, "unable to get raw tx")
8✔
1273

8✔
1274
        txid, err = miner2.Client.SendRawTransaction(tx.MsgTx(), false)
8✔
1275
        require.NoError(t, err, "unable to get send tx")
8✔
1276
        if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
8✔
1277
                t.Fatalf("tx not relayed to miner: %v", err)
×
1278
        }
×
1279

1280
        _, err = miner.Client.Generate(3)
8✔
1281
        require.NoError(t, err, "unable to generate single block")
8✔
1282

8✔
1283
        // Since confIntent did not request all confirmations, make sure that
8✔
1284
        // it only receives the final confirmation with num confs left as 0.
8✔
1285
        select {
8✔
1286
        case txConf := <-confIntent.Confirmed:
8✔
1287
                require.Zero(t, txConf.NumConfsLeft)
8✔
1288

1289
        case <-time.After(20 * time.Second):
×
1290
                t.Fatalf("confirmation notification never received")
×
1291
        }
1292
}
1293

1294
// testReorgSpend ensures that the different ChainNotifier implementations
1295
// correctly handle outpoints whose spending transaction has been reorged out of
1296
// the chain.
1297
func testReorgSpend(miner *rpctest.Harness,
1298
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool, t *testing.T) {
8✔
1299

8✔
1300
        // We'll start by creating an output and registering a spend
8✔
1301
        // notification for it.
8✔
1302
        outpoint, output, privKey := chainntnfs.CreateSpendableOutput(t, miner)
8✔
1303
        _, heightHint, err := miner.Client.GetBestBlock()
8✔
1304
        require.NoError(t, err, "unable to retrieve current height")
8✔
1305

8✔
1306
        var spendIntent *chainntnfs.SpendEvent
8✔
1307
        if scriptDispatch {
12✔
1308
                spendIntent, err = notifier.RegisterSpendNtfn(
4✔
1309
                        nil, output.PkScript, uint32(heightHint),
4✔
1310
                )
4✔
1311
        } else {
8✔
1312
                spendIntent, err = notifier.RegisterSpendNtfn(
4✔
1313
                        outpoint, output.PkScript, uint32(heightHint),
4✔
1314
                )
4✔
1315
        }
4✔
1316
        require.NoError(t, err, "unable to register for spend")
8✔
1317

8✔
1318
        // Set up a new miner that we can use to cause a reorg.
8✔
1319
        miner2 := unittest.NewMiner(
8✔
1320
                t, unittest.NetParams, []string{"--txindex"}, false, 0,
8✔
1321
        )
8✔
1322

8✔
1323
        // We start by connecting the new miner to our original miner, in order
8✔
1324
        // to have a consistent view of the chain from both miners. They should
8✔
1325
        // be on the same block height.
8✔
1326
        if err := rpctest.ConnectNode(miner, miner2); err != nil {
8✔
1327
                t.Fatalf("unable to connect miners: %v", err)
×
1328
        }
×
1329
        nodeSlice := []*rpctest.Harness{miner, miner2}
8✔
1330
        if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil {
8✔
1331
                t.Fatalf("unable to sync miners: %v", err)
×
1332
        }
×
1333
        _, minerHeight1, err := miner.Client.GetBestBlock()
8✔
1334
        require.NoError(t, err, "unable to get miner1's current height")
8✔
1335
        _, minerHeight2, err := miner2.Client.GetBestBlock()
8✔
1336
        require.NoError(t, err, "unable to get miner2's current height")
8✔
1337
        if minerHeight1 != minerHeight2 {
8✔
1338
                t.Fatalf("expected both miners to be on the same height: "+
×
1339
                        "%v vs %v", minerHeight1, minerHeight2)
×
1340
        }
×
1341

1342
        // We disconnect the two nodes, such that we can start mining on them
1343
        // individually without the other one learning about the new blocks.
1344
        err = miner.Client.AddNode(miner2.P2PAddress(), rpcclient.ANRemove)
8✔
1345
        require.NoError(t, err, "unable to disconnect miners")
8✔
1346

8✔
1347
        // Craft the spending transaction for the outpoint created above and
8✔
1348
        // confirm it under the chain of the original miner.
8✔
1349
        spendTx := chainntnfs.CreateSpendTx(t, outpoint, output, privKey)
8✔
1350
        spendTxHash, err := miner.Client.SendRawTransaction(spendTx, true)
8✔
1351
        require.NoError(t, err, "unable to broadcast spend tx")
8✔
1352
        if err := chainntnfs.WaitForMempoolTx(miner, spendTxHash); err != nil {
8✔
1353
                t.Fatalf("spend tx not relayed to miner: %v", err)
×
1354
        }
×
1355
        const numBlocks = 1
8✔
1356
        if _, err := miner.Client.Generate(numBlocks); err != nil {
8✔
1357
                t.Fatalf("unable to generate blocks: %v", err)
×
1358
        }
×
1359
        _, spendHeight, err := miner.Client.GetBestBlock()
8✔
1360
        require.NoError(t, err, "unable to get spend height")
8✔
1361

8✔
1362
        // We should see a spend notification dispatched with the correct spend
8✔
1363
        // details.
8✔
1364
        select {
8✔
1365
        case spendDetails := <-spendIntent.Spend:
8✔
1366
                checkNotificationFields(
8✔
1367
                        spendDetails, outpoint, spendTxHash, spendHeight, t,
8✔
1368
                )
8✔
1369
        case <-time.After(5 * time.Second):
×
1370
                t.Fatal("expected spend notification to be dispatched")
×
1371
        }
1372

1373
        // Now, with the other miner, we'll generate one more block than the
1374
        // other miner and connect them to cause a reorg.
1375
        if _, err := miner2.Client.Generate(numBlocks + 1); err != nil {
8✔
1376
                t.Fatalf("unable to generate blocks: %v", err)
×
1377
        }
×
1378
        if err := rpctest.ConnectNode(miner, miner2); err != nil {
8✔
1379
                t.Fatalf("unable to connect miners: %v", err)
×
1380
        }
×
1381
        nodeSlice = []*rpctest.Harness{miner2, miner}
8✔
1382
        if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil {
8✔
1383
                t.Fatalf("unable to sync miners: %v", err)
×
1384
        }
×
1385
        _, minerHeight1, err = miner.Client.GetBestBlock()
8✔
1386
        require.NoError(t, err, "unable to get miner1's current height")
8✔
1387
        _, minerHeight2, err = miner2.Client.GetBestBlock()
8✔
1388
        require.NoError(t, err, "unable to get miner2's current height")
8✔
1389
        if minerHeight1 != minerHeight2 {
8✔
1390
                t.Fatalf("expected both miners to be on the same height: "+
×
1391
                        "%v vs %v", minerHeight1, minerHeight2)
×
1392
        }
×
1393

1394
        // We should receive a reorg notification.
1395
        select {
8✔
1396
        case _, ok := <-spendIntent.Reorg:
8✔
1397
                if !ok {
8✔
1398
                        t.Fatal("unexpected reorg channel closed")
×
1399
                }
×
1400
        case <-time.After(5 * time.Second):
×
1401
                t.Fatal("expected to receive reorg notification")
×
1402
        }
1403

1404
        // Now that both miners are on the same chain, we'll confirm the
1405
        // spending transaction of the outpoint and receive a notification for
1406
        // it.
1407
        if _, err = miner2.Client.SendRawTransaction(spendTx, true); err != nil {
8✔
1408
                t.Fatalf("unable to broadcast spend tx: %v", err)
×
1409
        }
×
1410
        if err := chainntnfs.WaitForMempoolTx(miner, spendTxHash); err != nil {
8✔
1411
                t.Fatalf("tx not relayed to miner: %v", err)
×
1412
        }
×
1413
        if _, err := miner.Client.Generate(numBlocks); err != nil {
8✔
1414
                t.Fatalf("unable to generate single block: %v", err)
×
1415
        }
×
1416
        _, spendHeight, err = miner.Client.GetBestBlock()
8✔
1417
        require.NoError(t, err, "unable to retrieve current height")
8✔
1418

8✔
1419
        select {
8✔
1420
        case spendDetails := <-spendIntent.Spend:
8✔
1421
                checkNotificationFields(
8✔
1422
                        spendDetails, outpoint, spendTxHash, spendHeight, t,
8✔
1423
                )
8✔
1424
        case <-time.After(5 * time.Second):
×
1425
                t.Fatal("expected spend notification to be dispatched")
×
1426
        }
1427
}
1428

1429
// testCatchUpClientOnMissedBlocks tests the case of multiple registered client
1430
// receiving historical block epoch notifications due to their best known block
1431
// being out of date.
1432
func testCatchUpClientOnMissedBlocks(miner *rpctest.Harness,
1433
        notifier chainntnfs.TestChainNotifier, t *testing.T) {
4✔
1434

4✔
1435
        const numBlocks = 10
4✔
1436
        const numClients = 5
4✔
1437
        var wg sync.WaitGroup
4✔
1438

4✔
1439
        outdatedHash, outdatedHeight, err := miner.Client.GetBestBlock()
4✔
1440
        require.NoError(t, err, "unable to retrieve current height")
4✔
1441

4✔
1442
        // This function is used by UnsafeStart to ensure all notifications
4✔
1443
        // are fully drained before clients register for notifications.
4✔
1444
        generateBlocks := func() error {
8✔
1445
                _, err = miner.Client.Generate(numBlocks)
4✔
1446
                return err
4✔
1447
        }
4✔
1448

1449
        // We want to ensure that when a client registers for block notifications,
1450
        // the notifier's best block is at the tip of the chain. If it isn't, the
1451
        // client may not receive all historical notifications.
1452
        bestHeight := outdatedHeight + numBlocks
4✔
1453
        err = notifier.UnsafeStart(bestHeight, nil, bestHeight, generateBlocks)
4✔
1454
        require.NoError(t, err, "unable to unsafe start the notifier")
4✔
1455
        defer notifier.Stop()
4✔
1456

4✔
1457
        // Create numClients clients whose best known block is 10 blocks behind
4✔
1458
        // the tip of the chain. We expect each client to receive numBlocks
4✔
1459
        // notifications, 1 for each block  they're behind.
4✔
1460
        clients := make([]*chainntnfs.BlockEpochEvent, 0, numClients)
4✔
1461
        outdatedBlock := &chainntnfs.BlockEpoch{
4✔
1462
                Height: outdatedHeight, Hash: outdatedHash,
4✔
1463
        }
4✔
1464
        for i := 0; i < numClients; i++ {
24✔
1465
                epochClient, err := notifier.RegisterBlockEpochNtfn(outdatedBlock)
20✔
1466
                if err != nil {
20✔
1467
                        t.Fatalf("unable to register for epoch notification: %v", err)
×
1468
                }
×
1469
                clients = append(clients, epochClient)
20✔
1470
        }
1471
        for expectedHeight := outdatedHeight + 1; expectedHeight <=
4✔
1472
                bestHeight; expectedHeight++ {
44✔
1473

40✔
1474
                for _, epochClient := range clients {
240✔
1475
                        select {
200✔
1476
                        case block := <-epochClient.Epochs:
200✔
1477
                                if block.Height != expectedHeight {
200✔
1478
                                        t.Fatalf("received block of height: %d, "+
×
1479
                                                "expected: %d", block.Height,
×
1480
                                                expectedHeight)
×
1481
                                }
×
1482
                        case <-time.After(20 * time.Second):
×
1483
                                t.Fatalf("did not receive historical notification "+
×
1484
                                        "for height %d", expectedHeight)
×
1485
                        }
1486

1487
                }
1488
        }
1489

1490
        // Finally, ensure that an extra block notification wasn't received.
1491
        anyExtras := make(chan struct{}, len(clients))
4✔
1492
        for _, epochClient := range clients {
24✔
1493
                wg.Add(1)
20✔
1494
                go func(epochClient *chainntnfs.BlockEpochEvent) {
40✔
1495
                        defer wg.Done()
20✔
1496
                        select {
20✔
1497
                        case <-epochClient.Epochs:
×
1498
                                anyExtras <- struct{}{}
×
1499
                        case <-time.After(5 * time.Second):
20✔
1500
                        }
1501
                }(epochClient)
1502
        }
1503

1504
        wg.Wait()
4✔
1505
        close(anyExtras)
4✔
1506

4✔
1507
        var extraCount int
4✔
1508
        for range anyExtras {
4✔
1509
                extraCount++
×
1510
        }
×
1511

1512
        if extraCount > 0 {
4✔
1513
                t.Fatalf("received %d unexpected block notification", extraCount)
×
1514
        }
×
1515
}
1516

1517
// testCatchUpOnMissedBlocks the case of multiple registered clients receiving
1518
// historical block epoch notifications due to the notifier's best known block
1519
// being out of date.
1520
func testCatchUpOnMissedBlocks(miner *rpctest.Harness,
1521
        notifier chainntnfs.TestChainNotifier, t *testing.T) {
4✔
1522

4✔
1523
        const numBlocks = 10
4✔
1524
        const numClients = 5
4✔
1525
        var wg sync.WaitGroup
4✔
1526

4✔
1527
        _, bestHeight, err := miner.Client.GetBestBlock()
4✔
1528
        if err != nil {
4✔
1529
                t.Fatalf("unable to get current blockheight %v", err)
×
1530
        }
×
1531

1532
        // This function is used by UnsafeStart to ensure all notifications
1533
        // are fully drained before clients register for notifications.
1534
        generateBlocks := func() error {
8✔
1535
                _, err = miner.Client.Generate(numBlocks)
4✔
1536
                return err
4✔
1537
        }
4✔
1538

1539
        // Next, start the notifier with outdated best block information.
1540
        err = notifier.UnsafeStart(
4✔
1541
                bestHeight, nil, bestHeight+numBlocks, generateBlocks,
4✔
1542
        )
4✔
1543
        require.NoError(t, err, "unable to unsafe start the notifier")
4✔
1544
        defer notifier.Stop()
4✔
1545

4✔
1546
        // Create numClients clients who will listen for block notifications.
4✔
1547
        clients := make([]*chainntnfs.BlockEpochEvent, 0, numClients)
4✔
1548
        for i := 0; i < numClients; i++ {
24✔
1549
                epochClient, err := notifier.RegisterBlockEpochNtfn(nil)
20✔
1550
                if err != nil {
20✔
1551
                        t.Fatalf("unable to register for epoch notification: %v", err)
×
1552
                }
×
1553

1554
                // Drain the notification dispatched upon registration as we're
1555
                // not interested in it.
1556
                select {
20✔
1557
                case <-epochClient.Epochs:
20✔
1558
                case <-time.After(5 * time.Second):
×
1559
                        t.Fatal("expected to receive epoch for current block " +
×
1560
                                "upon registration")
×
1561
                }
1562

1563
                clients = append(clients, epochClient)
20✔
1564
        }
1565

1566
        // Generate a single block to trigger the backlog of historical
1567
        // notifications for the previously mined blocks.
1568
        if _, err := miner.Client.Generate(1); err != nil {
4✔
1569
                t.Fatalf("unable to generate blocks: %v", err)
×
1570
        }
×
1571

1572
        // We expect each client to receive numBlocks + 1 notifications, 1 for
1573
        // each block that the notifier has missed out on.
1574
        for expectedHeight := bestHeight + 1; expectedHeight <=
4✔
1575
                bestHeight+numBlocks+1; expectedHeight++ {
48✔
1576

44✔
1577
                for _, epochClient := range clients {
264✔
1578
                        select {
220✔
1579
                        case block := <-epochClient.Epochs:
220✔
1580
                                if block.Height != expectedHeight {
220✔
1581
                                        t.Fatalf("received block of height: %d, "+
×
1582
                                                "expected: %d", block.Height,
×
1583
                                                expectedHeight)
×
1584
                                }
×
1585
                        case <-time.After(20 * time.Second):
×
1586
                                t.Fatalf("did not receive historical notification "+
×
1587
                                        "for height %d", expectedHeight)
×
1588
                        }
1589
                }
1590
        }
1591

1592
        // Finally, ensure that an extra block notification wasn't received.
1593
        anyExtras := make(chan struct{}, len(clients))
4✔
1594
        for _, epochClient := range clients {
24✔
1595
                wg.Add(1)
20✔
1596
                go func(epochClient *chainntnfs.BlockEpochEvent) {
40✔
1597
                        defer wg.Done()
20✔
1598
                        select {
20✔
1599
                        case <-epochClient.Epochs:
×
1600
                                anyExtras <- struct{}{}
×
1601
                        case <-time.After(5 * time.Second):
20✔
1602
                        }
1603
                }(epochClient)
1604
        }
1605

1606
        wg.Wait()
4✔
1607
        close(anyExtras)
4✔
1608

4✔
1609
        var extraCount int
4✔
1610
        for range anyExtras {
4✔
1611
                extraCount++
×
1612
        }
×
1613

1614
        if extraCount > 0 {
4✔
1615
                t.Fatalf("received %d unexpected block notification", extraCount)
×
1616
        }
×
1617
}
1618

1619
// testCatchUpOnMissedBlocks tests that a client will still receive all valid
1620
// block notifications in the case where a notifier's best block has been reorged
1621
// out of the chain.
1622
func testCatchUpOnMissedBlocksWithReorg(miner1 *rpctest.Harness,
1623
        notifier chainntnfs.TestChainNotifier, t *testing.T) {
4✔
1624

4✔
1625
        // If this is the neutrino notifier, then we'll skip this test for now
4✔
1626
        // as we're missing functionality required to ensure the test passes
4✔
1627
        // reliably.
4✔
1628
        if _, ok := notifier.(*neutrinonotify.NeutrinoNotifier); ok {
5✔
1629
                t.Skip("skipping re-org test for neutrino")
1✔
1630
        }
1✔
1631

1632
        const numBlocks = 10
3✔
1633
        const numClients = 5
3✔
1634
        var wg sync.WaitGroup
3✔
1635

3✔
1636
        // Set up a new miner that we can use to cause a reorg.
3✔
1637
        miner2 := unittest.NewMiner(
3✔
1638
                t, unittest.NetParams, []string{"--txindex"}, false, 0,
3✔
1639
        )
3✔
1640

3✔
1641
        // We start by connecting the new miner to our original miner,
3✔
1642
        // such that it will sync to our original chain.
3✔
1643
        if err := rpctest.ConnectNode(miner1, miner2); err != nil {
3✔
1644
                t.Fatalf("unable to connect harnesses: %v", err)
×
1645
        }
×
1646
        nodeSlice := []*rpctest.Harness{miner1, miner2}
3✔
1647
        if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil {
3✔
1648
                t.Fatalf("unable to join node on blocks: %v", err)
×
1649
        }
×
1650

1651
        // The two should be on the same blockheight.
1652
        _, nodeHeight1, err := miner1.Client.GetBestBlock()
3✔
1653
        if err != nil {
3✔
1654
                t.Fatalf("unable to get current blockheight %v", err)
×
1655
        }
×
1656

1657
        _, nodeHeight2, err := miner2.Client.GetBestBlock()
3✔
1658
        if err != nil {
3✔
1659
                t.Fatalf("unable to get current blockheight %v", err)
×
1660
        }
×
1661

1662
        if nodeHeight1 != nodeHeight2 {
3✔
1663
                t.Fatalf("expected both miners to be on the same height: %v vs %v",
×
1664
                        nodeHeight1, nodeHeight2)
×
1665
        }
×
1666

1667
        // We disconnect the two nodes, such that we can start mining on them
1668
        // individually without the other one learning about the new blocks.
1669
        err = miner1.Client.AddNode(miner2.P2PAddress(), rpcclient.ANRemove)
3✔
1670
        require.NoError(t, err, "unable to remove node")
3✔
1671

3✔
1672
        // Now mine on each chain separately
3✔
1673
        blocks, err := miner1.Client.Generate(numBlocks)
3✔
1674
        require.NoError(t, err, "unable to generate single block")
3✔
1675

3✔
1676
        // We generate an extra block on miner 2's chain to ensure it is the
3✔
1677
        // longer chain.
3✔
1678
        _, err = miner2.Client.Generate(numBlocks + 1)
3✔
1679
        require.NoError(t, err, "unable to generate single block")
3✔
1680

3✔
1681
        // Sync the two chains to ensure they will sync to miner2's chain.
3✔
1682
        if err := rpctest.ConnectNode(miner1, miner2); err != nil {
3✔
1683
                t.Fatalf("unable to connect harnesses: %v", err)
×
1684
        }
×
1685
        nodeSlice = []*rpctest.Harness{miner1, miner2}
3✔
1686
        if err := rpctest.JoinNodes(nodeSlice, rpctest.Blocks); err != nil {
3✔
1687
                t.Fatalf("unable to join node on blocks: %v", err)
×
1688
        }
×
1689

1690
        // The two should be on the same block hash.
1691
        timeout := time.After(10 * time.Second)
3✔
1692
        for {
6✔
1693
                nodeHash1, _, err := miner1.Client.GetBestBlock()
3✔
1694
                if err != nil {
3✔
1695
                        t.Fatalf("unable to get current block hash: %v", err)
×
1696
                }
×
1697

1698
                nodeHash2, _, err := miner2.Client.GetBestBlock()
3✔
1699
                if err != nil {
3✔
1700
                        t.Fatalf("unable to get current block hash: %v", err)
×
1701
                }
×
1702

1703
                if *nodeHash1 == *nodeHash2 {
6✔
1704
                        break
3✔
1705
                }
1706
                select {
×
1707
                case <-timeout:
×
1708
                        t.Fatalf("Unable to sync two chains")
×
1709
                case <-time.After(50 * time.Millisecond):
×
1710
                        continue
×
1711
                }
1712
        }
1713

1714
        // Next, start the notifier with outdated best block information.
1715
        // We set the notifier's best block to be the last block mined on the
1716
        // shorter chain, to test that the notifier correctly rewinds to
1717
        // the common ancestor between the two chains.
1718
        syncHeight := nodeHeight1 + numBlocks + 1
3✔
1719
        err = notifier.UnsafeStart(
3✔
1720
                nodeHeight1+numBlocks, blocks[numBlocks-1], syncHeight, nil,
3✔
1721
        )
3✔
1722
        require.NoError(t, err, "Unable to unsafe start the notifier")
3✔
1723
        defer notifier.Stop()
3✔
1724

3✔
1725
        // Create numClients clients who will listen for block notifications.
3✔
1726
        clients := make([]*chainntnfs.BlockEpochEvent, 0, numClients)
3✔
1727
        for i := 0; i < numClients; i++ {
18✔
1728
                epochClient, err := notifier.RegisterBlockEpochNtfn(nil)
15✔
1729
                if err != nil {
15✔
1730
                        t.Fatalf("unable to register for epoch notification: %v", err)
×
1731
                }
×
1732

1733
                // Drain the notification dispatched upon registration as we're
1734
                // not interested in it.
1735
                select {
15✔
1736
                case <-epochClient.Epochs:
15✔
1737
                case <-time.After(5 * time.Second):
×
1738
                        t.Fatal("expected to receive epoch for current block " +
×
1739
                                "upon registration")
×
1740
                }
1741

1742
                clients = append(clients, epochClient)
15✔
1743
        }
1744

1745
        // Generate a single block, which should trigger the notifier to rewind
1746
        // to the common ancestor and dispatch notifications from there.
1747
        _, err = miner2.Client.Generate(1)
3✔
1748
        require.NoError(t, err, "unable to generate single block")
3✔
1749

3✔
1750
        // If the chain backend to the notifier stores information about reorged
3✔
1751
        // blocks, the notifier is able to rewind the chain to the common
3✔
1752
        // ancestor between the chain tip and its outdated best known block.
3✔
1753
        // In this case, the client is expected to receive numBlocks + 2
3✔
1754
        // notifications, 1 for each block the notifier has missed out on from
3✔
1755
        // the longer chain.
3✔
1756
        //
3✔
1757
        // If the chain backend does not store information about reorged blocks,
3✔
1758
        // the notifier has no way of knowing where to rewind to and therefore
3✔
1759
        // the client is only expected to receive notifications for blocks
3✔
1760
        // whose height is greater than the notifier's best known height: 2
3✔
1761
        // notifications, in this case.
3✔
1762
        var startingHeight int32
3✔
1763
        switch notifier.(type) {
3✔
1764
        case *neutrinonotify.NeutrinoNotifier:
×
1765
                startingHeight = nodeHeight1 + numBlocks + 1
×
1766
        default:
3✔
1767
                startingHeight = nodeHeight1 + 1
3✔
1768
        }
1769

1770
        for expectedHeight := startingHeight; expectedHeight <=
3✔
1771
                nodeHeight1+numBlocks+2; expectedHeight++ {
39✔
1772

36✔
1773
                for _, epochClient := range clients {
216✔
1774
                        select {
180✔
1775
                        case block := <-epochClient.Epochs:
180✔
1776
                                if block.Height != expectedHeight {
180✔
1777
                                        t.Fatalf("received block of height: %d, "+
×
1778
                                                "expected: %d", block.Height,
×
1779
                                                expectedHeight)
×
1780
                                }
×
1781
                        case <-time.After(20 * time.Second):
×
1782
                                t.Fatalf("did not receive historical notification "+
×
1783
                                        "for height %d", expectedHeight)
×
1784
                        }
1785
                }
1786
        }
1787

1788
        // Finally, ensure that an extra block notification wasn't received.
1789
        anyExtras := make(chan struct{}, len(clients))
3✔
1790
        for _, epochClient := range clients {
18✔
1791
                wg.Add(1)
15✔
1792
                go func(epochClient *chainntnfs.BlockEpochEvent) {
30✔
1793
                        defer wg.Done()
15✔
1794
                        select {
15✔
1795
                        case <-epochClient.Epochs:
×
1796
                                anyExtras <- struct{}{}
×
1797
                        case <-time.After(5 * time.Second):
15✔
1798
                        }
1799
                }(epochClient)
1800
        }
1801

1802
        wg.Wait()
3✔
1803
        close(anyExtras)
3✔
1804

3✔
1805
        var extraCount int
3✔
1806
        for range anyExtras {
3✔
1807
                extraCount++
×
1808
        }
×
1809

1810
        if extraCount > 0 {
3✔
1811
                t.Fatalf("received %d unexpected block notification", extraCount)
×
1812
        }
×
1813
}
1814

1815
// testIncludeBlockAsymmetry tests that if the same output is registered for a
1816
// notification by two callers, one is able to get a notification with the
1817
// block and the other one without it.
1818
func testIncludeBlockAsymmetry(miner *rpctest.Harness,
1819
        notifier chainntnfs.TestChainNotifier, scriptDispatch bool,
1820
        t *testing.T) {
8✔
1821

8✔
1822
        // We'll start by creating a new test transaction, waiting long enough
8✔
1823
        // for it to get into the mempool.
8✔
1824
        txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
8✔
1825
        require.NoError(t, err, "unable to create test tx")
8✔
1826
        if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
8✔
1827
                t.Fatalf("tx not relayed to miner: %v", err)
×
1828
        }
×
1829

1830
        _, currentHeight, err := miner.Client.GetBestBlock()
8✔
1831
        require.NoError(t, err, "unable to get current height")
8✔
1832

8✔
1833
        var (
8✔
1834
                confIntentNoBlock *chainntnfs.ConfirmationEvent
8✔
1835
                confIntentBlock   *chainntnfs.ConfirmationEvent
8✔
1836

8✔
1837
                numConfsLong  = uint32(6)
8✔
1838
                numConfsShort = uint32(1)
8✔
1839
        )
8✔
1840
        dispatchClients := func() {
24✔
1841
                dispatchTxid := txid
16✔
1842
                if scriptDispatch {
24✔
1843
                        dispatchTxid = nil
8✔
1844
                }
8✔
1845

1846
                confIntentNoBlock, err = notifier.RegisterConfirmationsNtfn(
16✔
1847
                        dispatchTxid, pkScript, numConfsLong,
16✔
1848
                        uint32(currentHeight),
16✔
1849
                )
16✔
1850
                require.NoError(t, err)
16✔
1851

16✔
1852
                confIntentBlock, err = notifier.RegisterConfirmationsNtfn(
16✔
1853
                        dispatchTxid, pkScript, numConfsShort,
16✔
1854
                        uint32(currentHeight), chainntnfs.WithIncludeBlock(),
16✔
1855
                )
16✔
1856
                require.NoError(t, err)
16✔
1857
        }
1858
        assertNtfns := func() {
24✔
1859
                // Make sure the long confirmation client receives the
16✔
1860
                // notification but not the block.
16✔
1861
                confNtfnNoBlock, err := lnutils.RecvOrTimeout(
16✔
1862
                        confIntentNoBlock.Confirmed, time.Second*5,
16✔
1863
                )
16✔
1864
                require.NoError(t, err)
16✔
1865
                require.Nil(t, (*confNtfnNoBlock).Block, "block not included")
16✔
1866

16✔
1867
                // And the short confirmation client receives the notification
16✔
1868
                // and the block.
16✔
1869
                confNtfnBlock, err := lnutils.RecvOrTimeout(
16✔
1870
                        confIntentBlock.Confirmed, time.Second*5,
16✔
1871
                )
16✔
1872
                require.NoError(t, err)
16✔
1873
                require.NotNil(t, (*confNtfnBlock).Block, "block included")
16✔
1874
        }
16✔
1875

1876
        // First, we start off by registering two clients for the same txid and
1877
        // pkScript. One of them will require 6 confirmations but not request
1878
        // the block, the other will just require a single confirmation and
1879
        // request the block.
1880
        dispatchClients()
8✔
1881

8✔
1882
        // Next, we'll generate a few blocks, which should confirm the
8✔
1883
        // transaction created above and trigger the notifications, as it should
8✔
1884
        // be confirmed enough for both clients.
8✔
1885
        _, err = miner.Client.Generate(6)
8✔
1886
        require.NoError(t, err, "unable to generate single block")
8✔
1887

8✔
1888
        // Make sure we got the notifications we expected.
8✔
1889
        assertNtfns()
8✔
1890

8✔
1891
        // Now dispatch the same clients again, which should hit the same
8✔
1892
        // conditions as above and use the cached rescan details.
8✔
1893
        dispatchClients()
8✔
1894

8✔
1895
        // And again, the notifications should be triggered as expected.
8✔
1896
        assertNtfns()
8✔
1897
}
1898

1899
type txNtfnTestCase struct {
1900
        name string
1901
        test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
1902
                scriptDispatch bool, t *testing.T)
1903
}
1904

1905
type blockNtfnTestCase struct {
1906
        name string
1907
        test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
1908
                t *testing.T)
1909
}
1910

1911
type blockCatchupTestCase struct {
1912
        name string
1913
        test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
1914
                t *testing.T)
1915
}
1916

1917
var txNtfnTests = []txNtfnTestCase{
1918
        {
1919
                name: "single conf ntfn",
1920
                test: testSingleConfirmationNotification,
1921
        },
1922
        {
1923
                name: "multi conf ntfn",
1924
                test: testMultiConfirmationNotification,
1925
        },
1926
        {
1927
                name: "batch conf ntfn",
1928
                test: testBatchConfirmationNotification,
1929
        },
1930
        {
1931
                name: "multi client conf",
1932
                test: testMultiClientConfirmationNotification,
1933
        },
1934
        {
1935
                name: "lazy ntfn consumer",
1936
                test: testLazyNtfnConsumer,
1937
        },
1938
        {
1939
                name: "historical conf dispatch",
1940
                test: testTxConfirmedBeforeNtfnRegistration,
1941
        },
1942
        {
1943
                name: "reorg conf",
1944
                test: testReorgConf,
1945
        },
1946
        {
1947
                name: "spend ntfn",
1948
                test: testSpendNotification,
1949
        },
1950
        {
1951
                name: "historical spend dispatch",
1952
                test: testSpendBeforeNtfnRegistration,
1953
        },
1954
        {
1955
                name: "reorg spend",
1956
                test: testReorgSpend,
1957
        },
1958
        {
1959
                name: "cancel spend ntfn",
1960
                test: testCancelSpendNtfn,
1961
        },
1962
        {
1963
                name: "test include block asymmetry",
1964
                test: testIncludeBlockAsymmetry,
1965
        },
1966
}
1967

1968
var blockNtfnTests = []blockNtfnTestCase{
1969
        {
1970
                name: "block epoch",
1971
                test: testBlockEpochNotification,
1972
        },
1973
        {
1974
                name: "cancel epoch ntfn",
1975
                test: testCancelEpochNtfn,
1976
        },
1977
}
1978

1979
var blockCatchupTests = []blockCatchupTestCase{
1980
        {
1981
                name: "catch up client on historical block epoch ntfns",
1982
                test: testCatchUpClientOnMissedBlocks,
1983
        },
1984
        {
1985
                name: "test catch up on missed blocks",
1986
                test: testCatchUpOnMissedBlocks,
1987
        },
1988
        {
1989
                name: "test catch up on missed blocks w/ reorged best block",
1990
                test: testCatchUpOnMissedBlocksWithReorg,
1991
        },
1992
}
1993

1994
// TestInterfaces tests all registered interfaces with a unified set of tests
1995
// which exercise each of the required methods found within the ChainNotifier
1996
// interface.
1997
//
1998
// NOTE: In the future, when additional implementations of the ChainNotifier
1999
// interface have been implemented, in order to ensure the new concrete
2000
// implementation is automatically tested, two steps must be undertaken. First,
2001
// one needs add a "non-captured" (_) import from the new sub-package. This
2002
// import should trigger an init() method within the package which registers
2003
// the interface. Second, an additional case in the switch within the main loop
2004
// below needs to be added which properly initializes the interface.
2005
func TestInterfaces(t *testing.T, targetBackEnd string) {
4✔
2006
        // Initialize the harness around a btcd node which will serve as our
4✔
2007
        // dedicated miner to generate blocks, cause re-orgs, etc. We'll set up
4✔
2008
        // this node with a chain length of 125, so we have plenty of BTC to
4✔
2009
        // play around with.
4✔
2010
        miner := unittest.NewMiner(t, unittest.NetParams, nil, true, 25)
4✔
2011

4✔
2012
        p2pAddr := miner.P2PAddress()
4✔
2013

4✔
2014
        log.Printf("Running %v ChainNotifier interface tests",
4✔
2015
                2*len(txNtfnTests)+len(blockNtfnTests)+len(blockCatchupTests))
4✔
2016

4✔
2017
        for _, notifierDriver := range chainntnfs.RegisteredNotifiers() {
20✔
2018
                notifierType := notifierDriver.NotifierType
16✔
2019
                if notifierType != targetBackEnd {
28✔
2020
                        continue
12✔
2021
                }
2022

2023
                // Initialize a height hint cache for each notifier.
2024
                tempDir := t.TempDir()
4✔
2025
                db := channeldb.OpenForTesting(t, tempDir)
4✔
2026

4✔
2027
                testCfg := channeldb.CacheConfig{
4✔
2028
                        QueryDisable: false,
4✔
2029
                }
4✔
2030
                hintCache, err := channeldb.NewHeightHintCache(
4✔
2031
                        testCfg, db.Backend,
4✔
2032
                )
4✔
2033
                if err != nil {
4✔
2034
                        t.Fatalf("unable to create height hint cache: %v", err)
×
2035
                }
×
2036

2037
                blockCache := blockcache.NewBlockCache(10000)
4✔
2038

4✔
2039
                var (
4✔
2040
                        newNotifier func() (chainntnfs.TestChainNotifier, error)
4✔
2041
                )
4✔
2042

4✔
2043
                switch notifierType {
4✔
2044
                case "bitcoind":
1✔
2045
                        var bitcoindConn *chain.BitcoindConn
1✔
2046
                        bitcoindConn = unittest.NewBitcoindBackend(
1✔
2047
                                t, unittest.NetParams, miner, true, false,
1✔
2048
                        )
1✔
2049
                        newNotifier = func() (chainntnfs.TestChainNotifier, error) {
5✔
2050
                                return bitcoindnotify.New(
4✔
2051
                                        bitcoindConn, unittest.NetParams,
4✔
2052
                                        hintCache, hintCache, blockCache,
4✔
2053
                                ), nil
4✔
2054
                        }
4✔
2055

2056
                case "bitcoind-rpc-polling":
1✔
2057
                        var bitcoindConn *chain.BitcoindConn
1✔
2058
                        bitcoindConn = unittest.NewBitcoindBackend(
1✔
2059
                                t, unittest.NetParams, miner, true, true,
1✔
2060
                        )
1✔
2061
                        newNotifier = func() (chainntnfs.TestChainNotifier, error) {
5✔
2062
                                return bitcoindnotify.New(
4✔
2063
                                        bitcoindConn, unittest.NetParams,
4✔
2064
                                        hintCache, hintCache, blockCache,
4✔
2065
                                ), nil
4✔
2066
                        }
4✔
2067

2068
                case "btcd":
1✔
2069
                        rpcConfig := miner.RPCConfig()
1✔
2070
                        newNotifier = func() (chainntnfs.TestChainNotifier, error) {
5✔
2071
                                return btcdnotify.New(
4✔
2072
                                        &rpcConfig, unittest.NetParams,
4✔
2073
                                        hintCache, hintCache, blockCache,
4✔
2074
                                )
4✔
2075
                        }
4✔
2076

2077
                case "neutrino":
1✔
2078
                        var spvNode *neutrino.ChainService
1✔
2079
                        spvNode = unittest.NewNeutrinoBackend(
1✔
2080
                                t, unittest.NetParams, p2pAddr,
1✔
2081
                        )
1✔
2082
                        newNotifier = func() (chainntnfs.TestChainNotifier, error) {
5✔
2083
                                return neutrinonotify.New(
4✔
2084
                                        spvNode, hintCache, hintCache,
4✔
2085
                                        blockCache,
4✔
2086
                                ), nil
4✔
2087
                        }
4✔
2088
                }
2089

2090
                log.Printf("Running ChainNotifier interface tests for: %v",
4✔
2091
                        notifierType)
4✔
2092

4✔
2093
                notifier, err := newNotifier()
4✔
2094
                if err != nil {
4✔
2095
                        t.Fatalf("unable to create %v notifier: %v",
×
2096
                                notifierType, err)
×
2097
                }
×
2098
                if err := notifier.Start(); err != nil {
4✔
2099
                        t.Fatalf("unable to start notifier %v: %v",
×
2100
                                notifierType, err)
×
2101
                }
×
2102

2103
                for _, txNtfnTest := range txNtfnTests {
52✔
2104
                        for _, scriptDispatch := range []bool{false, true} {
144✔
2105
                                testName := fmt.Sprintf("%v %v", notifierType,
96✔
2106
                                        txNtfnTest.name)
96✔
2107
                                if scriptDispatch {
144✔
2108
                                        testName += " with script dispatch"
48✔
2109
                                }
48✔
2110
                                success := t.Run(testName, func(t *testing.T) {
192✔
2111
                                        txNtfnTest.test(
96✔
2112
                                                miner, notifier, scriptDispatch,
96✔
2113
                                                t,
96✔
2114
                                        )
96✔
2115
                                })
96✔
2116
                                if !success {
96✔
2117
                                        break
×
2118
                                }
2119
                        }
2120
                }
2121

2122
                for _, blockNtfnTest := range blockNtfnTests {
12✔
2123
                        testName := fmt.Sprintf("%v %v", notifierType,
8✔
2124
                                blockNtfnTest.name)
8✔
2125
                        success := t.Run(testName, func(t *testing.T) {
16✔
2126
                                blockNtfnTest.test(miner, notifier, t)
8✔
2127
                        })
8✔
2128
                        if !success {
8✔
2129
                                break
×
2130
                        }
2131
                }
2132

2133
                notifier.Stop()
4✔
2134

4✔
2135
                // Run catchup tests separately since they require restarting
4✔
2136
                // the notifier every time.
4✔
2137
                for _, blockCatchupTest := range blockCatchupTests {
16✔
2138
                        notifier, err = newNotifier()
12✔
2139
                        if err != nil {
12✔
2140
                                t.Fatalf("unable to create %v notifier: %v",
×
2141
                                        notifierType, err)
×
2142
                        }
×
2143

2144
                        testName := fmt.Sprintf("%v %v", notifierType,
12✔
2145
                                blockCatchupTest.name)
12✔
2146

12✔
2147
                        success := t.Run(testName, func(t *testing.T) {
24✔
2148
                                blockCatchupTest.test(miner, notifier, t)
12✔
2149
                        })
12✔
2150
                        if !success {
12✔
2151
                                break
×
2152
                        }
2153
                }
2154
        }
2155
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc