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

lightningnetwork / lnd / 12428593038

20 Dec 2024 09:02AM UTC coverage: 58.33% (-0.2%) from 58.576%
12428593038

Pull #9382

github

guggero
.golangci.yml: speed up linter by updating start commit

With this we allow the linter to only look at recent changes, since
everything between that old commit and this most recent one has been
linted correctly anyway.
Pull Request #9382: lint: deprecate old linters, use new ref commit

133769 of 229330 relevant lines covered (58.33%)

19284.53 hits per line

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

88.42
/chainntnfs/txnotifier.go
1
package chainntnfs
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7
        "sync"
8
        "sync/atomic"
9

10
        "github.com/btcsuite/btcd/btcutil"
11
        "github.com/btcsuite/btcd/chaincfg/chainhash"
12
        "github.com/btcsuite/btcd/txscript"
13
        "github.com/btcsuite/btcd/wire"
14
)
15

16
const (
17
        // ReorgSafetyLimit is the chain depth beyond which it is assumed a
18
        // block will not be reorganized out of the chain. This is used to
19
        // determine when to prune old confirmation requests so that reorgs are
20
        // handled correctly. The average number of blocks in a day is a
21
        // reasonable value to use.
22
        ReorgSafetyLimit = 144
23

24
        // MaxNumConfs is the maximum number of confirmations that can be
25
        // requested on a transaction.
26
        MaxNumConfs = ReorgSafetyLimit
27
)
28

29
var (
30
        // ZeroHash is the value that should be used as the txid when
31
        // registering for the confirmation of a script on-chain. This allows
32
        // the notifier to match _and_ dispatch upon the inclusion of the script
33
        // on-chain, rather than the txid.
34
        ZeroHash chainhash.Hash
35

36
        // ZeroOutPoint is the value that should be used as the outpoint when
37
        // registering for the spend of a script on-chain. This allows the
38
        // notifier to match _and_ dispatch upon detecting the spend of the
39
        // script on-chain, rather than the outpoint.
40
        ZeroOutPoint wire.OutPoint
41

42
        // zeroV1KeyPush is a pkScript that pushes an all-zero 32-byte Taproot
43
        // SegWit v1 key to the stack.
44
        zeroV1KeyPush = [34]byte{
45
                txscript.OP_1, txscript.OP_DATA_32, // 32 byte of zeroes here
46
        }
47

48
        // ZeroTaprootPkScript is the parsed txscript.PkScript of an empty
49
        // Taproot SegWit v1 key being pushed to the stack. This allows the
50
        // notifier to match _and_ dispatch upon detecting the spend of the
51
        // outpoint on-chain, rather than the pkScript (which cannot be derived
52
        // from the witness alone in the SegWit v1 case).
53
        ZeroTaprootPkScript, _ = txscript.ParsePkScript(zeroV1KeyPush[:])
54
)
55

56
var (
57
        // ErrTxNotifierExiting is an error returned when attempting to interact
58
        // with the TxNotifier but it been shut down.
59
        ErrTxNotifierExiting = errors.New("TxNotifier is exiting")
60

61
        // ErrNoScript is an error returned when a confirmation/spend
62
        // registration is attempted without providing an accompanying output
63
        // script.
64
        ErrNoScript = errors.New("an output script must be provided")
65

66
        // ErrNoHeightHint is an error returned when a confirmation/spend
67
        // registration is attempted without providing an accompanying height
68
        // hint.
69
        ErrNoHeightHint = errors.New("a height hint greater than 0 must be " +
70
                "provided")
71

72
        // ErrNumConfsOutOfRange is an error returned when a confirmation/spend
73
        // registration is attempted and the number of confirmations provided is
74
        // out of range.
75
        ErrNumConfsOutOfRange = fmt.Errorf("number of confirmations must be "+
76
                "between %d and %d", 1, MaxNumConfs)
77

78
        // ErrEmptyWitnessStack is returned when a spending transaction has an
79
        // empty witness stack. More details in,
80
        // - https://github.com/bitcoin/bitcoin/issues/28730
81
        ErrEmptyWitnessStack = errors.New("witness stack is empty")
82
)
83

84
// rescanState indicates the progression of a registration before the notifier
85
// can begin dispatching confirmations at tip.
86
type rescanState byte
87

88
const (
89
        // rescanNotStarted is the initial state, denoting that a historical
90
        // dispatch may be required.
91
        rescanNotStarted rescanState = iota
92

93
        // rescanPending indicates that a dispatch has already been made, and we
94
        // are waiting for its completion. No other rescans should be dispatched
95
        // while in this state.
96
        rescanPending
97

98
        // rescanComplete signals either that a rescan was dispatched and has
99
        // completed, or that we began watching at tip immediately. In either
100
        // case, the notifier can only dispatch notifications from tip when in
101
        // this state.
102
        rescanComplete
103
)
104

105
// confNtfnSet holds all known, registered confirmation notifications for a
106
// txid/output script. If duplicates notifications are requested, only one
107
// historical dispatch will be spawned to ensure redundant scans are not
108
// permitted. A single conf detail will be constructed and dispatched to all
109
// interested
110
// clients.
111
type confNtfnSet struct {
112
        // ntfns keeps tracks of all the active client notification requests for
113
        // a transaction/output script
114
        ntfns map[uint64]*ConfNtfn
115

116
        // rescanStatus represents the current rescan state for the
117
        // transaction/output script.
118
        rescanStatus rescanState
119

120
        // details serves as a cache of the confirmation details of a
121
        // transaction that we'll use to determine if a transaction/output
122
        // script has already confirmed at the time of registration.
123
        // details is also used to make sure that in case of an address reuse
124
        // (funds sent to a previously confirmed script) no additional
125
        // notification is registered which would lead to an inconsistent state.
126
        details *TxConfirmation
127
}
128

129
// newConfNtfnSet constructs a fresh confNtfnSet for a group of clients
130
// interested in a notification for a particular txid.
131
func newConfNtfnSet() *confNtfnSet {
155✔
132
        return &confNtfnSet{
155✔
133
                ntfns:        make(map[uint64]*ConfNtfn),
155✔
134
                rescanStatus: rescanNotStarted,
155✔
135
        }
155✔
136
}
155✔
137

138
// spendNtfnSet holds all known, registered spend notifications for a spend
139
// request (outpoint/output script). If duplicate notifications are requested,
140
// only one historical dispatch will be spawned to ensure redundant scans are
141
// not permitted.
142
type spendNtfnSet struct {
143
        // ntfns keeps tracks of all the active client notification requests for
144
        // an outpoint/output script.
145
        ntfns map[uint64]*SpendNtfn
146

147
        // rescanStatus represents the current rescan state for the spend
148
        // request (outpoint/output script).
149
        rescanStatus rescanState
150

151
        // details serves as a cache of the spend details for an outpoint/output
152
        // script that we'll use to determine if it has already been spent at
153
        // the time of registration.
154
        details *SpendDetail
155
}
156

157
// newSpendNtfnSet constructs a new spend notification set.
158
func newSpendNtfnSet() *spendNtfnSet {
47✔
159
        return &spendNtfnSet{
47✔
160
                ntfns:        make(map[uint64]*SpendNtfn),
47✔
161
                rescanStatus: rescanNotStarted,
47✔
162
        }
47✔
163
}
47✔
164

165
// ConfRequest encapsulates a request for a confirmation notification of either
166
// a txid or output script.
167
type ConfRequest struct {
168
        // TxID is the hash of the transaction for which confirmation
169
        // notifications are requested. If set to a zero hash, then a
170
        // confirmation notification will be dispatched upon inclusion of the
171
        // _script_, rather than the txid.
172
        TxID chainhash.Hash
173

174
        // PkScript is the public key script of an outpoint created in this
175
        // transaction.
176
        PkScript txscript.PkScript
177
}
178

179
// NewConfRequest creates a request for a confirmation notification of either a
180
// txid or output script. A nil txid or an allocated ZeroHash can be used to
181
// dispatch the confirmation notification on the script.
182
func NewConfRequest(txid *chainhash.Hash, pkScript []byte) (ConfRequest, error) {
233✔
183
        var r ConfRequest
233✔
184
        outputScript, err := txscript.ParsePkScript(pkScript)
233✔
185
        if err != nil {
233✔
186
                return r, err
×
187
        }
×
188

189
        // We'll only set a txid for which we'll dispatch a confirmation
190
        // notification on this request if one was provided. Otherwise, we'll
191
        // default to dispatching on the confirmation of the script instead.
192
        if txid != nil {
372✔
193
                r.TxID = *txid
139✔
194
        }
139✔
195
        r.PkScript = outputScript
233✔
196

233✔
197
        return r, nil
233✔
198
}
199

200
// String returns the string representation of the ConfRequest.
201
func (r ConfRequest) String() string {
1✔
202
        if r.TxID != ZeroHash {
2✔
203
                return fmt.Sprintf("txid=%v", r.TxID)
1✔
204
        }
1✔
205
        return fmt.Sprintf("script=%v", r.PkScript)
×
206
}
207

208
// MatchesTx determines whether the given transaction satisfies the confirmation
209
// request. If the confirmation request is for a script, then we'll check all of
210
// the outputs of the transaction to determine if it matches. Otherwise, we'll
211
// match on the txid.
212
func (r ConfRequest) MatchesTx(tx *wire.MsgTx) bool {
214✔
213
        scriptMatches := func() bool {
354✔
214
                pkScript := r.PkScript.Script()
140✔
215
                for _, txOut := range tx.TxOut {
310✔
216
                        if bytes.Equal(txOut.PkScript, pkScript) {
223✔
217
                                return true
53✔
218
                        }
53✔
219
                }
220

221
                return false
87✔
222
        }
223

224
        if r.TxID != ZeroHash {
343✔
225
                return r.TxID == tx.TxHash() && scriptMatches()
129✔
226
        }
129✔
227

228
        return scriptMatches()
85✔
229
}
230

231
// ConfNtfn represents a notifier client's request to receive a notification
232
// once the target transaction/output script gets sufficient confirmations. The
233
// client is asynchronously notified via the ConfirmationEvent channels.
234
type ConfNtfn struct {
235
        // ConfID uniquely identifies the confirmation notification request for
236
        // the specified transaction/output script.
237
        ConfID uint64
238

239
        // ConfRequest represents either the txid or script we should detect
240
        // inclusion of within the chain.
241
        ConfRequest
242

243
        // NumConfirmations is the number of confirmations after which the
244
        // notification is to be sent.
245
        NumConfirmations uint32
246

247
        // Event contains references to the channels that the notifications are
248
        // to be sent over.
249
        Event *ConfirmationEvent
250

251
        // HeightHint is the minimum height in the chain that we expect to find
252
        // this txid.
253
        HeightHint uint32
254

255
        // dispatched is false if the confirmed notification has not been sent
256
        // yet.
257
        dispatched bool
258

259
        // includeBlock is true if the dispatched notification should also have
260
        // the block included with it.
261
        includeBlock bool
262

263
        // numConfsLeft is the number of confirmations left to be sent to the
264
        // subscriber.
265
        numConfsLeft uint32
266
}
267

268
// HistoricalConfDispatch parametrizes a manual rescan for a particular
269
// transaction/output script. The parameters include the start and end block
270
// heights specifying the range of blocks to scan.
271
type HistoricalConfDispatch struct {
272
        // ConfRequest represents either the txid or script we should detect
273
        // inclusion of within the chain.
274
        ConfRequest
275

276
        // StartHeight specifies the block height at which to begin the
277
        // historical rescan.
278
        StartHeight uint32
279

280
        // EndHeight specifies the last block height (inclusive) that the
281
        // historical scan should consider.
282
        EndHeight uint32
283
}
284

285
// ConfRegistration encompasses all of the information required for callers to
286
// retrieve details about a confirmation event.
287
type ConfRegistration struct {
288
        // Event contains references to the channels that the notifications are
289
        // to be sent over.
290
        Event *ConfirmationEvent
291

292
        // HistoricalDispatch, if non-nil, signals to the client who registered
293
        // the notification that they are responsible for attempting to manually
294
        // rescan blocks for the txid/output script between the start and end
295
        // heights.
296
        HistoricalDispatch *HistoricalConfDispatch
297

298
        // Height is the height of the TxNotifier at the time the confirmation
299
        // notification was registered. This can be used so that backends can
300
        // request to be notified of confirmations from this point forwards.
301
        Height uint32
302
}
303

304
// SpendRequest encapsulates a request for a spend notification of either an
305
// outpoint or output script.
306
type SpendRequest struct {
307
        // OutPoint is the outpoint for which a client has requested a spend
308
        // notification for. If set to a zero outpoint, then a spend
309
        // notification will be dispatched upon detecting the spend of the
310
        // _script_, rather than the outpoint.
311
        OutPoint wire.OutPoint
312

313
        // PkScript is the script of the outpoint. If a zero outpoint is set,
314
        // then this can be an arbitrary script.
315
        PkScript txscript.PkScript
316
}
317

318
// NewSpendRequest creates a request for a spend notification of either an
319
// outpoint or output script. A nil outpoint or an allocated ZeroOutPoint can be
320
// used to dispatch the confirmation notification on the script.
321
func NewSpendRequest(op *wire.OutPoint, pkScript []byte) (SpendRequest, error) {
126✔
322
        var r SpendRequest
126✔
323
        outputScript, err := txscript.ParsePkScript(pkScript)
126✔
324
        if err != nil {
126✔
325
                return r, err
×
326
        }
×
327

328
        // We'll only set an outpoint for which we'll dispatch a spend
329
        // notification on this request if one was provided. Otherwise, we'll
330
        // default to dispatching on the spend of the script instead.
331
        if op != nil {
200✔
332
                r.OutPoint = *op
74✔
333
        }
74✔
334
        r.PkScript = outputScript
126✔
335

126✔
336
        // For Taproot spends we have the main problem that for the key spend
126✔
337
        // path we cannot derive the pkScript from only looking at the input's
126✔
338
        // witness. So we need to rely on the outpoint information alone.
126✔
339
        //
126✔
340
        // TODO(guggero): For script path spends we can derive the pkScript from
126✔
341
        // the witness, since we have the full control block and the spent
126✔
342
        // script available.
126✔
343
        if outputScript.Class() == txscript.WitnessV1TaprootTy {
127✔
344
                if op == nil {
2✔
345
                        return r, fmt.Errorf("cannot register witness v1 " +
1✔
346
                                "spend request without outpoint")
1✔
347
                }
1✔
348

349
                // We have an outpoint, so we can set the pkScript to an all
350
                // zero Taproot key that we'll compare this spend request to.
351
                r.PkScript = ZeroTaprootPkScript
1✔
352
        }
353

354
        return r, nil
126✔
355
}
356

357
// String returns the string representation of the SpendRequest.
358
func (r SpendRequest) String() string {
2✔
359
        if r.OutPoint != ZeroOutPoint {
4✔
360
                return fmt.Sprintf("outpoint=%v, script=%v", r.OutPoint,
2✔
361
                        r.PkScript)
2✔
362
        }
2✔
363
        return fmt.Sprintf("outpoint=<zero>, script=%v", r.PkScript)
×
364
}
365

366
// MatchesTx determines whether the given transaction satisfies the spend
367
// request. If the spend request is for an outpoint, then we'll check all of
368
// the outputs being spent by the inputs of the transaction to determine if it
369
// matches. Otherwise, we'll need to match on the output script being spent, so
370
// we'll recompute it for each input of the transaction to determine if it
371
// matches.
372
func (r SpendRequest) MatchesTx(tx *wire.MsgTx) (bool, uint32, error) {
8✔
373
        if r.OutPoint != ZeroOutPoint {
12✔
374
                for i, txIn := range tx.TxIn {
8✔
375
                        if txIn.PreviousOutPoint == r.OutPoint {
4✔
376
                                return true, uint32(i), nil
×
377
                        }
×
378
                }
379

380
                return false, 0, nil
4✔
381
        }
382

383
        for i, txIn := range tx.TxIn {
8✔
384
                pkScript, err := txscript.ComputePkScript(
4✔
385
                        txIn.SignatureScript, txIn.Witness,
4✔
386
                )
4✔
387
                if err == txscript.ErrUnsupportedScriptType {
4✔
388
                        continue
×
389
                }
390
                if err != nil {
4✔
391
                        return false, 0, err
×
392
                }
×
393

394
                if bytes.Equal(pkScript.Script(), r.PkScript.Script()) {
4✔
395
                        return true, uint32(i), nil
×
396
                }
×
397
        }
398

399
        return false, 0, nil
4✔
400
}
401

402
// SpendNtfn represents a client's request to receive a notification once an
403
// outpoint/output script has been spent on-chain. The client is asynchronously
404
// notified via the SpendEvent channels.
405
type SpendNtfn struct {
406
        // SpendID uniquely identies the spend notification request for the
407
        // specified outpoint/output script.
408
        SpendID uint64
409

410
        // SpendRequest represents either the outpoint or script we should
411
        // detect the spend of.
412
        SpendRequest
413

414
        // Event contains references to the channels that the notifications are
415
        // to be sent over.
416
        Event *SpendEvent
417

418
        // HeightHint is the earliest height in the chain that we expect to find
419
        // the spending transaction of the specified outpoint/output script.
420
        // This value will be overridden by the spend hint cache if it contains
421
        // an entry for it.
422
        HeightHint uint32
423

424
        // dispatched signals whether a spend notification has been dispatched
425
        // to the client.
426
        dispatched bool
427
}
428

429
// HistoricalSpendDispatch parametrizes a manual rescan to determine the
430
// spending details (if any) of an outpoint/output script. The parameters
431
// include the start and end block heights specifying the range of blocks to
432
// scan.
433
type HistoricalSpendDispatch struct {
434
        // SpendRequest represents either the outpoint or script we should
435
        // detect the spend of.
436
        SpendRequest
437

438
        // StartHeight specified the block height at which to begin the
439
        // historical rescan.
440
        StartHeight uint32
441

442
        // EndHeight specifies the last block height (inclusive) that the
443
        // historical rescan should consider.
444
        EndHeight uint32
445
}
446

447
// SpendRegistration encompasses all of the information required for callers to
448
// retrieve details about a spend event.
449
type SpendRegistration struct {
450
        // Event contains references to the channels that the notifications are
451
        // to be sent over.
452
        Event *SpendEvent
453

454
        // HistoricalDispatch, if non-nil, signals to the client who registered
455
        // the notification that they are responsible for attempting to manually
456
        // rescan blocks for the txid/output script between the start and end
457
        // heights.
458
        HistoricalDispatch *HistoricalSpendDispatch
459

460
        // Height is the height of the TxNotifier at the time the spend
461
        // notification was registered. This can be used so that backends can
462
        // request to be notified of spends from this point forwards.
463
        Height uint32
464
}
465

466
// TxNotifier is a struct responsible for delivering transaction notifications
467
// to subscribers. These notifications can be of two different types:
468
// transaction/output script confirmations and/or outpoint/output script spends.
469
// The TxNotifier will watch the blockchain as new blocks come in, in order to
470
// satisfy its client requests.
471
type TxNotifier struct {
472
        confClientCounter  uint64 // To be used atomically.
473
        spendClientCounter uint64 // To be used atomically.
474

475
        // currentHeight is the height of the tracked blockchain. It is used to
476
        // determine the number of confirmations a tx has and ensure blocks are
477
        // connected and disconnected in order.
478
        currentHeight uint32
479

480
        // reorgSafetyLimit is the chain depth beyond which it is assumed a
481
        // block will not be reorganized out of the chain. This is used to
482
        // determine when to prune old notification requests so that reorgs are
483
        // handled correctly. The coinbase maturity period is a reasonable value
484
        // to use.
485
        reorgSafetyLimit uint32
486

487
        // reorgDepth is the depth of a chain organization that this system is
488
        // being informed of. This is incremented as long as a sequence of
489
        // blocks are disconnected without being interrupted by a new block.
490
        reorgDepth uint32
491

492
        // confNotifications is an index of confirmation notification requests
493
        // by transaction hash/output script.
494
        confNotifications map[ConfRequest]*confNtfnSet
495

496
        // confsByInitialHeight is an index of watched transactions/output
497
        // scripts by the height that they are included at in the chain. This
498
        // is tracked so that incorrect notifications are not sent if a
499
        // transaction/output script is reorged out of the chain and so that
500
        // negative confirmations can be recognized.
501
        confsByInitialHeight map[uint32]map[ConfRequest]struct{}
502

503
        // ntfnsByConfirmHeight is an index of notification requests by the
504
        // height at which the transaction/output script will have sufficient
505
        // confirmations.
506
        ntfnsByConfirmHeight map[uint32]map[*ConfNtfn]struct{}
507

508
        // spendNotifications is an index of all active notification requests
509
        // per outpoint/output script.
510
        spendNotifications map[SpendRequest]*spendNtfnSet
511

512
        // spendsByHeight is an index that keeps tracks of the spending height
513
        // of outpoints/output scripts we are currently tracking notifications
514
        // for. This is used in order to recover from spending transactions
515
        // being reorged out of the chain.
516
        spendsByHeight map[uint32]map[SpendRequest]struct{}
517

518
        // confirmHintCache is a cache used to maintain the latest height hints
519
        // for transactions/output scripts. Each height hint represents the
520
        // earliest height at which they scripts could have been confirmed
521
        // within the chain.
522
        confirmHintCache ConfirmHintCache
523

524
        // spendHintCache is a cache used to maintain the latest height hints
525
        // for outpoints/output scripts. Each height hint represents the
526
        // earliest height at which they could have been spent within the chain.
527
        spendHintCache SpendHintCache
528

529
        // quit is closed in order to signal that the notifier is gracefully
530
        // exiting.
531
        quit chan struct{}
532

533
        sync.Mutex
534
}
535

536
// NewTxNotifier creates a TxNotifier. The current height of the blockchain is
537
// accepted as a parameter. The different hint caches (confirm and spend) are
538
// used as an optimization in order to retrieve a better starting point when
539
// dispatching a rescan for a historical event in the chain.
540
func NewTxNotifier(startHeight uint32, reorgSafetyLimit uint32,
541
        confirmHintCache ConfirmHintCache,
542
        spendHintCache SpendHintCache) *TxNotifier {
48✔
543

48✔
544
        return &TxNotifier{
48✔
545
                currentHeight:        startHeight,
48✔
546
                reorgSafetyLimit:     reorgSafetyLimit,
48✔
547
                confNotifications:    make(map[ConfRequest]*confNtfnSet),
48✔
548
                confsByInitialHeight: make(map[uint32]map[ConfRequest]struct{}),
48✔
549
                ntfnsByConfirmHeight: make(map[uint32]map[*ConfNtfn]struct{}),
48✔
550
                spendNotifications:   make(map[SpendRequest]*spendNtfnSet),
48✔
551
                spendsByHeight:       make(map[uint32]map[SpendRequest]struct{}),
48✔
552
                confirmHintCache:     confirmHintCache,
48✔
553
                spendHintCache:       spendHintCache,
48✔
554
                quit:                 make(chan struct{}),
48✔
555
        }
48✔
556
}
48✔
557

558
// newConfNtfn validates all of the parameters required to successfully create
559
// and register a confirmation notification.
560
func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash,
561
        pkScript []byte, numConfs, heightHint uint32,
562
        opts *notifierOptions) (*ConfNtfn, error) {
225✔
563

225✔
564
        // An accompanying output script must always be provided.
225✔
565
        if len(pkScript) == 0 {
226✔
566
                return nil, ErrNoScript
1✔
567
        }
1✔
568

569
        // Enforce that we will not dispatch confirmations beyond the reorg
570
        // safety limit.
571
        if numConfs == 0 || numConfs > n.reorgSafetyLimit {
226✔
572
                return nil, ErrNumConfsOutOfRange
2✔
573
        }
2✔
574

575
        // A height hint must be provided to prevent scanning from the genesis
576
        // block.
577
        if heightHint == 0 {
223✔
578
                return nil, ErrNoHeightHint
1✔
579
        }
1✔
580

581
        // Ensure the output script is of a supported type.
582
        confRequest, err := NewConfRequest(txid, pkScript)
221✔
583
        if err != nil {
221✔
584
                return nil, err
×
585
        }
×
586

587
        confID := atomic.AddUint64(&n.confClientCounter, 1)
221✔
588
        return &ConfNtfn{
221✔
589
                ConfID:           confID,
221✔
590
                ConfRequest:      confRequest,
221✔
591
                NumConfirmations: numConfs,
221✔
592
                Event: NewConfirmationEvent(numConfs, func() {
225✔
593
                        n.CancelConf(confRequest, confID)
4✔
594
                }),
4✔
595
                HeightHint:   heightHint,
596
                includeBlock: opts.includeBlock,
597
                numConfsLeft: numConfs,
598
        }, nil
599
}
600

601
// RegisterConf handles a new confirmation notification request. The client will
602
// be notified when the transaction/output script gets a sufficient number of
603
// confirmations in the blockchain.
604
//
605
// NOTE: If the transaction/output script has already been included in a block
606
// on the chain, the confirmation details must be provided with the
607
// UpdateConfDetails method, otherwise we will wait for the transaction/output
608
// script to confirm even though it already has.
609
func (n *TxNotifier) RegisterConf(txid *chainhash.Hash, pkScript []byte,
610
        numConfs, heightHint uint32,
611
        optFuncs ...NotifierOption) (*ConfRegistration, error) {
226✔
612

226✔
613
        select {
226✔
614
        case <-n.quit:
1✔
615
                return nil, ErrTxNotifierExiting
1✔
616
        default:
225✔
617
        }
618

619
        opts := defaultNotifierOptions()
225✔
620
        for _, optFunc := range optFuncs {
278✔
621
                optFunc(opts)
53✔
622
        }
53✔
623

624
        // We'll start by performing a series of validation checks.
625
        ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint, opts)
225✔
626
        if err != nil {
229✔
627
                return nil, err
4✔
628
        }
4✔
629

630
        // Before proceeding to register the notification, we'll query our
631
        // height hint cache to determine whether a better one exists.
632
        //
633
        // TODO(conner): verify that all submitted height hints are identical.
634
        startHeight := ntfn.HeightHint
221✔
635
        hint, err := n.confirmHintCache.QueryConfirmHint(ntfn.ConfRequest)
221✔
636
        if err == nil {
244✔
637
                if hint > startHeight {
43✔
638
                        Log.Debugf("Using height hint %d retrieved from cache "+
20✔
639
                                "for %v instead of %d for conf subscription",
20✔
640
                                hint, ntfn.ConfRequest, startHeight)
20✔
641
                        startHeight = hint
20✔
642
                }
20✔
643
        } else if err != ErrConfirmHintNotFound {
199✔
644
                Log.Errorf("Unable to query confirm hint for %v: %v",
×
645
                        ntfn.ConfRequest, err)
×
646
        }
×
647

648
        Log.Infof("New confirmation subscription: conf_id=%d, %v, "+
221✔
649
                "num_confs=%v height_hint=%d", ntfn.ConfID, ntfn.ConfRequest,
221✔
650
                numConfs, startHeight)
221✔
651

221✔
652
        n.Lock()
221✔
653
        defer n.Unlock()
221✔
654

221✔
655
        confSet, ok := n.confNotifications[ntfn.ConfRequest]
221✔
656
        if !ok {
376✔
657
                // If this is the first registration for this request, construct
155✔
658
                // a confSet to coalesce all notifications for the same request.
155✔
659
                confSet = newConfNtfnSet()
155✔
660
                n.confNotifications[ntfn.ConfRequest] = confSet
155✔
661
        }
155✔
662
        confSet.ntfns[ntfn.ConfID] = ntfn
221✔
663

221✔
664
        switch confSet.rescanStatus {
221✔
665

666
        // A prior rescan has already completed and we are actively watching at
667
        // tip for this request.
668
        case rescanComplete:
25✔
669
                // If the confirmation details for this set of notifications has
25✔
670
                // already been found, we'll attempt to deliver them immediately
25✔
671
                // to this client.
25✔
672
                Log.Debugf("Attempting to dispatch confirmation for %v on "+
25✔
673
                        "registration since rescan has finished, conf_id=%v",
25✔
674
                        ntfn.ConfRequest, ntfn.ConfID)
25✔
675

25✔
676
                // The default notification we assigned above includes the
25✔
677
                // block along with the rest of the details. However not all
25✔
678
                // clients want the block, so we make a copy here w/o the block
25✔
679
                // if needed so we can give clients only what they ask for.
25✔
680
                confDetails := confSet.details
25✔
681
                if !ntfn.includeBlock && confDetails != nil {
37✔
682
                        confDetailsCopy := *confDetails
12✔
683
                        confDetailsCopy.Block = nil
12✔
684

12✔
685
                        confDetails = &confDetailsCopy
12✔
686
                }
12✔
687

688
                // Deliver the details to the whole conf set where this ntfn
689
                // lives in.
690
                for _, subscriber := range confSet.ntfns {
114✔
691
                        err := n.dispatchConfDetails(subscriber, confDetails)
89✔
692
                        if err != nil {
89✔
693
                                return nil, err
×
694
                        }
×
695
                }
696

697
                return &ConfRegistration{
25✔
698
                        Event:              ntfn.Event,
25✔
699
                        HistoricalDispatch: nil,
25✔
700
                        Height:             n.currentHeight,
25✔
701
                }, nil
25✔
702

703
        // A rescan is already in progress, return here to prevent dispatching
704
        // another. When the rescan returns, this notification's details will be
705
        // updated as well.
706
        case rescanPending:
43✔
707
                Log.Debugf("Waiting for pending rescan to finish before "+
43✔
708
                        "notifying %v at tip", ntfn.ConfRequest)
43✔
709

43✔
710
                return &ConfRegistration{
43✔
711
                        Event:              ntfn.Event,
43✔
712
                        HistoricalDispatch: nil,
43✔
713
                        Height:             n.currentHeight,
43✔
714
                }, nil
43✔
715

716
        // If no rescan has been dispatched, attempt to do so now.
717
        case rescanNotStarted:
155✔
718
        }
719

720
        // If the provided or cached height hint indicates that the
721
        // transaction with the given txid/output script is to be confirmed at a
722
        // height greater than the notifier's current height, we'll refrain from
723
        // spawning a historical dispatch.
724
        if startHeight > n.currentHeight {
161✔
725
                Log.Debugf("Height hint is above current height, not "+
6✔
726
                        "dispatching historical confirmation rescan for %v",
6✔
727
                        ntfn.ConfRequest)
6✔
728

6✔
729
                // Set the rescan status to complete, which will allow the
6✔
730
                // notifier to start delivering messages for this set
6✔
731
                // immediately.
6✔
732
                confSet.rescanStatus = rescanComplete
6✔
733
                return &ConfRegistration{
6✔
734
                        Event:              ntfn.Event,
6✔
735
                        HistoricalDispatch: nil,
6✔
736
                        Height:             n.currentHeight,
6✔
737
                }, nil
6✔
738
        }
6✔
739

740
        Log.Debugf("Dispatching historical confirmation rescan for %v",
150✔
741
                ntfn.ConfRequest)
150✔
742

150✔
743
        // Construct the parameters for historical dispatch, scanning the range
150✔
744
        // of blocks between our best known height hint and the notifier's
150✔
745
        // current height. The notifier will begin also watching for
150✔
746
        // confirmations at tip starting with the next block.
150✔
747
        dispatch := &HistoricalConfDispatch{
150✔
748
                ConfRequest: ntfn.ConfRequest,
150✔
749
                StartHeight: startHeight,
150✔
750
                EndHeight:   n.currentHeight,
150✔
751
        }
150✔
752

150✔
753
        // Set this confSet's status to pending, ensuring subsequent
150✔
754
        // registrations don't also attempt a dispatch.
150✔
755
        confSet.rescanStatus = rescanPending
150✔
756

150✔
757
        return &ConfRegistration{
150✔
758
                Event:              ntfn.Event,
150✔
759
                HistoricalDispatch: dispatch,
150✔
760
                Height:             n.currentHeight,
150✔
761
        }, nil
150✔
762
}
763

764
// CancelConf cancels an existing request for a spend notification of an
765
// outpoint/output script. The request is identified by its spend ID.
766
func (n *TxNotifier) CancelConf(confRequest ConfRequest, confID uint64) {
4✔
767
        select {
4✔
768
        case <-n.quit:
×
769
                return
×
770
        default:
4✔
771
        }
772

773
        n.Lock()
4✔
774
        defer n.Unlock()
4✔
775

4✔
776
        confSet, ok := n.confNotifications[confRequest]
4✔
777
        if !ok {
4✔
778
                return
×
779
        }
×
780
        ntfn, ok := confSet.ntfns[confID]
4✔
781
        if !ok {
4✔
782
                return
×
783
        }
×
784

785
        Log.Debugf("Canceling confirmation notification: conf_id=%d, %v",
4✔
786
                confID, confRequest)
4✔
787

4✔
788
        // We'll close all the notification channels to let the client know
4✔
789
        // their cancel request has been fulfilled.
4✔
790
        close(ntfn.Event.Confirmed)
4✔
791
        close(ntfn.Event.Updates)
4✔
792
        close(ntfn.Event.NegativeConf)
4✔
793

4✔
794
        // Finally, we'll clean up any lingering references to this
4✔
795
        // notification.
4✔
796
        delete(confSet.ntfns, confID)
4✔
797

4✔
798
        // Remove the queued confirmation notification if the transaction has
4✔
799
        // already confirmed, but hasn't met its required number of
4✔
800
        // confirmations.
4✔
801
        if confSet.details != nil {
7✔
802
                confHeight := confSet.details.BlockHeight +
3✔
803
                        ntfn.NumConfirmations - 1
3✔
804
                delete(n.ntfnsByConfirmHeight[confHeight], ntfn)
3✔
805
        }
3✔
806
}
807

808
// UpdateConfDetails attempts to update the confirmation details for an active
809
// notification within the notifier. This should only be used in the case of a
810
// transaction/output script that has confirmed before the notifier's current
811
// height.
812
//
813
// NOTE: The notification should be registered first to ensure notifications are
814
// dispatched correctly.
815
func (n *TxNotifier) UpdateConfDetails(confRequest ConfRequest,
816
        details *TxConfirmation) error {
141✔
817

141✔
818
        select {
141✔
819
        case <-n.quit:
×
820
                return ErrTxNotifierExiting
×
821
        default:
141✔
822
        }
823

824
        // Ensure we hold the lock throughout handling the notification to
825
        // prevent the notifier from advancing its height underneath us.
826
        n.Lock()
141✔
827
        defer n.Unlock()
141✔
828

141✔
829
        // First, we'll determine whether we have an active confirmation
141✔
830
        // notification for the given txid/script.
141✔
831
        confSet, ok := n.confNotifications[confRequest]
141✔
832
        if !ok {
141✔
833
                return fmt.Errorf("confirmation notification for %v not found",
×
834
                        confRequest)
×
835
        }
×
836

837
        // If the confirmation details were already found at tip, all existing
838
        // notifications will have been dispatched or queued for dispatch. We
839
        // can exit early to avoid sending too many notifications on the
840
        // buffered channels.
841
        if confSet.details != nil {
143✔
842
                return nil
2✔
843
        }
2✔
844

845
        // The historical dispatch has been completed for this confSet. We'll
846
        // update the rescan status and cache any details that were found. If
847
        // the details are nil, that implies we did not find them and will
848
        // continue to watch for them at tip.
849
        confSet.rescanStatus = rescanComplete
139✔
850

139✔
851
        // The notifier has yet to reach the height at which the
139✔
852
        // transaction/output script was included in a block, so we should defer
139✔
853
        // until handling it then within ConnectTip.
139✔
854
        if details == nil {
255✔
855
                Log.Debugf("Confirmation details for %v not found during "+
116✔
856
                        "historical dispatch, waiting to dispatch at tip",
116✔
857
                        confRequest)
116✔
858

116✔
859
                // We'll commit the current height as the confirm hint to
116✔
860
                // prevent another potentially long rescan if we restart before
116✔
861
                // a new block comes in.
116✔
862
                err := n.confirmHintCache.CommitConfirmHint(
116✔
863
                        n.currentHeight, confRequest,
116✔
864
                )
116✔
865
                if err != nil {
116✔
866
                        // The error is not fatal as this is an optimistic
×
867
                        // optimization, so we'll avoid returning an error.
×
868
                        Log.Debugf("Unable to update confirm hint to %d for "+
×
869
                                "%v: %v", n.currentHeight, confRequest, err)
×
870
                }
×
871

872
                return nil
116✔
873
        }
874

875
        if details.BlockHeight > n.currentHeight {
27✔
876
                Log.Debugf("Confirmation details for %v found above current "+
3✔
877
                        "height, waiting to dispatch at tip", confRequest)
3✔
878

3✔
879
                return nil
3✔
880
        }
3✔
881

882
        Log.Debugf("Updating confirmation details for %v", confRequest)
21✔
883

21✔
884
        err := n.confirmHintCache.CommitConfirmHint(
21✔
885
                details.BlockHeight, confRequest,
21✔
886
        )
21✔
887
        if err != nil {
21✔
888
                // The error is not fatal, so we should not return an error to
×
889
                // the caller.
×
890
                Log.Errorf("Unable to update confirm hint to %d for %v: %v",
×
891
                        details.BlockHeight, confRequest, err)
×
892
        }
×
893

894
        // Cache the details found in the rescan and attempt to dispatch any
895
        // notifications that have not yet been delivered.
896
        confSet.details = details
21✔
897
        for _, ntfn := range confSet.ntfns {
47✔
898
                // The default notification we assigned above includes the
26✔
899
                // block along with the rest of the details. However not all
26✔
900
                // clients want the block, so we make a copy here w/o the block
26✔
901
                // if needed so we can give clients only what they ask for.
26✔
902
                confDetails := *details
26✔
903
                if !ntfn.includeBlock {
44✔
904
                        confDetails.Block = nil
18✔
905
                }
18✔
906

907
                err = n.dispatchConfDetails(ntfn, &confDetails)
26✔
908
                if err != nil {
26✔
909
                        return err
×
910
                }
×
911
        }
912

913
        return nil
21✔
914
}
915

916
// dispatchConfDetails attempts to cache and dispatch details to a particular
917
// client if the transaction/output script has sufficiently confirmed. If the
918
// provided details are nil, this method will be a no-op.
919
func (n *TxNotifier) dispatchConfDetails(
920
        ntfn *ConfNtfn, details *TxConfirmation) error {
114✔
921

114✔
922
        // If there are no conf details to dispatch or if the notification has
114✔
923
        // already been dispatched, then we can skip dispatching to this
114✔
924
        // client.
114✔
925
        if details == nil {
136✔
926
                Log.Debugf("Skipped dispatching nil conf details for request "+
22✔
927
                        "%v, conf_id=%v", ntfn.ConfRequest, ntfn.ConfID)
22✔
928

22✔
929
                return nil
22✔
930
        }
22✔
931

932
        if ntfn.dispatched {
142✔
933
                Log.Debugf("Skipped dispatched conf details for request %v "+
49✔
934
                        "conf_id=%v", ntfn.ConfRequest, ntfn.ConfID)
49✔
935

49✔
936
                return nil
49✔
937
        }
49✔
938

939
        // Now, we'll examine whether the transaction/output script of this
940
        // request has reached its required number of confirmations. If it has,
941
        // we'll dispatch a confirmation notification to the caller.
942
        confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
45✔
943
        if confHeight <= n.currentHeight {
81✔
944
                Log.Debugf("Dispatching %v confirmation notification for "+
36✔
945
                        "conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
36✔
946
                        ntfn.ConfRequest)
36✔
947

36✔
948
                // We'll send a 0 value to the Updates channel,
36✔
949
                // indicating that the transaction/output script has already
36✔
950
                // been confirmed.
36✔
951
                err := n.notifyNumConfsLeft(ntfn, 0)
36✔
952
                if err != nil {
36✔
953
                        return err
×
954
                }
×
955

956
                select {
36✔
957
                case ntfn.Event.Confirmed <- details:
36✔
958
                        ntfn.dispatched = true
36✔
959
                case <-n.quit:
×
960
                        return ErrTxNotifierExiting
×
961
                }
962
        } else {
10✔
963
                Log.Debugf("Queueing %v confirmation notification for %v at "+
10✔
964
                        "tip", ntfn.NumConfirmations, ntfn.ConfRequest)
10✔
965

10✔
966
                // Otherwise, we'll keep track of the notification
10✔
967
                // request by the height at which we should dispatch the
10✔
968
                // confirmation notification.
10✔
969
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
10✔
970
                if !exists {
20✔
971
                        ntfnSet = make(map[*ConfNtfn]struct{})
10✔
972
                        n.ntfnsByConfirmHeight[confHeight] = ntfnSet
10✔
973
                }
10✔
974
                ntfnSet[ntfn] = struct{}{}
10✔
975

10✔
976
                // We'll also send an update to the client of how many
10✔
977
                // confirmations are left for the transaction/output script to
10✔
978
                // be confirmed.
10✔
979
                numConfsLeft := confHeight - n.currentHeight
10✔
980
                err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
10✔
981
                if err != nil {
10✔
982
                        return err
×
983
                }
×
984
        }
985

986
        // As a final check, we'll also watch the transaction/output script if
987
        // it's still possible for it to get reorged out of the chain.
988
        reorgSafeHeight := details.BlockHeight + n.reorgSafetyLimit
45✔
989
        if reorgSafeHeight > n.currentHeight {
90✔
990
                txSet, exists := n.confsByInitialHeight[details.BlockHeight]
45✔
991
                if !exists {
57✔
992
                        txSet = make(map[ConfRequest]struct{})
12✔
993
                        n.confsByInitialHeight[details.BlockHeight] = txSet
12✔
994
                }
12✔
995
                txSet[ntfn.ConfRequest] = struct{}{}
45✔
996
        }
997

998
        return nil
45✔
999
}
1000

1001
// newSpendNtfn validates all of the parameters required to successfully create
1002
// and register a spend notification.
1003
func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint,
1004
        pkScript []byte, heightHint uint32) (*SpendNtfn, error) {
128✔
1005

128✔
1006
        // An accompanying output script must always be provided.
128✔
1007
        if len(pkScript) == 0 {
129✔
1008
                return nil, ErrNoScript
1✔
1009
        }
1✔
1010

1011
        // A height hint must be provided to prevent scanning from the genesis
1012
        // block.
1013
        if heightHint == 0 {
128✔
1014
                return nil, ErrNoHeightHint
1✔
1015
        }
1✔
1016

1017
        // Ensure the output script is of a supported type.
1018
        spendRequest, err := NewSpendRequest(outpoint, pkScript)
126✔
1019
        if err != nil {
127✔
1020
                return nil, err
1✔
1021
        }
1✔
1022

1023
        spendID := atomic.AddUint64(&n.spendClientCounter, 1)
126✔
1024
        return &SpendNtfn{
126✔
1025
                SpendID:      spendID,
126✔
1026
                SpendRequest: spendRequest,
126✔
1027
                Event: NewSpendEvent(func() {
135✔
1028
                        n.CancelSpend(spendRequest, spendID)
9✔
1029
                }),
9✔
1030
                HeightHint: heightHint,
1031
        }, nil
1032
}
1033

1034
// RegisterSpend handles a new spend notification request. The client will be
1035
// notified once the outpoint/output script is detected as spent within the
1036
// chain.
1037
//
1038
// NOTE: If the outpoint/output script has already been spent within the chain
1039
// before the notifier's current tip, the spend details must be provided with
1040
// the UpdateSpendDetails method, otherwise we will wait for the outpoint/output
1041
// script to be spent at tip, even though it already has.
1042
func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte,
1043
        heightHint uint32) (*SpendRegistration, error) {
129✔
1044

129✔
1045
        select {
129✔
1046
        case <-n.quit:
1✔
1047
                return nil, ErrTxNotifierExiting
1✔
1048
        default:
128✔
1049
        }
1050

1051
        // We'll start by performing a series of validation checks.
1052
        ntfn, err := n.newSpendNtfn(outpoint, pkScript, heightHint)
128✔
1053
        if err != nil {
131✔
1054
                return nil, err
3✔
1055
        }
3✔
1056

1057
        // Before proceeding to register the notification, we'll query our spend
1058
        // hint cache to determine whether a better one exists.
1059
        startHeight := ntfn.HeightHint
126✔
1060
        hint, err := n.spendHintCache.QuerySpendHint(ntfn.SpendRequest)
126✔
1061
        if err == nil {
158✔
1062
                if hint > startHeight {
52✔
1063
                        Log.Debugf("Using height hint %d retrieved from cache "+
20✔
1064
                                "for %v instead of %d for spend subscription",
20✔
1065
                                hint, ntfn.SpendRequest, startHeight)
20✔
1066
                        startHeight = hint
20✔
1067
                }
20✔
1068
        } else if err != ErrSpendHintNotFound {
95✔
1069
                Log.Errorf("Unable to query spend hint for %v: %v",
×
1070
                        ntfn.SpendRequest, err)
×
1071
        }
×
1072

1073
        n.Lock()
126✔
1074
        defer n.Unlock()
126✔
1075

126✔
1076
        Log.Debugf("New spend subscription: spend_id=%d, %v, height_hint=%d",
126✔
1077
                ntfn.SpendID, ntfn.SpendRequest, startHeight)
126✔
1078

126✔
1079
        // Keep track of the notification request so that we can properly
126✔
1080
        // dispatch a spend notification later on.
126✔
1081
        spendSet, ok := n.spendNotifications[ntfn.SpendRequest]
126✔
1082
        if !ok {
173✔
1083
                // If this is the first registration for the request, we'll
47✔
1084
                // construct a spendNtfnSet to coalesce all notifications.
47✔
1085
                spendSet = newSpendNtfnSet()
47✔
1086
                n.spendNotifications[ntfn.SpendRequest] = spendSet
47✔
1087
        }
47✔
1088
        spendSet.ntfns[ntfn.SpendID] = ntfn
126✔
1089

126✔
1090
        // We'll now let the caller know whether a historical rescan is needed
126✔
1091
        // depending on the current rescan status.
126✔
1092
        switch spendSet.rescanStatus {
126✔
1093

1094
        // If the spending details for this request have already been determined
1095
        // and cached, then we can use them to immediately dispatch the spend
1096
        // notification to the client.
1097
        case rescanComplete:
70✔
1098
                Log.Debugf("Attempting to dispatch spend for %v on "+
70✔
1099
                        "registration since rescan has finished",
70✔
1100
                        ntfn.SpendRequest)
70✔
1101

70✔
1102
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
70✔
1103
                if err != nil {
70✔
1104
                        return nil, err
×
1105
                }
×
1106

1107
                return &SpendRegistration{
70✔
1108
                        Event:              ntfn.Event,
70✔
1109
                        HistoricalDispatch: nil,
70✔
1110
                        Height:             n.currentHeight,
70✔
1111
                }, nil
70✔
1112

1113
        // If there is an active rescan to determine whether the request has
1114
        // been spent, then we won't trigger another one.
1115
        case rescanPending:
11✔
1116
                Log.Debugf("Waiting for pending rescan to finish before "+
11✔
1117
                        "notifying %v at tip", ntfn.SpendRequest)
11✔
1118

11✔
1119
                return &SpendRegistration{
11✔
1120
                        Event:              ntfn.Event,
11✔
1121
                        HistoricalDispatch: nil,
11✔
1122
                        Height:             n.currentHeight,
11✔
1123
                }, nil
11✔
1124

1125
        // Otherwise, we'll fall through and let the caller know that a rescan
1126
        // should be dispatched to determine whether the request has already
1127
        // been spent.
1128
        case rescanNotStarted:
47✔
1129
        }
1130

1131
        // However, if the spend hint, either provided by the caller or
1132
        // retrieved from the cache, is found to be at a later height than the
1133
        // TxNotifier is aware of, then we'll refrain from dispatching a
1134
        // historical rescan and wait for the spend to come in at tip.
1135
        if startHeight > n.currentHeight {
71✔
1136
                Log.Debugf("Spend hint of %d for %v is above current height %d",
24✔
1137
                        startHeight, ntfn.SpendRequest, n.currentHeight)
24✔
1138

24✔
1139
                // We'll also set the rescan status as complete to ensure that
24✔
1140
                // spend hints for this request get updated upon
24✔
1141
                // connected/disconnected blocks.
24✔
1142
                spendSet.rescanStatus = rescanComplete
24✔
1143
                return &SpendRegistration{
24✔
1144
                        Event:              ntfn.Event,
24✔
1145
                        HistoricalDispatch: nil,
24✔
1146
                        Height:             n.currentHeight,
24✔
1147
                }, nil
24✔
1148
        }
24✔
1149

1150
        // We'll set the rescan status to pending to ensure subsequent
1151
        // notifications don't also attempt a historical dispatch.
1152
        spendSet.rescanStatus = rescanPending
23✔
1153

23✔
1154
        Log.Debugf("Dispatching historical spend rescan for %v, start=%d, "+
23✔
1155
                "end=%d", ntfn.SpendRequest, startHeight, n.currentHeight)
23✔
1156

23✔
1157
        return &SpendRegistration{
23✔
1158
                Event: ntfn.Event,
23✔
1159
                HistoricalDispatch: &HistoricalSpendDispatch{
23✔
1160
                        SpendRequest: ntfn.SpendRequest,
23✔
1161
                        StartHeight:  startHeight,
23✔
1162
                        EndHeight:    n.currentHeight,
23✔
1163
                },
23✔
1164
                Height: n.currentHeight,
23✔
1165
        }, nil
23✔
1166
}
1167

1168
// CancelSpend cancels an existing request for a spend notification of an
1169
// outpoint/output script. The request is identified by its spend ID.
1170
func (n *TxNotifier) CancelSpend(spendRequest SpendRequest, spendID uint64) {
10✔
1171
        select {
10✔
1172
        case <-n.quit:
×
1173
                return
×
1174
        default:
10✔
1175
        }
1176

1177
        n.Lock()
10✔
1178
        defer n.Unlock()
10✔
1179

10✔
1180
        spendSet, ok := n.spendNotifications[spendRequest]
10✔
1181
        if !ok {
10✔
1182
                return
×
1183
        }
×
1184
        ntfn, ok := spendSet.ntfns[spendID]
10✔
1185
        if !ok {
10✔
1186
                return
×
1187
        }
×
1188

1189
        Log.Debugf("Canceling spend notification: spend_id=%d, %v", spendID,
10✔
1190
                spendRequest)
10✔
1191

10✔
1192
        // We'll close all the notification channels to let the client know
10✔
1193
        // their cancel request has been fulfilled.
10✔
1194
        close(ntfn.Event.Spend)
10✔
1195
        close(ntfn.Event.Reorg)
10✔
1196
        close(ntfn.Event.Done)
10✔
1197
        delete(spendSet.ntfns, spendID)
10✔
1198
}
1199

1200
// ProcessRelevantSpendTx processes a transaction provided externally. This will
1201
// check whether the transaction is relevant to the notifier if it spends any
1202
// outpoints/output scripts for which we currently have registered notifications
1203
// for. If it is relevant, spend notifications will be dispatched to the caller.
1204
func (n *TxNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx,
1205
        blockHeight uint32) error {
42✔
1206

42✔
1207
        select {
42✔
1208
        case <-n.quit:
×
1209
                return ErrTxNotifierExiting
×
1210
        default:
42✔
1211
        }
1212

1213
        // Ensure we hold the lock throughout handling the notification to
1214
        // prevent the notifier from advancing its height underneath us.
1215
        n.Lock()
42✔
1216
        defer n.Unlock()
42✔
1217

42✔
1218
        // We'll use a channel to coalesce all the spend requests that this
42✔
1219
        // transaction fulfills.
42✔
1220
        type spend struct {
42✔
1221
                request *SpendRequest
42✔
1222
                details *SpendDetail
42✔
1223
        }
42✔
1224

42✔
1225
        // We'll set up the onSpend filter callback to gather all the fulfilled
42✔
1226
        // spends requests within this transaction.
42✔
1227
        var spends []spend
42✔
1228
        onSpend := func(request SpendRequest, details *SpendDetail) {
78✔
1229
                spends = append(spends, spend{&request, details})
36✔
1230
        }
36✔
1231
        n.filterTx(nil, tx, blockHeight, nil, onSpend)
42✔
1232

42✔
1233
        // After the transaction has been filtered, we can finally dispatch
42✔
1234
        // notifications for each request.
42✔
1235
        for _, spend := range spends {
78✔
1236
                err := n.updateSpendDetails(*spend.request, spend.details)
36✔
1237
                if err != nil {
36✔
1238
                        return err
×
1239
                }
×
1240
        }
1241

1242
        return nil
42✔
1243
}
1244

1245
// UpdateSpendDetails attempts to update the spend details for all active spend
1246
// notification requests for an outpoint/output script. This method should be
1247
// used once a historical scan of the chain has finished. If the historical scan
1248
// did not find a spending transaction for it, the spend details may be nil.
1249
//
1250
// NOTE: A notification request for the outpoint/output script must be
1251
// registered first to ensure notifications are delivered.
1252
func (n *TxNotifier) UpdateSpendDetails(spendRequest SpendRequest,
1253
        details *SpendDetail) error {
14✔
1254

14✔
1255
        select {
14✔
1256
        case <-n.quit:
×
1257
                return ErrTxNotifierExiting
×
1258
        default:
14✔
1259
        }
1260

1261
        // Ensure we hold the lock throughout handling the notification to
1262
        // prevent the notifier from advancing its height underneath us.
1263
        n.Lock()
14✔
1264
        defer n.Unlock()
14✔
1265

14✔
1266
        return n.updateSpendDetails(spendRequest, details)
14✔
1267
}
1268

1269
// updateSpendDetails attempts to update the spend details for all active spend
1270
// notification requests for an outpoint/output script. This method should be
1271
// used once a historical scan of the chain has finished. If the historical scan
1272
// did not find a spending transaction for it, the spend details may be nil.
1273
//
1274
// NOTE: This method must be called with the TxNotifier's lock held.
1275
func (n *TxNotifier) updateSpendDetails(spendRequest SpendRequest,
1276
        details *SpendDetail) error {
49✔
1277

49✔
1278
        // Mark the ongoing historical rescan for this request as finished. This
49✔
1279
        // will allow us to update the spend hints for it at tip.
49✔
1280
        spendSet, ok := n.spendNotifications[spendRequest]
49✔
1281
        if !ok {
50✔
1282
                return fmt.Errorf("spend notification for %v not found",
1✔
1283
                        spendRequest)
1✔
1284
        }
1✔
1285

1286
        // If the spend details have already been found either at tip, then the
1287
        // notifications should have already been dispatched, so we can exit
1288
        // early to prevent sending duplicate notifications.
1289
        if spendSet.details != nil {
66✔
1290
                return nil
18✔
1291
        }
18✔
1292

1293
        // Since the historical rescan has completed for this request, we'll
1294
        // mark its rescan status as complete in order to ensure that the
1295
        // TxNotifier can properly update its spend hints upon
1296
        // connected/disconnected blocks.
1297
        spendSet.rescanStatus = rescanComplete
31✔
1298

31✔
1299
        // If the historical rescan was not able to find a spending transaction
31✔
1300
        // for this request, then we can track the spend at tip.
31✔
1301
        if details == nil {
37✔
1302
                // We'll commit the current height as the spend hint to prevent
6✔
1303
                // another potentially long rescan if we restart before a new
6✔
1304
                // block comes in.
6✔
1305
                err := n.spendHintCache.CommitSpendHint(
6✔
1306
                        n.currentHeight, spendRequest,
6✔
1307
                )
6✔
1308
                if err != nil {
6✔
1309
                        // The error is not fatal as this is an optimistic
×
1310
                        // optimization, so we'll avoid returning an error.
×
1311
                        Log.Debugf("Unable to update spend hint to %d for %v: %v",
×
1312
                                n.currentHeight, spendRequest, err)
×
1313
                }
×
1314

1315
                Log.Debugf("Updated spend hint to height=%v for unconfirmed "+
6✔
1316
                        "spend request %v", n.currentHeight, spendRequest)
6✔
1317
                return nil
6✔
1318
        }
1319

1320
        // Return an error if the witness data is not present in the spending
1321
        // transaction.
1322
        //
1323
        // NOTE: if the witness stack is empty, we will do a critical log which
1324
        // shuts down the node.
1325
        if !details.HasSpenderWitness() {
26✔
1326
                Log.Criticalf("Found spending tx for outpoint=%v, but the "+
×
1327
                        "transaction %v does not have witness",
×
1328
                        spendRequest.OutPoint, details.SpendingTx.TxHash())
×
1329

×
1330
                return ErrEmptyWitnessStack
×
1331
        }
×
1332

1333
        // If the historical rescan found the spending transaction for this
1334
        // request, but it's at a later height than the notifier (this can
1335
        // happen due to latency with the backend during a reorg), then we'll
1336
        // defer handling the notification until the notifier has caught up to
1337
        // such height.
1338
        if uint32(details.SpendingHeight) > n.currentHeight {
48✔
1339
                return nil
22✔
1340
        }
22✔
1341

1342
        // Now that we've determined the request has been spent, we'll commit
1343
        // its spending height as its hint in the cache and dispatch
1344
        // notifications to all of its respective clients.
1345
        err := n.spendHintCache.CommitSpendHint(
5✔
1346
                uint32(details.SpendingHeight), spendRequest,
5✔
1347
        )
5✔
1348
        if err != nil {
5✔
1349
                // The error is not fatal as this is an optimistic optimization,
×
1350
                // so we'll avoid returning an error.
×
1351
                Log.Debugf("Unable to update spend hint to %d for %v: %v",
×
1352
                        details.SpendingHeight, spendRequest, err)
×
1353
        }
×
1354

1355
        Log.Debugf("Updated spend hint to height=%v for confirmed spend "+
5✔
1356
                "request %v", details.SpendingHeight, spendRequest)
5✔
1357

5✔
1358
        spendSet.details = details
5✔
1359
        for _, ntfn := range spendSet.ntfns {
15✔
1360
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
10✔
1361
                if err != nil {
10✔
1362
                        return err
×
1363
                }
×
1364
        }
1365

1366
        return nil
5✔
1367
}
1368

1369
// dispatchSpendDetails dispatches a spend notification to the client.
1370
//
1371
// NOTE: This must be called with the TxNotifier's lock held.
1372
func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail) error {
175✔
1373
        // If there are no spend details to dispatch or if the notification has
175✔
1374
        // already been dispatched, then we can skip dispatching to this client.
175✔
1375
        if details == nil || ntfn.dispatched {
225✔
1376
                Log.Debugf("Skipping dispatch of spend details(%v) for "+
50✔
1377
                        "request %v, dispatched=%v", details, ntfn.SpendRequest,
50✔
1378
                        ntfn.dispatched)
50✔
1379
                return nil
50✔
1380
        }
50✔
1381

1382
        Log.Debugf("Dispatching confirmed spend notification for %v at "+
126✔
1383
                "current height=%d: %v", ntfn.SpendRequest, n.currentHeight,
126✔
1384
                details)
126✔
1385

126✔
1386
        select {
126✔
1387
        case ntfn.Event.Spend <- details:
126✔
1388
                ntfn.dispatched = true
126✔
1389
        case <-n.quit:
×
1390
                return ErrTxNotifierExiting
×
1391
        }
1392

1393
        spendHeight := uint32(details.SpendingHeight)
126✔
1394

126✔
1395
        // We also add to spendsByHeight to notify on chain reorgs.
126✔
1396
        reorgSafeHeight := spendHeight + n.reorgSafetyLimit
126✔
1397
        if reorgSafeHeight > n.currentHeight {
252✔
1398
                txSet, exists := n.spendsByHeight[spendHeight]
126✔
1399
                if !exists {
131✔
1400
                        txSet = make(map[SpendRequest]struct{})
5✔
1401
                        n.spendsByHeight[spendHeight] = txSet
5✔
1402
                }
5✔
1403
                txSet[ntfn.SpendRequest] = struct{}{}
126✔
1404
        }
1405

1406
        return nil
126✔
1407
}
1408

1409
// ConnectTip handles a new block extending the current chain. It will go
1410
// through every transaction and determine if it is relevant to any of its
1411
// clients. A transaction can be relevant in either of the following two ways:
1412
//
1413
//  1. One of the inputs in the transaction spends an outpoint/output script
1414
//     for which we currently have an active spend registration for.
1415
//
1416
//  2. The transaction has a txid or output script for which we currently have
1417
//     an active confirmation registration for.
1418
//
1419
// In the event that the transaction is relevant, a confirmation/spend
1420
// notification will be queued for dispatch to the relevant clients.
1421
// Confirmation notifications will only be dispatched for transactions/output
1422
// scripts that have met the required number of confirmations required by the
1423
// client.
1424
//
1425
// NOTE: In order to actually dispatch the relevant transaction notifications to
1426
// clients, NotifyHeight must be called with the same block height in order to
1427
// maintain correctness.
1428
func (n *TxNotifier) ConnectTip(block *btcutil.Block,
1429
        blockHeight uint32) error {
1,587✔
1430

1,587✔
1431
        select {
1,587✔
1432
        case <-n.quit:
×
1433
                return ErrTxNotifierExiting
×
1434
        default:
1,587✔
1435
        }
1436

1437
        n.Lock()
1,587✔
1438
        defer n.Unlock()
1,587✔
1439

1,587✔
1440
        if blockHeight != n.currentHeight+1 {
1,587✔
1441
                return fmt.Errorf("received blocks out of order: "+
×
1442
                        "current height=%d, new height=%d",
×
1443
                        n.currentHeight, blockHeight)
×
1444
        }
×
1445
        n.currentHeight++
1,587✔
1446
        n.reorgDepth = 0
1,587✔
1447

1,587✔
1448
        // First, we'll iterate over all the transactions found in this block to
1,587✔
1449
        // determine if it includes any relevant transactions to the TxNotifier.
1,587✔
1450
        if block != nil {
3,171✔
1451
                Log.Debugf("Filtering %d txns for %d spend requests at "+
1,584✔
1452
                        "height %d", len(block.Transactions()),
1,584✔
1453
                        len(n.spendNotifications), blockHeight)
1,584✔
1454

1,584✔
1455
                for _, tx := range block.Transactions() {
3,845✔
1456
                        n.filterTx(
2,261✔
1457
                                block, tx, blockHeight,
2,261✔
1458
                                n.handleConfDetailsAtTip,
2,261✔
1459
                                n.handleSpendDetailsAtTip,
2,261✔
1460
                        )
2,261✔
1461
                }
2,261✔
1462
        }
1463

1464
        // Now that we've determined which requests were confirmed and spent
1465
        // within the new block, we can update their entries in their respective
1466
        // caches, along with all of our unconfirmed and unspent requests.
1467
        n.updateHints(blockHeight)
1,587✔
1468

1,587✔
1469
        // Finally, we'll clear the entries from our set of notifications for
1,587✔
1470
        // requests that are no longer under the risk of being reorged out of
1,587✔
1471
        // the chain.
1,587✔
1472
        if blockHeight >= n.reorgSafetyLimit {
3,062✔
1473
                matureBlockHeight := blockHeight - n.reorgSafetyLimit
1,475✔
1474
                for confRequest := range n.confsByInitialHeight[matureBlockHeight] {
1,491✔
1475
                        confSet := n.confNotifications[confRequest]
16✔
1476
                        for _, ntfn := range confSet.ntfns {
33✔
1477
                                select {
17✔
1478
                                case ntfn.Event.Done <- struct{}{}:
17✔
1479
                                case <-n.quit:
×
1480
                                        return ErrTxNotifierExiting
×
1481
                                }
1482
                        }
1483

1484
                        delete(n.confNotifications, confRequest)
16✔
1485
                }
1486
                delete(n.confsByInitialHeight, matureBlockHeight)
1,475✔
1487

1,475✔
1488
                for spendRequest := range n.spendsByHeight[matureBlockHeight] {
1,477✔
1489
                        spendSet := n.spendNotifications[spendRequest]
2✔
1490
                        for _, ntfn := range spendSet.ntfns {
4✔
1491
                                select {
2✔
1492
                                case ntfn.Event.Done <- struct{}{}:
2✔
1493
                                case <-n.quit:
×
1494
                                        return ErrTxNotifierExiting
×
1495
                                }
1496
                        }
1497

1498
                        Log.Debugf("Deleting mature spend request %v at "+
2✔
1499
                                "height=%d", spendRequest, blockHeight)
2✔
1500
                        delete(n.spendNotifications, spendRequest)
2✔
1501
                }
1502
                delete(n.spendsByHeight, matureBlockHeight)
1,475✔
1503
        }
1504

1505
        return nil
1,587✔
1506
}
1507

1508
// filterTx determines whether the transaction spends or confirms any
1509
// outstanding pending requests. The onConf and onSpend callbacks can be used to
1510
// retrieve all the requests fulfilled by this transaction as they occur.
1511
func (n *TxNotifier) filterTx(block *btcutil.Block, tx *btcutil.Tx,
1512
        blockHeight uint32, onConf func(ConfRequest, *TxConfirmation),
1513
        onSpend func(SpendRequest, *SpendDetail)) {
2,302✔
1514

2,302✔
1515
        // In order to determine if this transaction is relevant to the
2,302✔
1516
        // notifier, we'll check its inputs for any outstanding spend
2,302✔
1517
        // requests.
2,302✔
1518
        txHash := tx.Hash()
2,302✔
1519
        if onSpend != nil {
4,604✔
1520
                // notifyDetails is a helper closure that will construct the
2,302✔
1521
                // spend details of a request and hand them off to the onSpend
2,302✔
1522
                // callback.
2,302✔
1523
                notifyDetails := func(spendRequest SpendRequest,
2,302✔
1524
                        prevOut wire.OutPoint, inputIdx uint32) {
2,388✔
1525

86✔
1526
                        Log.Debugf("Found spend of %v: spend_tx=%v, "+
86✔
1527
                                "block_height=%d", spendRequest, txHash,
86✔
1528
                                blockHeight)
86✔
1529

86✔
1530
                        onSpend(spendRequest, &SpendDetail{
86✔
1531
                                SpentOutPoint:     &prevOut,
86✔
1532
                                SpenderTxHash:     txHash,
86✔
1533
                                SpendingTx:        tx.MsgTx(),
86✔
1534
                                SpenderInputIndex: inputIdx,
86✔
1535
                                SpendingHeight:    int32(blockHeight),
86✔
1536
                        })
86✔
1537
                }
86✔
1538

1539
                for i, txIn := range tx.MsgTx().TxIn {
4,842✔
1540
                        // We'll re-derive the script of the output being spent
2,540✔
1541
                        // to determine if the inputs spends any registered
2,540✔
1542
                        // requests.
2,540✔
1543
                        prevOut := txIn.PreviousOutPoint
2,540✔
1544
                        pkScript, err := txscript.ComputePkScript(
2,540✔
1545
                                txIn.SignatureScript, txIn.Witness,
2,540✔
1546
                        )
2,540✔
1547
                        if err != nil {
2,540✔
1548
                                continue
×
1549
                        }
1550
                        spendRequest := SpendRequest{
2,540✔
1551
                                OutPoint: prevOut,
2,540✔
1552
                                PkScript: pkScript,
2,540✔
1553
                        }
2,540✔
1554

2,540✔
1555
                        // If we have any, we'll record their spend height so
2,540✔
1556
                        // that notifications get dispatched to the respective
2,540✔
1557
                        // clients.
2,540✔
1558
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,588✔
1559
                                notifyDetails(spendRequest, prevOut, uint32(i))
48✔
1560
                        }
48✔
1561

1562
                        // Now try with an empty taproot key pkScript, since we
1563
                        // cannot derive the spent pkScript directly from the
1564
                        // witness. But we have the outpoint, which should be
1565
                        // enough.
1566
                        spendRequest.PkScript = ZeroTaprootPkScript
2,540✔
1567
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,541✔
1568
                                notifyDetails(spendRequest, prevOut, uint32(i))
1✔
1569
                        }
1✔
1570

1571
                        // Restore the pkScript but try with a zero outpoint
1572
                        // instead (won't be possible for Taproot).
1573
                        spendRequest.PkScript = pkScript
2,540✔
1574
                        spendRequest.OutPoint = ZeroOutPoint
2,540✔
1575
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,578✔
1576
                                notifyDetails(spendRequest, prevOut, uint32(i))
38✔
1577
                        }
38✔
1578
                }
1579
        }
1580

1581
        // We'll also check its outputs to determine if there are any
1582
        // outstanding confirmation requests.
1583
        if onConf != nil {
4,563✔
1584
                // notifyDetails is a helper closure that will construct the
2,261✔
1585
                // confirmation details of a request and hand them off to the
2,261✔
1586
                // onConf callback.
2,261✔
1587
                notifyDetails := func(confRequest ConfRequest) {
2,401✔
1588
                        Log.Debugf("Found initial confirmation of %v: "+
140✔
1589
                                "height=%d, hash=%v", confRequest,
140✔
1590
                                blockHeight, block.Hash())
140✔
1591

140✔
1592
                        details := &TxConfirmation{
140✔
1593
                                Tx:          tx.MsgTx(),
140✔
1594
                                BlockHash:   block.Hash(),
140✔
1595
                                BlockHeight: blockHeight,
140✔
1596
                                TxIndex:     uint32(tx.Index()),
140✔
1597
                                Block:       block.MsgBlock(),
140✔
1598
                        }
140✔
1599

140✔
1600
                        onConf(confRequest, details)
140✔
1601
                }
140✔
1602

1603
                for _, txOut := range tx.MsgTx().TxOut {
5,532✔
1604
                        // We'll parse the script of the output to determine if
3,271✔
1605
                        // we have any registered requests for it or the
3,271✔
1606
                        // transaction itself.
3,271✔
1607
                        pkScript, err := txscript.ParsePkScript(txOut.PkScript)
3,271✔
1608
                        if err != nil {
3,414✔
1609
                                continue
143✔
1610
                        }
1611
                        confRequest := ConfRequest{
3,129✔
1612
                                TxID:     *txHash,
3,129✔
1613
                                PkScript: pkScript,
3,129✔
1614
                        }
3,129✔
1615

3,129✔
1616
                        // If we have any, we'll record their confirmed height
3,129✔
1617
                        // so that notifications get dispatched when they
3,129✔
1618
                        // reaches the clients' desired number of confirmations.
3,129✔
1619
                        if _, ok := n.confNotifications[confRequest]; ok {
3,204✔
1620
                                notifyDetails(confRequest)
75✔
1621
                        }
75✔
1622
                        confRequest.TxID = ZeroHash
3,129✔
1623
                        if _, ok := n.confNotifications[confRequest]; ok {
3,194✔
1624
                                notifyDetails(confRequest)
65✔
1625
                        }
65✔
1626
                }
1627
        }
1628
}
1629

1630
// handleConfDetailsAtTip tracks the confirmation height of the txid/output
1631
// script in order to properly dispatch a confirmation notification after
1632
// meeting each request's desired number of confirmations for all current and
1633
// future registered clients.
1634
func (n *TxNotifier) handleConfDetailsAtTip(confRequest ConfRequest,
1635
        details *TxConfirmation) {
140✔
1636

140✔
1637
        // TODO(wilmer): cancel pending historical rescans if any?
140✔
1638
        confSet := n.confNotifications[confRequest]
140✔
1639

140✔
1640
        // If we already have details for this request, we don't want to add it
140✔
1641
        // again since we have already dispatched notifications for it.
140✔
1642
        if confSet.details != nil {
144✔
1643
                Log.Warnf("Ignoring address reuse for %s at height %d.",
4✔
1644
                        confRequest, details.BlockHeight)
4✔
1645
                return
4✔
1646
        }
4✔
1647

1648
        confSet.rescanStatus = rescanComplete
137✔
1649
        confSet.details = details
137✔
1650

137✔
1651
        for _, ntfn := range confSet.ntfns {
315✔
1652
                // In the event that this notification was aware that the
178✔
1653
                // transaction/output script was reorged out of the chain, we'll
178✔
1654
                // consume the reorg notification if it hasn't been done yet
178✔
1655
                // already.
178✔
1656
                select {
178✔
1657
                case <-ntfn.Event.NegativeConf:
1✔
1658
                default:
178✔
1659
                }
1660

1661
                // We'll note this client's required number of confirmations so
1662
                // that we can notify them when expected.
1663
                confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
178✔
1664
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
178✔
1665
                if !exists {
322✔
1666
                        ntfnSet = make(map[*ConfNtfn]struct{})
144✔
1667
                        n.ntfnsByConfirmHeight[confHeight] = ntfnSet
144✔
1668
                }
144✔
1669
                ntfnSet[ntfn] = struct{}{}
178✔
1670
        }
1671

1672
        // We'll also note the initial confirmation height in order to correctly
1673
        // handle dispatching notifications when the transaction/output script
1674
        // gets reorged out of the chain.
1675
        txSet, exists := n.confsByInitialHeight[details.BlockHeight]
137✔
1676
        if !exists {
229✔
1677
                txSet = make(map[ConfRequest]struct{})
92✔
1678
                n.confsByInitialHeight[details.BlockHeight] = txSet
92✔
1679
        }
92✔
1680
        txSet[confRequest] = struct{}{}
137✔
1681
}
1682

1683
// handleSpendDetailsAtTip tracks the spend height of the outpoint/output script
1684
// in order to properly dispatch a spend notification for all current and future
1685
// registered clients.
1686
func (n *TxNotifier) handleSpendDetailsAtTip(spendRequest SpendRequest,
1687
        details *SpendDetail) {
51✔
1688

51✔
1689
        // TODO(wilmer): cancel pending historical rescans if any?
51✔
1690
        spendSet := n.spendNotifications[spendRequest]
51✔
1691
        spendSet.rescanStatus = rescanComplete
51✔
1692
        spendSet.details = details
51✔
1693

51✔
1694
        for _, ntfn := range spendSet.ntfns {
147✔
1695
                // In the event that this notification was aware that the
96✔
1696
                // spending transaction of its outpoint/output script was
96✔
1697
                // reorged out of the chain, we'll consume the reorg
96✔
1698
                // notification if it hasn't been done yet already.
96✔
1699
                select {
96✔
1700
                case <-ntfn.Event.Reorg:
×
1701
                default:
96✔
1702
                }
1703
        }
1704

1705
        // We'll note the spending height of the request in order to correctly
1706
        // handle dispatching notifications when the spending transactions gets
1707
        // reorged out of the chain.
1708
        spendHeight := uint32(details.SpendingHeight)
51✔
1709
        opSet, exists := n.spendsByHeight[spendHeight]
51✔
1710
        if !exists {
102✔
1711
                opSet = make(map[SpendRequest]struct{})
51✔
1712
                n.spendsByHeight[spendHeight] = opSet
51✔
1713
        }
51✔
1714
        opSet[spendRequest] = struct{}{}
51✔
1715

51✔
1716
        Log.Debugf("Spend request %v spent at tip=%d", spendRequest,
51✔
1717
                spendHeight)
51✔
1718
}
1719

1720
// NotifyHeight dispatches confirmation and spend notifications to the clients
1721
// who registered for a notification which has been fulfilled at the passed
1722
// height.
1723
func (n *TxNotifier) NotifyHeight(height uint32) error {
1,487✔
1724
        n.Lock()
1,487✔
1725
        defer n.Unlock()
1,487✔
1726

1,487✔
1727
        // First, we'll dispatch an update to all of the notification clients
1,487✔
1728
        // for our watched requests with the number of confirmations left at
1,487✔
1729
        // this new height.
1,487✔
1730
        for _, confRequests := range n.confsByInitialHeight {
9,057✔
1731
                for confRequest := range confRequests {
20,931✔
1732
                        confSet := n.confNotifications[confRequest]
13,361✔
1733
                        for _, ntfn := range confSet.ntfns {
30,074✔
1734
                                txConfHeight := confSet.details.BlockHeight +
16,713✔
1735
                                        ntfn.NumConfirmations - 1
16,713✔
1736
                                numConfsLeft := txConfHeight - height
16,713✔
1737

16,713✔
1738
                                // Since we don't clear notifications until
16,713✔
1739
                                // transactions/output scripts are no longer
16,713✔
1740
                                // under the risk of being reorganized out of
16,713✔
1741
                                // the chain, we'll skip sending updates for
16,713✔
1742
                                // those that have already been confirmed.
16,713✔
1743
                                if int32(numConfsLeft) < 0 {
32,741✔
1744
                                        continue
16,028✔
1745
                                }
1746

1747
                                err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
686✔
1748
                                if err != nil {
686✔
1749
                                        return err
×
1750
                                }
×
1751
                        }
1752
                }
1753
        }
1754

1755
        // Then, we'll dispatch notifications for all the requests that have
1756
        // become confirmed at this new block height.
1757
        for ntfn := range n.ntfnsByConfirmHeight[height] {
1,662✔
1758
                confSet := n.confNotifications[ntfn.ConfRequest]
175✔
1759

175✔
1760
                Log.Debugf("Dispatching %v confirmation notification for "+
175✔
1761
                        "conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
175✔
1762
                        ntfn.ConfRequest)
175✔
1763

175✔
1764
                // The default notification we assigned above includes the
175✔
1765
                // block along with the rest of the details. However not all
175✔
1766
                // clients want the block, so we make a copy here w/o the block
175✔
1767
                // if needed so we can give clients only what they ask for.
175✔
1768
                confDetails := *confSet.details
175✔
1769
                if !ntfn.includeBlock {
310✔
1770
                        confDetails.Block = nil
135✔
1771
                }
135✔
1772

1773
                select {
175✔
1774
                case ntfn.Event.Confirmed <- &confDetails:
175✔
1775
                        ntfn.dispatched = true
175✔
1776
                case <-n.quit:
×
1777
                        return ErrTxNotifierExiting
×
1778
                }
1779
        }
1780
        delete(n.ntfnsByConfirmHeight, height)
1,487✔
1781

1,487✔
1782
        // Finally, we'll dispatch spend notifications for all the requests that
1,487✔
1783
        // were spent at this new block height.
1,487✔
1784
        for spendRequest := range n.spendsByHeight[height] {
1,538✔
1785
                spendSet := n.spendNotifications[spendRequest]
51✔
1786
                for _, ntfn := range spendSet.ntfns {
148✔
1787
                        err := n.dispatchSpendDetails(ntfn, spendSet.details)
97✔
1788
                        if err != nil {
97✔
1789
                                return err
×
1790
                        }
×
1791
                }
1792
        }
1793

1794
        return nil
1,487✔
1795
}
1796

1797
// DisconnectTip handles the tip of the current chain being disconnected during
1798
// a chain reorganization. If any watched requests were included in this block,
1799
// internal structures are updated to ensure confirmation/spend notifications
1800
// are consumed (if not already), and reorg notifications are dispatched
1801
// instead. Confirmation/spend notifications will be dispatched again upon block
1802
// inclusion.
1803
func (n *TxNotifier) DisconnectTip(blockHeight uint32) error {
93✔
1804
        select {
93✔
1805
        case <-n.quit:
×
1806
                return ErrTxNotifierExiting
×
1807
        default:
93✔
1808
        }
1809

1810
        n.Lock()
93✔
1811
        defer n.Unlock()
93✔
1812

93✔
1813
        if blockHeight != n.currentHeight {
93✔
1814
                return fmt.Errorf("received blocks out of order: "+
×
1815
                        "current height=%d, disconnected height=%d",
×
1816
                        n.currentHeight, blockHeight)
×
1817
        }
×
1818
        n.currentHeight--
93✔
1819
        n.reorgDepth++
93✔
1820

93✔
1821
        // With the block disconnected, we'll update the confirm and spend hints
93✔
1822
        // for our notification requests to reflect the new height, except for
93✔
1823
        // those that have confirmed/spent at previous heights.
93✔
1824
        n.updateHints(blockHeight)
93✔
1825

93✔
1826
        // We'll go through all of our watched confirmation requests and attempt
93✔
1827
        // to drain their notification channels to ensure sending notifications
93✔
1828
        // to the clients is always non-blocking.
93✔
1829
        for initialHeight, txHashes := range n.confsByInitialHeight {
383✔
1830
                for txHash := range txHashes {
773✔
1831
                        // If the transaction/output script has been reorged out
483✔
1832
                        // of the chain, we'll make sure to remove the cached
483✔
1833
                        // confirmation details to prevent notifying clients
483✔
1834
                        // with old information.
483✔
1835
                        confSet := n.confNotifications[txHash]
483✔
1836
                        if initialHeight == blockHeight {
496✔
1837
                                confSet.details = nil
13✔
1838
                        }
13✔
1839

1840
                        for _, ntfn := range confSet.ntfns {
1,094✔
1841
                                // First, we'll attempt to drain an update
611✔
1842
                                // from each notification to ensure sends to the
611✔
1843
                                // Updates channel are always non-blocking.
611✔
1844
                                select {
611✔
1845
                                case <-ntfn.Event.Updates:
324✔
1846
                                case <-n.quit:
×
1847
                                        return ErrTxNotifierExiting
×
1848
                                default:
288✔
1849
                                }
1850

1851
                                // We also reset the num of confs update.
1852
                                ntfn.numConfsLeft = ntfn.NumConfirmations
611✔
1853

611✔
1854
                                // Then, we'll check if the current
611✔
1855
                                // transaction/output script was included in the
611✔
1856
                                // block currently being disconnected. If it
611✔
1857
                                // was, we'll need to dispatch a reorg
611✔
1858
                                // notification to the client.
611✔
1859
                                if initialHeight == blockHeight {
624✔
1860
                                        err := n.dispatchConfReorg(
13✔
1861
                                                ntfn, blockHeight,
13✔
1862
                                        )
13✔
1863
                                        if err != nil {
13✔
1864
                                                return err
×
1865
                                        }
×
1866
                                }
1867
                        }
1868
                }
1869
        }
1870

1871
        // We'll also go through our watched spend requests and attempt to drain
1872
        // their dispatched notifications to ensure dispatching notifications to
1873
        // clients later on is always non-blocking. We're only interested in
1874
        // requests whose spending transaction was included at the height being
1875
        // disconnected.
1876
        for op := range n.spendsByHeight[blockHeight] {
105✔
1877
                // Since the spending transaction is being reorged out of the
12✔
1878
                // chain, we'll need to clear out the spending details of the
12✔
1879
                // request.
12✔
1880
                spendSet := n.spendNotifications[op]
12✔
1881
                spendSet.details = nil
12✔
1882

12✔
1883
                // For all requests which have had a spend notification
12✔
1884
                // dispatched, we'll attempt to drain it and send a reorg
12✔
1885
                // notification instead.
12✔
1886
                for _, ntfn := range spendSet.ntfns {
24✔
1887
                        if err := n.dispatchSpendReorg(ntfn); err != nil {
12✔
1888
                                return err
×
1889
                        }
×
1890
                }
1891
        }
1892

1893
        // Finally, we can remove the requests that were confirmed and/or spent
1894
        // at the height being disconnected. We'll still continue to track them
1895
        // until they have been confirmed/spent and are no longer under the risk
1896
        // of being reorged out of the chain again.
1897
        delete(n.confsByInitialHeight, blockHeight)
93✔
1898
        delete(n.spendsByHeight, blockHeight)
93✔
1899

93✔
1900
        return nil
93✔
1901
}
1902

1903
// updateHints attempts to update the confirm and spend hints for all relevant
1904
// requests respectively. The height parameter is used to determine which
1905
// requests we should update based on whether a new block is being
1906
// connected/disconnected.
1907
//
1908
// NOTE: This must be called with the TxNotifier's lock held and after its
1909
// height has already been reflected by a block being connected/disconnected.
1910
func (n *TxNotifier) updateHints(height uint32) {
1,679✔
1911
        // TODO(wilmer): update under one database transaction.
1,679✔
1912
        //
1,679✔
1913
        // To update the height hint for all the required confirmation requests
1,679✔
1914
        // under one database transaction, we'll gather the set of unconfirmed
1,679✔
1915
        // requests along with the ones that confirmed at the height being
1,679✔
1916
        // connected/disconnected.
1,679✔
1917
        confRequests := n.unconfirmedRequests()
1,679✔
1918
        for confRequest := range n.confsByInitialHeight[height] {
1,828✔
1919
                confRequests = append(confRequests, confRequest)
149✔
1920
        }
149✔
1921
        err := n.confirmHintCache.CommitConfirmHint(
1,679✔
1922
                n.currentHeight, confRequests...,
1,679✔
1923
        )
1,679✔
1924
        if err != nil {
1,679✔
1925
                // The error is not fatal as this is an optimistic optimization,
×
1926
                // so we'll avoid returning an error.
×
1927
                Log.Debugf("Unable to update confirm hints to %d for "+
×
1928
                        "%v: %v", n.currentHeight, confRequests, err)
×
1929
        }
×
1930

1931
        // Similarly, to update the height hint for all the required spend
1932
        // requests under one database transaction, we'll gather the set of
1933
        // unspent requests along with the ones that were spent at the height
1934
        // being connected/disconnected.
1935
        spendRequests := n.unspentRequests()
1,679✔
1936
        for spendRequest := range n.spendsByHeight[height] {
1,742✔
1937
                spendRequests = append(spendRequests, spendRequest)
63✔
1938
        }
63✔
1939
        err = n.spendHintCache.CommitSpendHint(n.currentHeight, spendRequests...)
1,679✔
1940
        if err != nil {
1,679✔
1941
                // The error is not fatal as this is an optimistic optimization,
×
1942
                // so we'll avoid returning an error.
×
1943
                Log.Debugf("Unable to update spend hints to %d for "+
×
1944
                        "%v: %v", n.currentHeight, spendRequests, err)
×
1945
        }
×
1946
}
1947

1948
// unconfirmedRequests returns the set of confirmation requests that are
1949
// still seen as unconfirmed by the TxNotifier.
1950
//
1951
// NOTE: This method must be called with the TxNotifier's lock held.
1952
func (n *TxNotifier) unconfirmedRequests() []ConfRequest {
1,679✔
1953
        var unconfirmed []ConfRequest
1,679✔
1954
        for confRequest, confNtfnSet := range n.confNotifications {
16,330✔
1955
                // If the notification is already aware of its confirmation
14,651✔
1956
                // details, or it's in the process of learning them, we'll skip
14,651✔
1957
                // it as we can't yet determine if it's confirmed or not.
14,651✔
1958
                if confNtfnSet.rescanStatus != rescanComplete ||
14,651✔
1959
                        confNtfnSet.details != nil {
28,611✔
1960
                        continue
13,960✔
1961
                }
1962

1963
                unconfirmed = append(unconfirmed, confRequest)
692✔
1964
        }
1965

1966
        return unconfirmed
1,679✔
1967
}
1968

1969
// unspentRequests returns the set of spend requests that are still seen as
1970
// unspent by the TxNotifier.
1971
//
1972
// NOTE: This method must be called with the TxNotifier's lock held.
1973
func (n *TxNotifier) unspentRequests() []SpendRequest {
1,679✔
1974
        var unspent []SpendRequest
1,679✔
1975
        for spendRequest, spendNtfnSet := range n.spendNotifications {
3,264✔
1976
                // If the notification is already aware of its spend details, or
1,585✔
1977
                // it's in the process of learning them, we'll skip it as we
1,585✔
1978
                // can't yet determine if it's unspent or not.
1,585✔
1979
                if spendNtfnSet.rescanStatus != rescanComplete ||
1,585✔
1980
                        spendNtfnSet.details != nil {
3,128✔
1981
                        continue
1,543✔
1982
                }
1983

1984
                unspent = append(unspent, spendRequest)
43✔
1985
        }
1986

1987
        return unspent
1,679✔
1988
}
1989

1990
// dispatchConfReorg dispatches a reorg notification to the client if the
1991
// confirmation notification was already delivered.
1992
//
1993
// NOTE: This must be called with the TxNotifier's lock held.
1994
func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
1995
        heightDisconnected uint32) error {
13✔
1996

13✔
1997
        // If the request's confirmation notification has yet to be dispatched,
13✔
1998
        // we'll need to clear its entry within the ntfnsByConfirmHeight index
13✔
1999
        // to prevent from notifying the client once the notifier reaches the
13✔
2000
        // confirmation height.
13✔
2001
        if !ntfn.dispatched {
24✔
2002
                confHeight := heightDisconnected + ntfn.NumConfirmations - 1
11✔
2003
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
11✔
2004
                if exists {
22✔
2005
                        delete(ntfnSet, ntfn)
11✔
2006
                }
11✔
2007
                return nil
11✔
2008
        }
2009

2010
        // Otherwise, the entry within the ntfnsByConfirmHeight has already been
2011
        // deleted, so we'll attempt to drain the confirmation notification to
2012
        // ensure sends to the Confirmed channel are always non-blocking.
2013
        select {
3✔
2014
        case <-ntfn.Event.Confirmed:
×
2015
        case <-n.quit:
×
2016
                return ErrTxNotifierExiting
×
2017
        default:
3✔
2018
        }
2019

2020
        ntfn.dispatched = false
3✔
2021

3✔
2022
        // Send a negative confirmation notification to the client indicating
3✔
2023
        // how many blocks have been disconnected successively.
3✔
2024
        select {
3✔
2025
        case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
3✔
2026
        case <-n.quit:
×
2027
                return ErrTxNotifierExiting
×
2028
        }
2029

2030
        return nil
3✔
2031
}
2032

2033
// dispatchSpendReorg dispatches a reorg notification to the client if a spend
2034
// notiification was already delivered.
2035
//
2036
// NOTE: This must be called with the TxNotifier's lock held.
2037
func (n *TxNotifier) dispatchSpendReorg(ntfn *SpendNtfn) error {
12✔
2038
        if !ntfn.dispatched {
12✔
2039
                return nil
×
2040
        }
×
2041

2042
        // Attempt to drain the spend notification to ensure sends to the Spend
2043
        // channel are always non-blocking.
2044
        select {
12✔
2045
        case <-ntfn.Event.Spend:
1✔
2046
        default:
11✔
2047
        }
2048

2049
        // Send a reorg notification to the client in order for them to
2050
        // correctly handle reorgs.
2051
        select {
12✔
2052
        case ntfn.Event.Reorg <- struct{}{}:
12✔
2053
        case <-n.quit:
×
2054
                return ErrTxNotifierExiting
×
2055
        }
2056

2057
        ntfn.dispatched = false
12✔
2058

12✔
2059
        return nil
12✔
2060
}
2061

2062
// TearDown is to be called when the owner of the TxNotifier is exiting. This
2063
// closes the event channels of all registered notifications that have not been
2064
// dispatched yet.
2065
func (n *TxNotifier) TearDown() {
23✔
2066
        close(n.quit)
23✔
2067

23✔
2068
        n.Lock()
23✔
2069
        defer n.Unlock()
23✔
2070

23✔
2071
        for _, confSet := range n.confNotifications {
149✔
2072
                for confID, ntfn := range confSet.ntfns {
308✔
2073
                        close(ntfn.Event.Confirmed)
182✔
2074
                        close(ntfn.Event.Updates)
182✔
2075
                        close(ntfn.Event.NegativeConf)
182✔
2076
                        close(ntfn.Event.Done)
182✔
2077
                        delete(confSet.ntfns, confID)
182✔
2078
                }
182✔
2079
        }
2080

2081
        for _, spendSet := range n.spendNotifications {
57✔
2082
                for spendID, ntfn := range spendSet.ntfns {
132✔
2083
                        close(ntfn.Event.Spend)
98✔
2084
                        close(ntfn.Event.Reorg)
98✔
2085
                        close(ntfn.Event.Done)
98✔
2086
                        delete(spendSet.ntfns, spendID)
98✔
2087
                }
98✔
2088
        }
2089
}
2090

2091
// notifyNumConfsLeft sends the number of confirmations left to the
2092
// notification subscriber through the Event.Updates channel.
2093
//
2094
// NOTE: must be used with the TxNotifier's lock held.
2095
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn, num uint32) error {
730✔
2096
        // If the number left is no less than the recorded value, we can skip
730✔
2097
        // sending it as it means this same value has already been sent before.
730✔
2098
        if num >= ntfn.numConfsLeft {
731✔
2099
                Log.Debugf("Skipped dispatched update (numConfsLeft=%v) for "+
1✔
2100
                        "request %v conf_id=%v", num, ntfn.ConfRequest,
1✔
2101
                        ntfn.ConfID)
1✔
2102

1✔
2103
                return nil
1✔
2104
        }
1✔
2105

2106
        // Update the number of confirmations left to the notification.
2107
        ntfn.numConfsLeft = num
730✔
2108

730✔
2109
        select {
730✔
2110
        case ntfn.Event.Updates <- num:
730✔
2111
        case <-n.quit:
×
2112
                return ErrTxNotifierExiting
×
2113
        }
2114

2115
        return nil
730✔
2116
}
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