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

lightningnetwork / lnd / 16252769597

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

Pull #9878

github

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

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

48 existing lines in 19 files now uncovered.

135386 of 201028 relevant lines covered (67.35%)

21747.24 hits per line

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

88.62
/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 {
159✔
132
        return &confNtfnSet{
159✔
133
                ntfns:        make(map[uint64]*ConfNtfn),
159✔
134
                rescanStatus: rescanNotStarted,
159✔
135
        }
159✔
136
}
159✔
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 {
49✔
159
        return &spendNtfnSet{
49✔
160
                ntfns:        make(map[uint64]*SpendNtfn),
49✔
161
                rescanStatus: rescanNotStarted,
49✔
162
        }
49✔
163
}
49✔
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) {
237✔
183
        var r ConfRequest
237✔
184
        outputScript, err := txscript.ParsePkScript(pkScript)
237✔
185
        if err != nil {
237✔
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 {
380✔
193
                r.TxID = *txid
143✔
194
        }
143✔
195
        r.PkScript = outputScript
237✔
196

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

200
// String returns the string representation of the ConfRequest.
201
func (r ConfRequest) String() string {
3✔
202
        if r.TxID != ZeroHash {
6✔
203
                return fmt.Sprintf("txid=%v", r.TxID)
3✔
204
        }
3✔
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 {
217✔
213
        scriptMatches := func() bool {
359✔
214
                pkScript := r.PkScript.Script()
142✔
215
                for _, txOut := range tx.TxOut {
314✔
216
                        if bytes.Equal(txOut.PkScript, pkScript) {
227✔
217
                                return true
55✔
218
                        }
55✔
219
                }
220

221
                return false
87✔
222
        }
223

224
        if r.TxID != ZeroHash {
349✔
225
                return r.TxID == tx.TxHash() && scriptMatches()
132✔
226
        }
132✔
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
        // allConfirmations is true if the client wants to receive a
264
        // notification for every confirmation of this transaction/output script
265
        // rather than just the final one. If true, the client will receive a
266
        // notification for each confirmation, starting from the first.
267
        allConfirmations bool
268

269
        // numConfsLeft is the number of confirmations left to be sent to the
270
        // subscriber.
271
        numConfsLeft uint32
272
}
273

274
// HistoricalConfDispatch parametrizes a manual rescan for a particular
275
// transaction/output script. The parameters include the start and end block
276
// heights specifying the range of blocks to scan.
277
type HistoricalConfDispatch struct {
278
        // ConfRequest represents either the txid or script we should detect
279
        // inclusion of within the chain.
280
        ConfRequest
281

282
        // StartHeight specifies the block height at which to begin the
283
        // historical rescan.
284
        StartHeight uint32
285

286
        // EndHeight specifies the last block height (inclusive) that the
287
        // historical scan should consider.
288
        EndHeight uint32
289
}
290

291
// ConfRegistration encompasses all of the information required for callers to
292
// retrieve details about a confirmation event.
293
type ConfRegistration struct {
294
        // Event contains references to the channels that the notifications are
295
        // to be sent over.
296
        Event *ConfirmationEvent
297

298
        // HistoricalDispatch, if non-nil, signals to the client who registered
299
        // the notification that they are responsible for attempting to manually
300
        // rescan blocks for the txid/output script between the start and end
301
        // heights.
302
        HistoricalDispatch *HistoricalConfDispatch
303

304
        // Height is the height of the TxNotifier at the time the confirmation
305
        // notification was registered. This can be used so that backends can
306
        // request to be notified of confirmations from this point forwards.
307
        Height uint32
308
}
309

310
// SpendRequest encapsulates a request for a spend notification of either an
311
// outpoint or output script.
312
type SpendRequest struct {
313
        // OutPoint is the outpoint for which a client has requested a spend
314
        // notification for. If set to a zero outpoint, then a spend
315
        // notification will be dispatched upon detecting the spend of the
316
        // _script_, rather than the outpoint.
317
        OutPoint wire.OutPoint
318

319
        // PkScript is the script of the outpoint. If a zero outpoint is set,
320
        // then this can be an arbitrary script.
321
        PkScript txscript.PkScript
322
}
323

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

334
        // We'll only set an outpoint for which we'll dispatch a spend
335
        // notification on this request if one was provided. Otherwise, we'll
336
        // default to dispatching on the spend of the script instead.
337
        if op != nil {
204✔
338
                r.OutPoint = *op
76✔
339
        }
76✔
340
        r.PkScript = outputScript
128✔
341

128✔
342
        // For Taproot spends we have the main problem that for the key spend
128✔
343
        // path we cannot derive the pkScript from only looking at the input's
128✔
344
        // witness. So we need to rely on the outpoint information alone.
128✔
345
        //
128✔
346
        // TODO(guggero): For script path spends we can derive the pkScript from
128✔
347
        // the witness, since we have the full control block and the spent
128✔
348
        // script available.
128✔
349
        if outputScript.Class() == txscript.WitnessV1TaprootTy {
131✔
350
                if op == nil {
6✔
351
                        return r, fmt.Errorf("cannot register witness v1 " +
3✔
352
                                "spend request without outpoint")
3✔
353
                }
3✔
354

355
                // We have an outpoint, so we can set the pkScript to an all
356
                // zero Taproot key that we'll compare this spend request to.
357
                r.PkScript = ZeroTaprootPkScript
3✔
358
        }
359

360
        return r, nil
128✔
361
}
362

363
// String returns the string representation of the SpendRequest.
364
func (r SpendRequest) String() string {
4✔
365
        if r.OutPoint != ZeroOutPoint {
8✔
366
                return fmt.Sprintf("outpoint=%v, script=%v", r.OutPoint,
4✔
367
                        r.PkScript)
4✔
368
        }
4✔
369
        return fmt.Sprintf("outpoint=<zero>, script=%v", r.PkScript)
×
370
}
371

372
// MatchesTx determines whether the given transaction satisfies the spend
373
// request. If the spend request is for an outpoint, then we'll check all of
374
// the outputs being spent by the inputs of the transaction to determine if it
375
// matches. Otherwise, we'll need to match on the output script being spent, so
376
// we'll recompute it for each input of the transaction to determine if it
377
// matches.
378
func (r SpendRequest) MatchesTx(tx *wire.MsgTx) (bool, uint32, error) {
9✔
379
        if r.OutPoint != ZeroOutPoint {
14✔
380
                for i, txIn := range tx.TxIn {
10✔
381
                        if txIn.PreviousOutPoint == r.OutPoint {
6✔
382
                                return true, uint32(i), nil
1✔
383
                        }
1✔
384
                }
385

386
                return false, 0, nil
5✔
387
        }
388

389
        for i, txIn := range tx.TxIn {
8✔
390
                pkScript, err := txscript.ComputePkScript(
4✔
391
                        txIn.SignatureScript, txIn.Witness,
4✔
392
                )
4✔
393
                if err == txscript.ErrUnsupportedScriptType {
4✔
394
                        continue
×
395
                }
396
                if err != nil {
4✔
397
                        return false, 0, err
×
398
                }
×
399

400
                if bytes.Equal(pkScript.Script(), r.PkScript.Script()) {
4✔
401
                        return true, uint32(i), nil
×
402
                }
×
403
        }
404

405
        return false, 0, nil
4✔
406
}
407

408
// SpendNtfn represents a client's request to receive a notification once an
409
// outpoint/output script has been spent on-chain. The client is asynchronously
410
// notified via the SpendEvent channels.
411
type SpendNtfn struct {
412
        // SpendID uniquely identies the spend notification request for the
413
        // specified outpoint/output script.
414
        SpendID uint64
415

416
        // SpendRequest represents either the outpoint or script we should
417
        // detect the spend of.
418
        SpendRequest
419

420
        // Event contains references to the channels that the notifications are
421
        // to be sent over.
422
        Event *SpendEvent
423

424
        // HeightHint is the earliest height in the chain that we expect to find
425
        // the spending transaction of the specified outpoint/output script.
426
        // This value will be overridden by the spend hint cache if it contains
427
        // an entry for it.
428
        HeightHint uint32
429

430
        // dispatched signals whether a spend notification has been dispatched
431
        // to the client.
432
        dispatched bool
433
}
434

435
// HistoricalSpendDispatch parametrizes a manual rescan to determine the
436
// spending details (if any) of an outpoint/output script. The parameters
437
// include the start and end block heights specifying the range of blocks to
438
// scan.
439
type HistoricalSpendDispatch struct {
440
        // SpendRequest represents either the outpoint or script we should
441
        // detect the spend of.
442
        SpendRequest
443

444
        // StartHeight specified the block height at which to begin the
445
        // historical rescan.
446
        StartHeight uint32
447

448
        // EndHeight specifies the last block height (inclusive) that the
449
        // historical rescan should consider.
450
        EndHeight uint32
451
}
452

453
// SpendRegistration encompasses all of the information required for callers to
454
// retrieve details about a spend event.
455
type SpendRegistration struct {
456
        // Event contains references to the channels that the notifications are
457
        // to be sent over.
458
        Event *SpendEvent
459

460
        // HistoricalDispatch, if non-nil, signals to the client who registered
461
        // the notification that they are responsible for attempting to manually
462
        // rescan blocks for the txid/output script between the start and end
463
        // heights.
464
        HistoricalDispatch *HistoricalSpendDispatch
465

466
        // Height is the height of the TxNotifier at the time the spend
467
        // notification was registered. This can be used so that backends can
468
        // request to be notified of spends from this point forwards.
469
        Height uint32
470
}
471

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

481
        // currentHeight is the height of the tracked blockchain. It is used to
482
        // determine the number of confirmations a tx has and ensure blocks are
483
        // connected and disconnected in order.
484
        currentHeight uint32
485

486
        // reorgSafetyLimit is the chain depth beyond which it is assumed a
487
        // block will not be reorganized out of the chain. This is used to
488
        // determine when to prune old notification requests so that reorgs are
489
        // handled correctly. The coinbase maturity period is a reasonable value
490
        // to use.
491
        reorgSafetyLimit uint32
492

493
        // reorgDepth is the depth of a chain organization that this system is
494
        // being informed of. This is incremented as long as a sequence of
495
        // blocks are disconnected without being interrupted by a new block.
496
        reorgDepth uint32
497

498
        // confNotifications is an index of confirmation notification requests
499
        // by transaction hash/output script.
500
        confNotifications map[ConfRequest]*confNtfnSet
501

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

509
        // ntfnsByConfirmHeight is an index of notification requests by the
510
        // height at which the transaction/output script will have sufficient
511
        // confirmations.
512
        ntfnsByConfirmHeight map[uint32]map[*ConfNtfn]struct{}
513

514
        // spendNotifications is an index of all active notification requests
515
        // per outpoint/output script.
516
        spendNotifications map[SpendRequest]*spendNtfnSet
517

518
        // spendsByHeight is an index that keeps tracks of the spending height
519
        // of outpoints/output scripts we are currently tracking notifications
520
        // for. This is used in order to recover from spending transactions
521
        // being reorged out of the chain.
522
        spendsByHeight map[uint32]map[SpendRequest]struct{}
523

524
        // confirmHintCache is a cache used to maintain the latest height hints
525
        // for transactions/output scripts. Each height hint represents the
526
        // earliest height at which they scripts could have been confirmed
527
        // within the chain.
528
        confirmHintCache ConfirmHintCache
529

530
        // spendHintCache is a cache used to maintain the latest height hints
531
        // for outpoints/output scripts. Each height hint represents the
532
        // earliest height at which they could have been spent within the chain.
533
        spendHintCache SpendHintCache
534

535
        // quit is closed in order to signal that the notifier is gracefully
536
        // exiting.
537
        quit chan struct{}
538

539
        sync.Mutex
540
}
541

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

52✔
550
        return &TxNotifier{
52✔
551
                currentHeight:        startHeight,
52✔
552
                reorgSafetyLimit:     reorgSafetyLimit,
52✔
553
                confNotifications:    make(map[ConfRequest]*confNtfnSet),
52✔
554
                confsByInitialHeight: make(map[uint32]map[ConfRequest]struct{}),
52✔
555
                ntfnsByConfirmHeight: make(map[uint32]map[*ConfNtfn]struct{}),
52✔
556
                spendNotifications:   make(map[SpendRequest]*spendNtfnSet),
52✔
557
                spendsByHeight:       make(map[uint32]map[SpendRequest]struct{}),
52✔
558
                confirmHintCache:     confirmHintCache,
52✔
559
                spendHintCache:       spendHintCache,
52✔
560
                quit:                 make(chan struct{}),
52✔
561
        }
52✔
562
}
52✔
563

564
// newConfNtfn validates all of the parameters required to successfully create
565
// and register a confirmation notification.
566
func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash,
567
        pkScript []byte, numConfs, heightHint uint32,
568
        opts *NotifierOptions) (*ConfNtfn, error) {
229✔
569

229✔
570
        // An accompanying output script must always be provided.
229✔
571
        if len(pkScript) == 0 {
230✔
572
                return nil, ErrNoScript
1✔
573
        }
1✔
574

575
        // Enforce that we will not dispatch confirmations beyond the reorg
576
        // safety limit.
577
        if numConfs == 0 || numConfs > n.reorgSafetyLimit {
230✔
578
                return nil, ErrNumConfsOutOfRange
2✔
579
        }
2✔
580

581
        // A height hint must be provided to prevent scanning from the genesis
582
        // block.
583
        if heightHint == 0 {
227✔
584
                return nil, ErrNoHeightHint
1✔
585
        }
1✔
586

587
        // Ensure the output script is of a supported type.
588
        confRequest, err := NewConfRequest(txid, pkScript)
225✔
589
        if err != nil {
225✔
590
                return nil, err
×
591
        }
×
592

593
        confID := atomic.AddUint64(&n.confClientCounter, 1)
225✔
594
        return &ConfNtfn{
225✔
595
                ConfID:           confID,
225✔
596
                ConfRequest:      confRequest,
225✔
597
                NumConfirmations: numConfs,
225✔
598
                Event: NewConfirmationEvent(numConfs, func() {
231✔
599
                        n.CancelConf(confRequest, confID)
6✔
600
                }),
6✔
601
                HeightHint:       heightHint,
602
                includeBlock:     opts.IncludeBlock,
603
                allConfirmations: opts.allConfirmations,
604
                numConfsLeft:     numConfs,
605
        }, nil
606
}
607

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

230✔
620
        select {
230✔
621
        case <-n.quit:
1✔
622
                return nil, ErrTxNotifierExiting
1✔
623
        default:
229✔
624
        }
625

626
        opts := DefaultNotifierOptions()
229✔
627
        for _, optFunc := range optFuncs {
313✔
628
                optFunc(opts)
84✔
629
        }
84✔
630

631
        // We'll start by performing a series of validation checks.
632
        ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint, opts)
229✔
633
        if err != nil {
233✔
634
                return nil, err
4✔
635
        }
4✔
636

637
        // Before proceeding to register the notification, we'll query our
638
        // height hint cache to determine whether a better one exists.
639
        //
640
        // TODO(conner): verify that all submitted height hints are identical.
641
        startHeight := ntfn.HeightHint
225✔
642
        hint, err := n.confirmHintCache.QueryConfirmHint(ntfn.ConfRequest)
225✔
643
        if err == nil {
251✔
644
                if hint > startHeight {
48✔
645
                        Log.Debugf("Using height hint %d retrieved from cache "+
22✔
646
                                "for %v instead of %d for conf subscription",
22✔
647
                                hint, ntfn.ConfRequest, startHeight)
22✔
648
                        startHeight = hint
22✔
649
                }
22✔
650
        } else if err != ErrConfirmHintNotFound {
202✔
651
                Log.Errorf("Unable to query confirm hint for %v: %v",
×
652
                        ntfn.ConfRequest, err)
×
653
        }
×
654

655
        Log.Infof("New confirmation subscription: conf_id=%d, %v, "+
225✔
656
                "num_confs=%v height_hint=%d", ntfn.ConfID, ntfn.ConfRequest,
225✔
657
                numConfs, startHeight)
225✔
658

225✔
659
        n.Lock()
225✔
660
        defer n.Unlock()
225✔
661

225✔
662
        confSet, ok := n.confNotifications[ntfn.ConfRequest]
225✔
663
        if !ok {
384✔
664
                // If this is the first registration for this request, construct
159✔
665
                // a confSet to coalesce all notifications for the same request.
159✔
666
                confSet = newConfNtfnSet()
159✔
667
                n.confNotifications[ntfn.ConfRequest] = confSet
159✔
668
        }
159✔
669
        confSet.ntfns[ntfn.ConfID] = ntfn
225✔
670

225✔
671
        switch confSet.rescanStatus {
225✔
672

673
        // A prior rescan has already completed and we are actively watching at
674
        // tip for this request.
675
        case rescanComplete:
28✔
676
                // If the confirmation details for this set of notifications has
28✔
677
                // already been found, we'll attempt to deliver them immediately
28✔
678
                // to this client.
28✔
679
                Log.Debugf("Attempting to dispatch confirmation for %v on "+
28✔
680
                        "registration since rescan has finished, conf_id=%v",
28✔
681
                        ntfn.ConfRequest, ntfn.ConfID)
28✔
682

28✔
683
                // The default notification we assigned above includes the
28✔
684
                // block along with the rest of the details. However not all
28✔
685
                // clients want the block, so we make a copy here w/o the block
28✔
686
                // if needed so we can give clients only what they ask for.
28✔
687
                confDetails := confSet.details
28✔
688
                if !ntfn.includeBlock && confDetails != nil {
42✔
689
                        confDetailsCopy := *confDetails
14✔
690
                        confDetailsCopy.Block = nil
14✔
691

14✔
692
                        confDetails = &confDetailsCopy
14✔
693
                }
14✔
694

695
                // Deliver the details to the whole conf set where this ntfn
696
                // lives in.
697
                for _, subscriber := range confSet.ntfns {
122✔
698
                        err := n.dispatchConfDetails(subscriber, confDetails)
94✔
699
                        if err != nil {
94✔
700
                                return nil, err
×
701
                        }
×
702
                }
703

704
                return &ConfRegistration{
28✔
705
                        Event:              ntfn.Event,
28✔
706
                        HistoricalDispatch: nil,
28✔
707
                        Height:             n.currentHeight,
28✔
708
                }, nil
28✔
709

710
        // A rescan is already in progress, return here to prevent dispatching
711
        // another. When the rescan returns, this notification's details will be
712
        // updated as well.
713
        case rescanPending:
44✔
714
                Log.Debugf("Waiting for pending rescan to finish before "+
44✔
715
                        "notifying %v at tip", ntfn.ConfRequest)
44✔
716

44✔
717
                return &ConfRegistration{
44✔
718
                        Event:              ntfn.Event,
44✔
719
                        HistoricalDispatch: nil,
44✔
720
                        Height:             n.currentHeight,
44✔
721
                }, nil
44✔
722

723
        // If no rescan has been dispatched, attempt to do so now.
724
        case rescanNotStarted:
159✔
725
        }
726

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

6✔
736
                // Set the rescan status to complete, which will allow the
6✔
737
                // notifier to start delivering messages for this set
6✔
738
                // immediately.
6✔
739
                confSet.rescanStatus = rescanComplete
6✔
740
                return &ConfRegistration{
6✔
741
                        Event:              ntfn.Event,
6✔
742
                        HistoricalDispatch: nil,
6✔
743
                        Height:             n.currentHeight,
6✔
744
                }, nil
6✔
745
        }
6✔
746

747
        Log.Debugf("Dispatching historical confirmation rescan for %v",
155✔
748
                ntfn.ConfRequest)
155✔
749

155✔
750
        // Construct the parameters for historical dispatch, scanning the range
155✔
751
        // of blocks between our best known height hint and the notifier's
155✔
752
        // current height. The notifier will begin also watching for
155✔
753
        // confirmations at tip starting with the next block.
155✔
754
        dispatch := &HistoricalConfDispatch{
155✔
755
                ConfRequest: ntfn.ConfRequest,
155✔
756
                StartHeight: startHeight,
155✔
757
                EndHeight:   n.currentHeight,
155✔
758
        }
155✔
759

155✔
760
        // Set this confSet's status to pending, ensuring subsequent
155✔
761
        // registrations don't also attempt a dispatch.
155✔
762
        confSet.rescanStatus = rescanPending
155✔
763

155✔
764
        return &ConfRegistration{
155✔
765
                Event:              ntfn.Event,
155✔
766
                HistoricalDispatch: dispatch,
155✔
767
                Height:             n.currentHeight,
155✔
768
        }, nil
155✔
769
}
770

771
// CancelConf cancels an existing request for a spend notification of an
772
// outpoint/output script. The request is identified by its spend ID.
773
func (n *TxNotifier) CancelConf(confRequest ConfRequest, confID uint64) {
6✔
774
        select {
6✔
775
        case <-n.quit:
×
776
                return
×
777
        default:
6✔
778
        }
779

780
        n.Lock()
6✔
781
        defer n.Unlock()
6✔
782

6✔
783
        confSet, ok := n.confNotifications[confRequest]
6✔
784
        if !ok {
6✔
785
                return
×
786
        }
×
787
        ntfn, ok := confSet.ntfns[confID]
6✔
788
        if !ok {
6✔
789
                return
×
790
        }
×
791

792
        Log.Debugf("Canceling confirmation notification: conf_id=%d, %v",
6✔
793
                confID, confRequest)
6✔
794

6✔
795
        // We'll close all the notification channels to let the client know
6✔
796
        // their cancel request has been fulfilled.
6✔
797
        close(ntfn.Event.Confirmed)
6✔
798
        close(ntfn.Event.NegativeConf)
6✔
799

6✔
800
        // Finally, we'll clean up any lingering references to this
6✔
801
        // notification.
6✔
802
        delete(confSet.ntfns, confID)
6✔
803

6✔
804
        // Remove the queued confirmation notification if the transaction has
6✔
805
        // already confirmed, but hasn't met its required number of
6✔
806
        // confirmations.
6✔
807
        if confSet.details != nil {
11✔
808
                confHeight := confSet.details.BlockHeight +
5✔
809
                        ntfn.NumConfirmations - 1
5✔
810
                delete(n.ntfnsByConfirmHeight[confHeight], ntfn)
5✔
811
        }
5✔
812
}
813

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

146✔
824
        select {
146✔
825
        case <-n.quit:
×
826
                return ErrTxNotifierExiting
×
827
        default:
146✔
828
        }
829

830
        // Ensure we hold the lock throughout handling the notification to
831
        // prevent the notifier from advancing its height underneath us.
832
        n.Lock()
146✔
833
        defer n.Unlock()
146✔
834

146✔
835
        // First, we'll determine whether we have an active confirmation
146✔
836
        // notification for the given txid/script.
146✔
837
        confSet, ok := n.confNotifications[confRequest]
146✔
838
        if !ok {
146✔
839
                return fmt.Errorf("confirmation notification for %v not found",
×
840
                        confRequest)
×
841
        }
×
842

843
        // If the confirmation details were already found at tip, all existing
844
        // notifications will have been dispatched or queued for dispatch. We
845
        // can exit early to avoid sending too many notifications on the
846
        // buffered channels.
847
        if confSet.details != nil {
151✔
848
                return nil
5✔
849
        }
5✔
850

851
        // The historical dispatch has been completed for this confSet. We'll
852
        // update the rescan status and cache any details that were found. If
853
        // the details are nil, that implies we did not find them and will
854
        // continue to watch for them at tip.
855
        confSet.rescanStatus = rescanComplete
142✔
856

142✔
857
        // The notifier has yet to reach the height at which the
142✔
858
        // transaction/output script was included in a block, so we should defer
142✔
859
        // until handling it then within ConnectTip.
142✔
860
        if details == nil {
263✔
861
                Log.Debugf("Confirmation details for %v not found during "+
121✔
862
                        "historical dispatch, waiting to dispatch at tip",
121✔
863
                        confRequest)
121✔
864

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

878
                return nil
121✔
879
        }
880

881
        if details.BlockHeight > n.currentHeight {
25✔
882
                Log.Debugf("Confirmation details for %v found above current "+
1✔
883
                        "height, waiting to dispatch at tip", confRequest)
1✔
884

1✔
885
                return nil
1✔
886
        }
1✔
887

888
        Log.Debugf("Updating confirmation details for %v", confRequest)
23✔
889

23✔
890
        err := n.confirmHintCache.CommitConfirmHint(
23✔
891
                details.BlockHeight, confRequest,
23✔
892
        )
23✔
893
        if err != nil {
23✔
894
                // The error is not fatal, so we should not return an error to
×
895
                // the caller.
×
896
                Log.Errorf("Unable to update confirm hint to %d for %v: %v",
×
897
                        details.BlockHeight, confRequest, err)
×
898
        }
×
899

900
        // Cache the details found in the rescan and attempt to dispatch any
901
        // notifications that have not yet been delivered.
902
        confSet.details = details
23✔
903
        for _, ntfn := range confSet.ntfns {
51✔
904
                // The default notification we assigned above includes the
28✔
905
                // block along with the rest of the details. However not all
28✔
906
                // clients want the block, so we make a copy here w/o the block
28✔
907
                // if needed so we can give clients only what they ask for.
28✔
908
                confDetails := *details
28✔
909
                if !ntfn.includeBlock {
48✔
910
                        confDetails.Block = nil
20✔
911
                }
20✔
912

913
                err = n.dispatchConfDetails(ntfn, &confDetails)
28✔
914
                if err != nil {
28✔
915
                        return err
×
916
                }
×
917
        }
918

919
        return nil
23✔
920
}
921

922
// dispatchConfDetails attempts to cache and dispatch details to a particular
923
// client if the transaction/output script has sufficiently confirmed. If the
924
// provided details are nil, this method will be a no-op.
925
func (n *TxNotifier) dispatchConfDetails(
926
        ntfn *ConfNtfn, details *TxConfirmation) error {
119✔
927

119✔
928
        // If there are no conf details to dispatch or if the notification has
119✔
929
        // already been dispatched, then we can skip dispatching to this
119✔
930
        // client.
119✔
931
        if details == nil {
146✔
932
                Log.Debugf("Skipped dispatching nil conf details for request "+
27✔
933
                        "%v, conf_id=%v", ntfn.ConfRequest, ntfn.ConfID)
27✔
934

27✔
935
                return nil
27✔
936
        }
27✔
937

938
        if ntfn.dispatched {
146✔
939
                Log.Debugf("Skipped dispatched conf details for request %v "+
51✔
940
                        "conf_id=%v", ntfn.ConfRequest, ntfn.ConfID)
51✔
941

51✔
942
                return nil
51✔
943
        }
51✔
944

945
        // Now, we'll examine whether the transaction/output script of this
946
        // request has reached its required number of confirmations. If it has,
947
        // we'll dispatch a confirmation notification to the caller.
948
        confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
47✔
949
        if confHeight <= n.currentHeight {
85✔
950
                Log.Debugf("Dispatching %v confirmation notification for "+
38✔
951
                        "conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
38✔
952
                        ntfn.ConfRequest)
38✔
953

38✔
954
                // Update the number of confirmations left to the notification.
38✔
955
                ntfn.numConfsLeft = 0
38✔
956

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

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

12✔
977
                // We'll send a confirmation update to the client only if
12✔
978
                // the client has opted in to receive updates on the number
12✔
979
                // of confirmations remaining for the transaction/output script.
12✔
980
                numConfsLeft := confHeight - n.currentHeight
12✔
981
                err := n.updateConfsLeftAndNotify(ntfn, numConfsLeft, *details)
12✔
982
                if err != nil {
12✔
983
                        return err
×
984
                }
×
985
        }
986

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

999
        return nil
47✔
1000
}
1001

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

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

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

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

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

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

131✔
1046
        select {
131✔
1047
        case <-n.quit:
1✔
1048
                return nil, ErrTxNotifierExiting
1✔
1049
        default:
130✔
1050
        }
1051

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

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

1074
        n.Lock()
128✔
1075
        defer n.Unlock()
128✔
1076

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1178
        n.Lock()
12✔
1179
        defer n.Unlock()
12✔
1180

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

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

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

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

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

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

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

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

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

1243
        return nil
45✔
1244
}
1245

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

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

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

16✔
1267
        return n.updateSpendDetails(spendRequest, details)
16✔
1268
}
1269

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

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

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

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

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

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

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

×
1331
                return ErrEmptyWitnessStack
×
1332
        }
×
1333

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

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

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

7✔
1359
        spendSet.details = details
7✔
1360
        for _, ntfn := range spendSet.ntfns {
19✔
1361
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
12✔
1362
                if err != nil {
12✔
1363
                        return err
×
1364
                }
×
1365
        }
1366

1367
        return nil
7✔
1368
}
1369

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

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

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

1394
        spendHeight := uint32(details.SpendingHeight)
128✔
1395

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

1407
        return nil
128✔
1408
}
1409

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

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

1438
        n.Lock()
1,595✔
1439
        defer n.Unlock()
1,595✔
1440

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

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

1,588✔
1456
                for _, tx := range block.Transactions() {
3,852✔
1457
                        n.filterTx(
2,264✔
1458
                                block, tx, blockHeight,
2,264✔
1459
                                n.handleConfDetailsAtTip,
2,264✔
1460
                                n.handleSpendDetailsAtTip,
2,264✔
1461
                        )
2,264✔
1462
                }
2,264✔
1463
        }
1464

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

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

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

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

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

1506
        return nil
1,595✔
1507
}
1508

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

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

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

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

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

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

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

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

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

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

144✔
1601
                        onConf(confRequest, details)
144✔
1602
                }
144✔
1603

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

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

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

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

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

1649
        confSet.rescanStatus = rescanComplete
141✔
1650
        confSet.details = details
141✔
1651

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

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

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

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

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

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

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

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

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

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

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

1748
                                err := n.updateConfsLeftAndNotify(ntfn,
694✔
1749
                                        numConfsLeft, *confSet.details)
694✔
1750
                                if err != nil {
694✔
1751
                                        return err
×
1752
                                }
×
1753
                        }
1754
                }
1755
        }
1756

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

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

1771
                // If the `confDetails` has already been sent before, we'll
1772
                // skip it and continue processing the next one.
1773
                if ntfn.dispatched {
179✔
1774
                        Log.Debugf("Skipped dispatched conf details for "+
×
1775
                                "request %v conf_id=%v", ntfn.ConfRequest,
×
1776
                                ntfn.ConfID)
×
1777

×
1778
                        continue
×
1779
                }
1780

1781
                Log.Debugf("Dispatching %v confirmation notification for "+
179✔
1782
                        "conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
179✔
1783
                        ntfn.ConfRequest)
179✔
1784

179✔
1785
                select {
179✔
1786
                case ntfn.Event.Confirmed <- &confDetails:
179✔
1787
                        ntfn.dispatched = true
179✔
1788
                case <-n.quit:
×
1789
                        return ErrTxNotifierExiting
×
1790
                }
1791
        }
1792
        delete(n.ntfnsByConfirmHeight, height)
1,495✔
1793

1,495✔
1794
        // Finally, we'll dispatch spend notifications for all the requests that
1,495✔
1795
        // were spent at this new block height.
1,495✔
1796
        for spendRequest := range n.spendsByHeight[height] {
1,548✔
1797
                spendSet := n.spendNotifications[spendRequest]
53✔
1798
                for _, ntfn := range spendSet.ntfns {
152✔
1799
                        err := n.dispatchSpendDetails(ntfn, spendSet.details)
99✔
1800
                        if err != nil {
99✔
1801
                                return err
×
1802
                        }
×
1803
                }
1804
        }
1805

1806
        return nil
1,495✔
1807
}
1808

1809
// DisconnectTip handles the tip of the current chain being disconnected during
1810
// a chain reorganization. If any watched requests were included in this block,
1811
// internal structures are updated to ensure confirmation/spend notifications
1812
// are consumed (if not already), and reorg notifications are dispatched
1813
// instead. Confirmation/spend notifications will be dispatched again upon block
1814
// inclusion.
1815
func (n *TxNotifier) DisconnectTip(blockHeight uint32) error {
98✔
1816
        select {
98✔
1817
        case <-n.quit:
×
1818
                return ErrTxNotifierExiting
×
1819
        default:
98✔
1820
        }
1821

1822
        n.Lock()
98✔
1823
        defer n.Unlock()
98✔
1824

98✔
1825
        if blockHeight != n.currentHeight {
98✔
1826
                return fmt.Errorf("received blocks out of order: "+
×
1827
                        "current height=%d, disconnected height=%d",
×
1828
                        n.currentHeight, blockHeight)
×
1829
        }
×
1830
        n.currentHeight--
98✔
1831
        n.reorgDepth++
98✔
1832

98✔
1833
        // With the block disconnected, we'll update the confirm and spend hints
98✔
1834
        // for our notification requests to reflect the new height, except for
98✔
1835
        // those that have confirmed/spent at previous heights.
98✔
1836
        n.updateHints(blockHeight)
98✔
1837

98✔
1838
        // We'll go through all of our watched confirmation requests and attempt
98✔
1839
        // to drain their notification channels to ensure sending notifications
98✔
1840
        // to the clients is always non-blocking.
98✔
1841
        for txHash := range n.confsByInitialHeight[blockHeight] {
113✔
1842
                // If the transaction/output script has been reorged out
15✔
1843
                // of the chain, we'll make sure to remove the cached
15✔
1844
                // confirmation details to prevent notifying clients
15✔
1845
                // with old information.
15✔
1846
                confSet := n.confNotifications[txHash]
15✔
1847
                confSet.details = nil
15✔
1848

15✔
1849
                for _, ntfn := range confSet.ntfns {
30✔
1850
                        // If the caller requested intermediate confirmations,
15✔
1851
                        // drain all pending confirmation events so that
15✔
1852
                        // subsequent sends (after a reorg) remain non‑blocking.
15✔
1853
                        if ntfn.allConfirmations {
17✔
1854
                        drain:
2✔
1855
                                for {
6✔
1856
                                        select {
4✔
1857
                                        case <-ntfn.Event.Confirmed:
2✔
NEW
1858
                                        case <-n.quit:
×
NEW
1859
                                                return ErrTxNotifierExiting
×
1860
                                        default:
2✔
1861
                                                break drain
2✔
1862
                                        }
1863
                                }
1864
                        }
1865

1866
                        // We also reset the num of confs left.
1867
                        ntfn.numConfsLeft = ntfn.NumConfirmations
15✔
1868

15✔
1869
                        // Then, we'll check if the current transaction/output
15✔
1870
                        // script was included in the block currently being
15✔
1871
                        // disconnected. If it was, we'll need to dispatch a
15✔
1872
                        // reorg notification to the client.
15✔
1873
                        err := n.dispatchConfReorg(ntfn, blockHeight)
15✔
1874
                        if err != nil {
15✔
NEW
1875
                                return err
×
UNCOV
1876
                        }
×
1877
                }
1878
        }
1879

1880
        // We'll also go through our watched spend requests and attempt to drain
1881
        // their dispatched notifications to ensure dispatching notifications to
1882
        // clients later on is always non-blocking. We're only interested in
1883
        // requests whose spending transaction was included at the height being
1884
        // disconnected.
1885
        for op := range n.spendsByHeight[blockHeight] {
110✔
1886
                // Since the spending transaction is being reorged out of the
12✔
1887
                // chain, we'll need to clear out the spending details of the
12✔
1888
                // request.
12✔
1889
                spendSet := n.spendNotifications[op]
12✔
1890
                spendSet.details = nil
12✔
1891

12✔
1892
                // For all requests which have had a spend notification
12✔
1893
                // dispatched, we'll attempt to drain it and send a reorg
12✔
1894
                // notification instead.
12✔
1895
                for _, ntfn := range spendSet.ntfns {
24✔
1896
                        if err := n.dispatchSpendReorg(ntfn); err != nil {
12✔
1897
                                return err
×
1898
                        }
×
1899
                }
1900
        }
1901

1902
        // Finally, we can remove the requests that were confirmed and/or spent
1903
        // at the height being disconnected. We'll still continue to track them
1904
        // until they have been confirmed/spent and are no longer under the risk
1905
        // of being reorged out of the chain again.
1906
        delete(n.confsByInitialHeight, blockHeight)
98✔
1907
        delete(n.spendsByHeight, blockHeight)
98✔
1908

98✔
1909
        return nil
98✔
1910
}
1911

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

1940
        // Similarly, to update the height hint for all the required spend
1941
        // requests under one database transaction, we'll gather the set of
1942
        // unspent requests along with the ones that were spent at the height
1943
        // being connected/disconnected.
1944
        spendRequests := n.unspentRequests()
1,691✔
1945
        for spendRequest := range n.spendsByHeight[height] {
1,756✔
1946
                spendRequests = append(spendRequests, spendRequest)
65✔
1947
        }
65✔
1948
        err = n.spendHintCache.CommitSpendHint(n.currentHeight, spendRequests...)
1,691✔
1949
        if err != nil {
1,691✔
1950
                // The error is not fatal as this is an optimistic optimization,
×
1951
                // so we'll avoid returning an error.
×
1952
                Log.Debugf("Unable to update spend hints to %d for "+
×
1953
                        "%v: %v", n.currentHeight, spendRequests, err)
×
1954
        }
×
1955
}
1956

1957
// unconfirmedRequests returns the set of confirmation requests that are
1958
// still seen as unconfirmed by the TxNotifier.
1959
//
1960
// NOTE: This method must be called with the TxNotifier's lock held.
1961
func (n *TxNotifier) unconfirmedRequests() []ConfRequest {
1,691✔
1962
        var unconfirmed []ConfRequest
1,691✔
1963
        for confRequest, confNtfnSet := range n.confNotifications {
16,353✔
1964
                // If the notification is already aware of its confirmation
14,662✔
1965
                // details, or it's in the process of learning them, we'll skip
14,662✔
1966
                // it as we can't yet determine if it's confirmed or not.
14,662✔
1967
                if confNtfnSet.rescanStatus != rescanComplete ||
14,662✔
1968
                        confNtfnSet.details != nil {
28,633✔
1969
                        continue
13,971✔
1970
                }
1971

1972
                unconfirmed = append(unconfirmed, confRequest)
694✔
1973
        }
1974

1975
        return unconfirmed
1,691✔
1976
}
1977

1978
// unspentRequests returns the set of spend requests that are still seen as
1979
// unspent by the TxNotifier.
1980
//
1981
// NOTE: This method must be called with the TxNotifier's lock held.
1982
func (n *TxNotifier) unspentRequests() []SpendRequest {
1,691✔
1983
        var unspent []SpendRequest
1,691✔
1984
        for spendRequest, spendNtfnSet := range n.spendNotifications {
3,277✔
1985
                // If the notification is already aware of its spend details, or
1,586✔
1986
                // it's in the process of learning them, we'll skip it as we
1,586✔
1987
                // can't yet determine if it's unspent or not.
1,586✔
1988
                if spendNtfnSet.rescanStatus != rescanComplete ||
1,586✔
1989
                        spendNtfnSet.details != nil {
3,131✔
1990
                        continue
1,545✔
1991
                }
1992

1993
                unspent = append(unspent, spendRequest)
44✔
1994
        }
1995

1996
        return unspent
1,691✔
1997
}
1998

1999
// dispatchConfReorg dispatches a reorg notification to the client if the
2000
// confirmation notification was already delivered.
2001
//
2002
// NOTE: This must be called with the TxNotifier's lock held.
2003
func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
2004
        heightDisconnected uint32) error {
15✔
2005

15✔
2006
        // If the request's confirmation notification has yet to be dispatched,
15✔
2007
        // we'll need to clear its entry within the ntfnsByConfirmHeight index
15✔
2008
        // to prevent from notifying the client once the notifier reaches the
15✔
2009
        // confirmation height.
15✔
2010
        if !ntfn.dispatched {
27✔
2011
                confHeight := heightDisconnected + ntfn.NumConfirmations - 1
12✔
2012
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
12✔
2013
                if exists {
24✔
2014
                        delete(ntfnSet, ntfn)
12✔
2015
                }
12✔
2016
                return nil
12✔
2017
        }
2018

2019
        // Otherwise, the entry within the ntfnsByConfirmHeight has already been
2020
        // deleted, so we'll attempt to drain the confirmation notification to
2021
        // ensure sends to the Confirmed channel are always non-blocking.
2022
        select {
5✔
2023
        case <-ntfn.Event.Confirmed:
×
2024
        case <-n.quit:
×
2025
                return ErrTxNotifierExiting
×
2026
        default:
5✔
2027
        }
2028

2029
        ntfn.dispatched = false
5✔
2030

5✔
2031
        // Send a negative confirmation notification to the client indicating
5✔
2032
        // how many blocks have been disconnected successively.
5✔
2033
        select {
5✔
2034
        case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
5✔
2035
        case <-n.quit:
×
2036
                return ErrTxNotifierExiting
×
2037
        }
2038

2039
        return nil
5✔
2040
}
2041

2042
// dispatchSpendReorg dispatches a reorg notification to the client if a spend
2043
// notiification was already delivered.
2044
//
2045
// NOTE: This must be called with the TxNotifier's lock held.
2046
func (n *TxNotifier) dispatchSpendReorg(ntfn *SpendNtfn) error {
12✔
2047
        if !ntfn.dispatched {
12✔
2048
                return nil
×
2049
        }
×
2050

2051
        // Attempt to drain the spend notification to ensure sends to the Spend
2052
        // channel are always non-blocking.
2053
        select {
12✔
2054
        case <-ntfn.Event.Spend:
1✔
2055
        default:
11✔
2056
        }
2057

2058
        // Send a reorg notification to the client in order for them to
2059
        // correctly handle reorgs.
2060
        select {
12✔
2061
        case ntfn.Event.Reorg <- struct{}{}:
12✔
2062
        case <-n.quit:
×
2063
                return ErrTxNotifierExiting
×
2064
        }
2065

2066
        ntfn.dispatched = false
12✔
2067

12✔
2068
        return nil
12✔
2069
}
2070

2071
// TearDown is to be called when the owner of the TxNotifier is exiting. This
2072
// closes the event channels of all registered notifications that have not been
2073
// dispatched yet.
2074
func (n *TxNotifier) TearDown() {
25✔
2075
        close(n.quit)
25✔
2076

25✔
2077
        n.Lock()
25✔
2078
        defer n.Unlock()
25✔
2079

25✔
2080
        for _, confSet := range n.confNotifications {
153✔
2081
                for confID, ntfn := range confSet.ntfns {
312✔
2082
                        close(ntfn.Event.Confirmed)
184✔
2083
                        close(ntfn.Event.NegativeConf)
184✔
2084
                        close(ntfn.Event.Done)
184✔
2085
                        delete(confSet.ntfns, confID)
184✔
2086
                }
184✔
2087
        }
2088

2089
        for _, spendSet := range n.spendNotifications {
61✔
2090
                for spendID, ntfn := range spendSet.ntfns {
136✔
2091
                        close(ntfn.Event.Spend)
100✔
2092
                        close(ntfn.Event.Reorg)
100✔
2093
                        close(ntfn.Event.Done)
100✔
2094
                        delete(spendSet.ntfns, spendID)
100✔
2095
                }
100✔
2096
        }
2097
}
2098

2099
// updateConfsLeftAndNotify sends a confirmation notification to the subscriber
2100
// through the Event.Confirmed channel, but only if the caller has opted to
2101
// receive updates for all confirmations and the transaction/output script
2102
// has not yet reached the target number of confirmations.
2103
//
2104
// NOTE: must be used with the TxNotifier's lock held.
2105
func (n *TxNotifier) updateConfsLeftAndNotify(ntfn *ConfNtfn, num uint32,
2106
        details TxConfirmation) error {
703✔
2107

703✔
2108
        // Send the confirmation notification only if the caller has opted to
703✔
2109
        // receive updates for all confirmations and the transaction/output
703✔
2110
        // script has not yet reached the target number of confirmations.
703✔
2111
        if num == 0 {
882✔
2112
                Log.Debugf("Skipped confirmation notification with "+
179✔
2113
                        "numConfsLeft=0 for request %v conf_id=%v",
179✔
2114
                        ntfn.ConfRequest, ntfn.ConfID)
179✔
2115

179✔
2116
                return nil
179✔
2117
        }
179✔
2118
        if !ntfn.allConfirmations {
976✔
2119
                return nil
449✔
2120
        }
449✔
2121

2122
        // If the number left is no less than the recorded value, we can skip
2123
        // sending it as it means this same value has already been sent before.
2124
        if num >= ntfn.numConfsLeft {
79✔
2125
                Log.Debugf("Skipped dispatched confirmation (numConfsLeft=%v) "+
1✔
2126
                        "for request %v conf_id=%v", num, ntfn.ConfRequest,
1✔
2127
                        ntfn.ConfID)
1✔
2128

1✔
2129
                return nil
1✔
2130
        }
1✔
2131

2132
        // Update the number of confirmations left to the notification.
2133
        ntfn.numConfsLeft = num
77✔
2134
        details.NumConfsLeft = num
77✔
2135

77✔
2136
        select {
77✔
2137
        case ntfn.Event.Confirmed <- &details:
77✔
2138
        case <-n.quit:
×
2139
                return ErrTxNotifierExiting
×
2140
        }
2141

2142
        return nil
77✔
2143
}
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