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

lightningnetwork / lnd / 17917482292

22 Sep 2025 01:50PM UTC coverage: 56.562% (-10.1%) from 66.668%
17917482292

Pull #10182

github

web-flow
Merge 9efe3bd8c into 055fb436e
Pull Request #10182: Aux feature bits

32 of 68 new or added lines in 5 files covered. (47.06%)

29734 existing lines in 467 files now uncovered.

98449 of 174056 relevant lines covered (56.56%)

1.18 hits per line

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

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

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

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

UNCOV
221
                return false
×
222
        }
223

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

UNCOV
228
        return scriptMatches()
×
229
}
230

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

354
        return r, nil
2✔
355
}
356

357
// String returns the string representation of the SpendRequest.
358
func (r SpendRequest) String() string {
2✔
359
        var (
2✔
360
                outpointStr = fmt.Sprintf("%v", r.OutPoint)
2✔
361
                scriptStr   = fmt.Sprintf("%v", r.PkScript)
2✔
362
        )
2✔
363

2✔
364
        if r.OutPoint == ZeroOutPoint {
2✔
365
                outpointStr = "<zero>"
×
366
        }
×
367

368
        // If the pk script is all zeros, we blank the pk script.
369
        // Currently we do not support taproot pk scripts for notifications.
370
        if r.PkScript == ZeroTaprootPkScript {
4✔
371
                scriptStr = "<zero> (taproot pk script not supported)"
2✔
372
        }
2✔
373

374
        return fmt.Sprintf("outpoint=%s, script=%s", outpointStr, scriptStr)
2✔
375
}
376

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

UNCOV
391
                return false, 0, nil
×
392
        }
393

UNCOV
394
        for i, txIn := range tx.TxIn {
×
UNCOV
395
                pkScript, err := txscript.ComputePkScript(
×
UNCOV
396
                        txIn.SignatureScript, txIn.Witness,
×
UNCOV
397
                )
×
UNCOV
398
                if err == txscript.ErrUnsupportedScriptType {
×
399
                        continue
×
400
                }
UNCOV
401
                if err != nil {
×
402
                        return false, 0, err
×
403
                }
×
404

UNCOV
405
                if bytes.Equal(pkScript.Script(), r.PkScript.Script()) {
×
406
                        return true, uint32(i), nil
×
407
                }
×
408
        }
409

UNCOV
410
        return false, 0, nil
×
411
}
412

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

421
        // SpendRequest represents either the outpoint or script we should
422
        // detect the spend of.
423
        SpendRequest
424

425
        // Event contains references to the channels that the notifications are
426
        // to be sent over.
427
        Event *SpendEvent
428

429
        // HeightHint is the earliest height in the chain that we expect to find
430
        // the spending transaction of the specified outpoint/output script.
431
        // This value will be overridden by the spend hint cache if it contains
432
        // an entry for it.
433
        HeightHint uint32
434

435
        // dispatched signals whether a spend notification has been dispatched
436
        // to the client.
437
        dispatched bool
438
}
439

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

449
        // StartHeight specified the block height at which to begin the
450
        // historical rescan.
451
        StartHeight uint32
452

453
        // EndHeight specifies the last block height (inclusive) that the
454
        // historical rescan should consider.
455
        EndHeight uint32
456
}
457

458
// SpendRegistration encompasses all of the information required for callers to
459
// retrieve details about a spend event.
460
type SpendRegistration struct {
461
        // Event contains references to the channels that the notifications are
462
        // to be sent over.
463
        Event *SpendEvent
464

465
        // HistoricalDispatch, if non-nil, signals to the client who registered
466
        // the notification that they are responsible for attempting to manually
467
        // rescan blocks for the txid/output script between the start and end
468
        // heights.
469
        HistoricalDispatch *HistoricalSpendDispatch
470

471
        // Height is the height of the TxNotifier at the time the spend
472
        // notification was registered. This can be used so that backends can
473
        // request to be notified of spends from this point forwards.
474
        Height uint32
475
}
476

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

486
        // currentHeight is the height of the tracked blockchain. It is used to
487
        // determine the number of confirmations a tx has and ensure blocks are
488
        // connected and disconnected in order.
489
        currentHeight uint32
490

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

498
        // reorgDepth is the depth of a chain organization that this system is
499
        // being informed of. This is incremented as long as a sequence of
500
        // blocks are disconnected without being interrupted by a new block.
501
        reorgDepth uint32
502

503
        // confNotifications is an index of confirmation notification requests
504
        // by transaction hash/output script.
505
        confNotifications map[ConfRequest]*confNtfnSet
506

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

514
        // ntfnsByConfirmHeight is an index of notification requests by the
515
        // height at which the transaction/output script will have sufficient
516
        // confirmations.
517
        ntfnsByConfirmHeight map[uint32]map[*ConfNtfn]struct{}
518

519
        // spendNotifications is an index of all active notification requests
520
        // per outpoint/output script.
521
        spendNotifications map[SpendRequest]*spendNtfnSet
522

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

529
        // confirmHintCache is a cache used to maintain the latest height hints
530
        // for transactions/output scripts. Each height hint represents the
531
        // earliest height at which they scripts could have been confirmed
532
        // within the chain.
533
        confirmHintCache ConfirmHintCache
534

535
        // spendHintCache is a cache used to maintain the latest height hints
536
        // for outpoints/output scripts. Each height hint represents the
537
        // earliest height at which they could have been spent within the chain.
538
        spendHintCache SpendHintCache
539

540
        // quit is closed in order to signal that the notifier is gracefully
541
        // exiting.
542
        quit chan struct{}
543

544
        sync.Mutex
545
}
546

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

2✔
555
        return &TxNotifier{
2✔
556
                currentHeight:        startHeight,
2✔
557
                reorgSafetyLimit:     reorgSafetyLimit,
2✔
558
                confNotifications:    make(map[ConfRequest]*confNtfnSet),
2✔
559
                confsByInitialHeight: make(map[uint32]map[ConfRequest]struct{}),
2✔
560
                ntfnsByConfirmHeight: make(map[uint32]map[*ConfNtfn]struct{}),
2✔
561
                spendNotifications:   make(map[SpendRequest]*spendNtfnSet),
2✔
562
                spendsByHeight:       make(map[uint32]map[SpendRequest]struct{}),
2✔
563
                confirmHintCache:     confirmHintCache,
2✔
564
                spendHintCache:       spendHintCache,
2✔
565
                quit:                 make(chan struct{}),
2✔
566
        }
2✔
567
}
2✔
568

569
// newConfNtfn validates all of the parameters required to successfully create
570
// and register a confirmation notification.
571
func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash,
572
        pkScript []byte, numConfs, heightHint uint32,
573
        opts *NotifierOptions) (*ConfNtfn, error) {
2✔
574

2✔
575
        // An accompanying output script must always be provided.
2✔
576
        if len(pkScript) == 0 {
2✔
UNCOV
577
                return nil, ErrNoScript
×
UNCOV
578
        }
×
579

580
        // Enforce that we will not dispatch confirmations beyond the reorg
581
        // safety limit.
582
        if numConfs == 0 || numConfs > n.reorgSafetyLimit {
2✔
UNCOV
583
                return nil, ErrNumConfsOutOfRange
×
UNCOV
584
        }
×
585

586
        // A height hint must be provided to prevent scanning from the genesis
587
        // block.
588
        if heightHint == 0 {
2✔
UNCOV
589
                return nil, ErrNoHeightHint
×
UNCOV
590
        }
×
591

592
        // Ensure the output script is of a supported type.
593
        confRequest, err := NewConfRequest(txid, pkScript)
2✔
594
        if err != nil {
2✔
595
                return nil, err
×
596
        }
×
597

598
        confID := atomic.AddUint64(&n.confClientCounter, 1)
2✔
599
        return &ConfNtfn{
2✔
600
                ConfID:           confID,
2✔
601
                ConfRequest:      confRequest,
2✔
602
                NumConfirmations: numConfs,
2✔
603
                Event: NewConfirmationEvent(numConfs, func() {
4✔
604
                        n.CancelConf(confRequest, confID)
2✔
605
                }),
2✔
606
                HeightHint:   heightHint,
607
                includeBlock: opts.IncludeBlock,
608
                numConfsLeft: numConfs,
609
        }, nil
610
}
611

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

2✔
624
        select {
2✔
UNCOV
625
        case <-n.quit:
×
UNCOV
626
                return nil, ErrTxNotifierExiting
×
627
        default:
2✔
628
        }
629

630
        opts := DefaultNotifierOptions()
2✔
631
        for _, optFunc := range optFuncs {
4✔
632
                optFunc(opts)
2✔
633
        }
2✔
634

635
        // We'll start by performing a series of validation checks.
636
        ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint, opts)
2✔
637
        if err != nil {
2✔
UNCOV
638
                return nil, err
×
UNCOV
639
        }
×
640

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

659
        Log.Infof("New confirmation subscription: conf_id=%d, %v, "+
2✔
660
                "num_confs=%v height_hint=%d", ntfn.ConfID, ntfn.ConfRequest,
2✔
661
                numConfs, startHeight)
2✔
662

2✔
663
        n.Lock()
2✔
664
        defer n.Unlock()
2✔
665

2✔
666
        confSet, ok := n.confNotifications[ntfn.ConfRequest]
2✔
667
        if !ok {
4✔
668
                // If this is the first registration for this request, construct
2✔
669
                // a confSet to coalesce all notifications for the same request.
2✔
670
                confSet = newConfNtfnSet()
2✔
671
                n.confNotifications[ntfn.ConfRequest] = confSet
2✔
672
        }
2✔
673
        confSet.ntfns[ntfn.ConfID] = ntfn
2✔
674

2✔
675
        switch confSet.rescanStatus {
2✔
676

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

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

2✔
696
                        confDetails = &confDetailsCopy
2✔
697
                }
2✔
698

699
                // Deliver the details to the whole conf set where this ntfn
700
                // lives in.
701
                for _, subscriber := range confSet.ntfns {
4✔
702
                        err := n.dispatchConfDetails(subscriber, confDetails)
2✔
703
                        if err != nil {
2✔
704
                                return nil, err
×
705
                        }
×
706
                }
707

708
                return &ConfRegistration{
2✔
709
                        Event:              ntfn.Event,
2✔
710
                        HistoricalDispatch: nil,
2✔
711
                        Height:             n.currentHeight,
2✔
712
                }, nil
2✔
713

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

2✔
721
                return &ConfRegistration{
2✔
722
                        Event:              ntfn.Event,
2✔
723
                        HistoricalDispatch: nil,
2✔
724
                        Height:             n.currentHeight,
2✔
725
                }, nil
2✔
726

727
        // If no rescan has been dispatched, attempt to do so now.
728
        case rescanNotStarted:
2✔
729
        }
730

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

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

751
        Log.Debugf("Dispatching historical confirmation rescan for %v",
2✔
752
                ntfn.ConfRequest)
2✔
753

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

2✔
764
        // Set this confSet's status to pending, ensuring subsequent
2✔
765
        // registrations don't also attempt a dispatch.
2✔
766
        confSet.rescanStatus = rescanPending
2✔
767

2✔
768
        return &ConfRegistration{
2✔
769
                Event:              ntfn.Event,
2✔
770
                HistoricalDispatch: dispatch,
2✔
771
                Height:             n.currentHeight,
2✔
772
        }, nil
2✔
773
}
774

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

784
        n.Lock()
2✔
785
        defer n.Unlock()
2✔
786

2✔
787
        confSet, ok := n.confNotifications[confRequest]
2✔
788
        if !ok {
2✔
789
                return
×
790
        }
×
791
        ntfn, ok := confSet.ntfns[confID]
2✔
792
        if !ok {
2✔
793
                return
×
794
        }
×
795

796
        Log.Debugf("Canceling confirmation notification: conf_id=%d, %v",
2✔
797
                confID, confRequest)
2✔
798

2✔
799
        // We'll close all the notification channels to let the client know
2✔
800
        // their cancel request has been fulfilled.
2✔
801
        close(ntfn.Event.Confirmed)
2✔
802
        close(ntfn.Event.Updates)
2✔
803
        close(ntfn.Event.NegativeConf)
2✔
804

2✔
805
        // Finally, we'll clean up any lingering references to this
2✔
806
        // notification.
2✔
807
        delete(confSet.ntfns, confID)
2✔
808

2✔
809
        // Remove the queued confirmation notification if the transaction has
2✔
810
        // already confirmed, but hasn't met its required number of
2✔
811
        // confirmations.
2✔
812
        if confSet.details != nil {
4✔
813
                confHeight := confSet.details.BlockHeight +
2✔
814
                        ntfn.NumConfirmations - 1
2✔
815
                delete(n.ntfnsByConfirmHeight[confHeight], ntfn)
2✔
816
        }
2✔
817
}
818

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

2✔
829
        select {
2✔
830
        case <-n.quit:
×
831
                return ErrTxNotifierExiting
×
832
        default:
2✔
833
        }
834

835
        // Ensure we hold the lock throughout handling the notification to
836
        // prevent the notifier from advancing its height underneath us.
837
        n.Lock()
2✔
838
        defer n.Unlock()
2✔
839

2✔
840
        // First, we'll determine whether we have an active confirmation
2✔
841
        // notification for the given txid/script.
2✔
842
        confSet, ok := n.confNotifications[confRequest]
2✔
843
        if !ok {
2✔
844
                return fmt.Errorf("confirmation notification for %v not found",
×
845
                        confRequest)
×
846
        }
×
847

848
        // If the confirmation details were already found at tip, all existing
849
        // notifications will have been dispatched or queued for dispatch. We
850
        // can exit early to avoid sending too many notifications on the
851
        // buffered channels.
852
        if confSet.details != nil {
3✔
853
                return nil
1✔
854
        }
1✔
855

856
        // The historical dispatch has been completed for this confSet. We'll
857
        // update the rescan status and cache any details that were found. If
858
        // the details are nil, that implies we did not find them and will
859
        // continue to watch for them at tip.
860
        confSet.rescanStatus = rescanComplete
2✔
861

2✔
862
        // The notifier has yet to reach the height at which the
2✔
863
        // transaction/output script was included in a block, so we should defer
2✔
864
        // until handling it then within ConnectTip.
2✔
865
        if details == nil {
4✔
866
                Log.Debugf("Confirmation details for %v not found during "+
2✔
867
                        "historical dispatch, waiting to dispatch at tip",
2✔
868
                        confRequest)
2✔
869

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

883
                return nil
2✔
884
        }
885

886
        if details.BlockHeight > n.currentHeight {
2✔
UNCOV
887
                Log.Debugf("Confirmation details for %v found above current "+
×
UNCOV
888
                        "height, waiting to dispatch at tip", confRequest)
×
UNCOV
889

×
UNCOV
890
                return nil
×
UNCOV
891
        }
×
892

893
        Log.Debugf("Updating confirmation details for %v", confRequest)
2✔
894

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

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

918
                err = n.dispatchConfDetails(ntfn, &confDetails)
2✔
919
                if err != nil {
2✔
920
                        return err
×
921
                }
×
922
        }
923

924
        return nil
2✔
925
}
926

927
// dispatchConfDetails attempts to cache and dispatch details to a particular
928
// client if the transaction/output script has sufficiently confirmed. If the
929
// provided details are nil, this method will be a no-op.
930
func (n *TxNotifier) dispatchConfDetails(
931
        ntfn *ConfNtfn, details *TxConfirmation) error {
2✔
932

2✔
933
        // If there are no conf details to dispatch or if the notification has
2✔
934
        // already been dispatched, then we can skip dispatching to this
2✔
935
        // client.
2✔
936
        if details == nil {
4✔
937
                Log.Debugf("Skipped dispatching nil conf details for request "+
2✔
938
                        "%v, conf_id=%v", ntfn.ConfRequest, ntfn.ConfID)
2✔
939

2✔
940
                return nil
2✔
941
        }
2✔
942

943
        if ntfn.dispatched {
4✔
944
                Log.Debugf("Skipped dispatched conf details for request %v "+
2✔
945
                        "conf_id=%v", ntfn.ConfRequest, ntfn.ConfID)
2✔
946

2✔
947
                return nil
2✔
948
        }
2✔
949

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

2✔
959
                // We'll send a 0 value to the Updates channel,
2✔
960
                // indicating that the transaction/output script has already
2✔
961
                // been confirmed, and include the block height at which the
2✔
962
                // transaction was included.
2✔
963
                err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
2✔
964
                        NumConfsLeft: 0,
2✔
965
                        BlockHeight:  details.BlockHeight,
2✔
966
                })
2✔
967
                if err != nil {
2✔
968
                        return err
×
969
                }
×
970

971
                select {
2✔
972
                case ntfn.Event.Confirmed <- details:
2✔
973
                        ntfn.dispatched = true
2✔
974
                case <-n.quit:
×
975
                        return ErrTxNotifierExiting
×
976
                }
977
        } else {
2✔
978
                Log.Debugf("Queueing %v confirmation notification for %v at "+
2✔
979
                        "tip", ntfn.NumConfirmations, ntfn.ConfRequest)
2✔
980

2✔
981
                // Otherwise, we'll keep track of the notification
2✔
982
                // request by the height at which we should dispatch the
2✔
983
                // confirmation notification.
2✔
984
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
2✔
985
                if !exists {
4✔
986
                        ntfnSet = make(map[*ConfNtfn]struct{})
2✔
987
                        n.ntfnsByConfirmHeight[confHeight] = ntfnSet
2✔
988
                }
2✔
989
                ntfnSet[ntfn] = struct{}{}
2✔
990

2✔
991
                // We'll also send an update to the client of how many
2✔
992
                // confirmations are left for the transaction/output script to
2✔
993
                // be confirmed.
2✔
994
                numConfsLeft := confHeight - n.currentHeight
2✔
995
                err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
2✔
996
                        NumConfsLeft: numConfsLeft,
2✔
997
                        BlockHeight:  details.BlockHeight,
2✔
998
                })
2✔
999
                if err != nil {
2✔
1000
                        return err
×
1001
                }
×
1002
        }
1003

1004
        // As a final check, we'll also watch the transaction/output script if
1005
        // it's still possible for it to get reorged out of the chain.
1006
        reorgSafeHeight := details.BlockHeight + n.reorgSafetyLimit
2✔
1007
        if reorgSafeHeight > n.currentHeight {
4✔
1008
                txSet, exists := n.confsByInitialHeight[details.BlockHeight]
2✔
1009
                if !exists {
4✔
1010
                        txSet = make(map[ConfRequest]struct{})
2✔
1011
                        n.confsByInitialHeight[details.BlockHeight] = txSet
2✔
1012
                }
2✔
1013
                txSet[ntfn.ConfRequest] = struct{}{}
2✔
1014
        }
1015

1016
        return nil
2✔
1017
}
1018

1019
// newSpendNtfn validates all of the parameters required to successfully create
1020
// and register a spend notification.
1021
func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint,
1022
        pkScript []byte, heightHint uint32) (*SpendNtfn, error) {
2✔
1023

2✔
1024
        // An accompanying output script must always be provided.
2✔
1025
        if len(pkScript) == 0 {
2✔
UNCOV
1026
                return nil, ErrNoScript
×
UNCOV
1027
        }
×
1028

1029
        // A height hint must be provided to prevent scanning from the genesis
1030
        // block.
1031
        if heightHint == 0 {
2✔
UNCOV
1032
                return nil, ErrNoHeightHint
×
UNCOV
1033
        }
×
1034

1035
        // Ensure the output script is of a supported type.
1036
        spendRequest, err := NewSpendRequest(outpoint, pkScript)
2✔
1037
        if err != nil {
4✔
1038
                return nil, err
2✔
1039
        }
2✔
1040

1041
        spendID := atomic.AddUint64(&n.spendClientCounter, 1)
2✔
1042
        return &SpendNtfn{
2✔
1043
                SpendID:      spendID,
2✔
1044
                SpendRequest: spendRequest,
2✔
1045
                Event: NewSpendEvent(func() {
4✔
1046
                        n.CancelSpend(spendRequest, spendID)
2✔
1047
                }),
2✔
1048
                HeightHint: heightHint,
1049
        }, nil
1050
}
1051

1052
// RegisterSpend handles a new spend notification request. The client will be
1053
// notified once the outpoint/output script is detected as spent within the
1054
// chain.
1055
//
1056
// NOTE: If the outpoint/output script has already been spent within the chain
1057
// before the notifier's current tip, the spend details must be provided with
1058
// the UpdateSpendDetails method, otherwise we will wait for the outpoint/output
1059
// script to be spent at tip, even though it already has.
1060
func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte,
1061
        heightHint uint32) (*SpendRegistration, error) {
2✔
1062

2✔
1063
        select {
2✔
UNCOV
1064
        case <-n.quit:
×
UNCOV
1065
                return nil, ErrTxNotifierExiting
×
1066
        default:
2✔
1067
        }
1068

1069
        // We'll start by performing a series of validation checks.
1070
        ntfn, err := n.newSpendNtfn(outpoint, pkScript, heightHint)
2✔
1071
        if err != nil {
4✔
1072
                return nil, err
2✔
1073
        }
2✔
1074

1075
        // Before proceeding to register the notification, we'll query our spend
1076
        // hint cache to determine whether a better one exists.
1077
        startHeight := ntfn.HeightHint
2✔
1078
        hint, err := n.spendHintCache.QuerySpendHint(ntfn.SpendRequest)
2✔
1079
        if err == nil {
4✔
1080
                if hint > startHeight {
4✔
1081
                        Log.Debugf("Using height hint %d retrieved from cache "+
2✔
1082
                                "for %v instead of %d for spend subscription",
2✔
1083
                                hint, ntfn.SpendRequest, startHeight)
2✔
1084
                        startHeight = hint
2✔
1085
                }
2✔
1086
        } else if err != ErrSpendHintNotFound {
2✔
1087
                Log.Errorf("Unable to query spend hint for %v: %v",
×
1088
                        ntfn.SpendRequest, err)
×
1089
        }
×
1090

1091
        n.Lock()
2✔
1092
        defer n.Unlock()
2✔
1093

2✔
1094
        Log.Debugf("New spend subscription: spend_id=%d, %v, height_hint=%d",
2✔
1095
                ntfn.SpendID, ntfn.SpendRequest, startHeight)
2✔
1096

2✔
1097
        // Keep track of the notification request so that we can properly
2✔
1098
        // dispatch a spend notification later on.
2✔
1099
        spendSet, ok := n.spendNotifications[ntfn.SpendRequest]
2✔
1100
        if !ok {
4✔
1101
                // If this is the first registration for the request, we'll
2✔
1102
                // construct a spendNtfnSet to coalesce all notifications.
2✔
1103
                spendSet = newSpendNtfnSet()
2✔
1104
                n.spendNotifications[ntfn.SpendRequest] = spendSet
2✔
1105
        }
2✔
1106
        spendSet.ntfns[ntfn.SpendID] = ntfn
2✔
1107

2✔
1108
        // We'll now let the caller know whether a historical rescan is needed
2✔
1109
        // depending on the current rescan status.
2✔
1110
        switch spendSet.rescanStatus {
2✔
1111

1112
        // If the spending details for this request have already been determined
1113
        // and cached, then we can use them to immediately dispatch the spend
1114
        // notification to the client.
1115
        case rescanComplete:
2✔
1116
                Log.Debugf("Attempting to dispatch spend for %v on "+
2✔
1117
                        "registration since rescan has finished",
2✔
1118
                        ntfn.SpendRequest)
2✔
1119

2✔
1120
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
2✔
1121
                if err != nil {
2✔
1122
                        return nil, err
×
1123
                }
×
1124

1125
                return &SpendRegistration{
2✔
1126
                        Event:              ntfn.Event,
2✔
1127
                        HistoricalDispatch: nil,
2✔
1128
                        Height:             n.currentHeight,
2✔
1129
                }, nil
2✔
1130

1131
        // If there is an active rescan to determine whether the request has
1132
        // been spent, then we won't trigger another one.
1133
        case rescanPending:
2✔
1134
                Log.Debugf("Waiting for pending rescan to finish before "+
2✔
1135
                        "notifying %v at tip", ntfn.SpendRequest)
2✔
1136

2✔
1137
                return &SpendRegistration{
2✔
1138
                        Event:              ntfn.Event,
2✔
1139
                        HistoricalDispatch: nil,
2✔
1140
                        Height:             n.currentHeight,
2✔
1141
                }, nil
2✔
1142

1143
        // Otherwise, we'll fall through and let the caller know that a rescan
1144
        // should be dispatched to determine whether the request has already
1145
        // been spent.
1146
        case rescanNotStarted:
2✔
1147
        }
1148

1149
        // However, if the spend hint, either provided by the caller or
1150
        // retrieved from the cache, is found to be at a later height than the
1151
        // TxNotifier is aware of, then we'll refrain from dispatching a
1152
        // historical rescan and wait for the spend to come in at tip.
1153
        if startHeight > n.currentHeight {
2✔
UNCOV
1154
                Log.Debugf("Spend hint of %d for %v is above current height %d",
×
UNCOV
1155
                        startHeight, ntfn.SpendRequest, n.currentHeight)
×
UNCOV
1156

×
UNCOV
1157
                // We'll also set the rescan status as complete to ensure that
×
UNCOV
1158
                // spend hints for this request get updated upon
×
UNCOV
1159
                // connected/disconnected blocks.
×
UNCOV
1160
                spendSet.rescanStatus = rescanComplete
×
UNCOV
1161
                return &SpendRegistration{
×
UNCOV
1162
                        Event:              ntfn.Event,
×
UNCOV
1163
                        HistoricalDispatch: nil,
×
UNCOV
1164
                        Height:             n.currentHeight,
×
UNCOV
1165
                }, nil
×
UNCOV
1166
        }
×
1167

1168
        // We'll set the rescan status to pending to ensure subsequent
1169
        // notifications don't also attempt a historical dispatch.
1170
        spendSet.rescanStatus = rescanPending
2✔
1171

2✔
1172
        Log.Debugf("Dispatching historical spend rescan for %v, start=%d, "+
2✔
1173
                "end=%d", ntfn.SpendRequest, startHeight, n.currentHeight)
2✔
1174

2✔
1175
        return &SpendRegistration{
2✔
1176
                Event: ntfn.Event,
2✔
1177
                HistoricalDispatch: &HistoricalSpendDispatch{
2✔
1178
                        SpendRequest: ntfn.SpendRequest,
2✔
1179
                        StartHeight:  startHeight,
2✔
1180
                        EndHeight:    n.currentHeight,
2✔
1181
                },
2✔
1182
                Height: n.currentHeight,
2✔
1183
        }, nil
2✔
1184
}
1185

1186
// CancelSpend cancels an existing request for a spend notification of an
1187
// outpoint/output script. The request is identified by its spend ID.
1188
func (n *TxNotifier) CancelSpend(spendRequest SpendRequest, spendID uint64) {
2✔
1189
        select {
2✔
1190
        case <-n.quit:
×
1191
                return
×
1192
        default:
2✔
1193
        }
1194

1195
        n.Lock()
2✔
1196
        defer n.Unlock()
2✔
1197

2✔
1198
        spendSet, ok := n.spendNotifications[spendRequest]
2✔
1199
        if !ok {
2✔
1200
                return
×
1201
        }
×
1202
        ntfn, ok := spendSet.ntfns[spendID]
2✔
1203
        if !ok {
2✔
1204
                return
×
1205
        }
×
1206

1207
        Log.Debugf("Canceling spend notification: spend_id=%d, %v", spendID,
2✔
1208
                spendRequest)
2✔
1209

2✔
1210
        // We'll close all the notification channels to let the client know
2✔
1211
        // their cancel request has been fulfilled.
2✔
1212
        close(ntfn.Event.Spend)
2✔
1213
        close(ntfn.Event.Reorg)
2✔
1214
        close(ntfn.Event.Done)
2✔
1215
        delete(spendSet.ntfns, spendID)
2✔
1216
}
1217

1218
// ProcessRelevantSpendTx processes a transaction provided externally. This will
1219
// check whether the transaction is relevant to the notifier if it spends any
1220
// outpoints/output scripts for which we currently have registered notifications
1221
// for. If it is relevant, spend notifications will be dispatched to the caller.
1222
func (n *TxNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx,
1223
        blockHeight uint32) error {
2✔
1224

2✔
1225
        select {
2✔
1226
        case <-n.quit:
×
1227
                return ErrTxNotifierExiting
×
1228
        default:
2✔
1229
        }
1230

1231
        // Ensure we hold the lock throughout handling the notification to
1232
        // prevent the notifier from advancing its height underneath us.
1233
        n.Lock()
2✔
1234
        defer n.Unlock()
2✔
1235

2✔
1236
        // We'll use a channel to coalesce all the spend requests that this
2✔
1237
        // transaction fulfills.
2✔
1238
        type spend struct {
2✔
1239
                request *SpendRequest
2✔
1240
                details *SpendDetail
2✔
1241
        }
2✔
1242

2✔
1243
        // We'll set up the onSpend filter callback to gather all the fulfilled
2✔
1244
        // spends requests within this transaction.
2✔
1245
        var spends []spend
2✔
1246
        onSpend := func(request SpendRequest, details *SpendDetail) {
4✔
1247
                spends = append(spends, spend{&request, details})
2✔
1248
        }
2✔
1249
        n.filterTx(nil, tx, blockHeight, nil, onSpend)
2✔
1250

2✔
1251
        // After the transaction has been filtered, we can finally dispatch
2✔
1252
        // notifications for each request.
2✔
1253
        for _, spend := range spends {
4✔
1254
                err := n.updateSpendDetails(*spend.request, spend.details)
2✔
1255
                if err != nil {
2✔
1256
                        return err
×
1257
                }
×
1258
        }
1259

1260
        return nil
2✔
1261
}
1262

1263
// UpdateSpendDetails attempts to update the spend details for all active spend
1264
// notification requests for an outpoint/output script. This method should be
1265
// used once a historical scan of the chain has finished. If the historical scan
1266
// did not find a spending transaction for it, the spend details may be nil.
1267
//
1268
// NOTE: A notification request for the outpoint/output script must be
1269
// registered first to ensure notifications are delivered.
1270
func (n *TxNotifier) UpdateSpendDetails(spendRequest SpendRequest,
1271
        details *SpendDetail) error {
2✔
1272

2✔
1273
        select {
2✔
1274
        case <-n.quit:
×
1275
                return ErrTxNotifierExiting
×
1276
        default:
2✔
1277
        }
1278

1279
        // Ensure we hold the lock throughout handling the notification to
1280
        // prevent the notifier from advancing its height underneath us.
1281
        n.Lock()
2✔
1282
        defer n.Unlock()
2✔
1283

2✔
1284
        return n.updateSpendDetails(spendRequest, details)
2✔
1285
}
1286

1287
// updateSpendDetails attempts to update the spend details for all active spend
1288
// notification requests for an outpoint/output script. This method should be
1289
// used once a historical scan of the chain has finished. If the historical scan
1290
// did not find a spending transaction for it, the spend details may be nil.
1291
//
1292
// NOTE: This method must be called with the TxNotifier's lock held.
1293
func (n *TxNotifier) updateSpendDetails(spendRequest SpendRequest,
1294
        details *SpendDetail) error {
2✔
1295

2✔
1296
        // Mark the ongoing historical rescan for this request as finished. This
2✔
1297
        // will allow us to update the spend hints for it at tip.
2✔
1298
        spendSet, ok := n.spendNotifications[spendRequest]
2✔
1299
        if !ok {
2✔
UNCOV
1300
                return fmt.Errorf("spend notification for %v not found",
×
UNCOV
1301
                        spendRequest)
×
UNCOV
1302
        }
×
1303

1304
        // If the spend details have already been found either at tip, then the
1305
        // notifications should have already been dispatched, so we can exit
1306
        // early to prevent sending duplicate notifications.
1307
        if spendSet.details != nil {
4✔
1308
                return nil
2✔
1309
        }
2✔
1310

1311
        // Since the historical rescan has completed for this request, we'll
1312
        // mark its rescan status as complete in order to ensure that the
1313
        // TxNotifier can properly update its spend hints upon
1314
        // connected/disconnected blocks.
1315
        spendSet.rescanStatus = rescanComplete
2✔
1316

2✔
1317
        // If the historical rescan was not able to find a spending transaction
2✔
1318
        // for this request, then we can track the spend at tip.
2✔
1319
        if details == nil {
4✔
1320
                // We'll commit the current height as the spend hint to prevent
2✔
1321
                // another potentially long rescan if we restart before a new
2✔
1322
                // block comes in.
2✔
1323
                err := n.spendHintCache.CommitSpendHint(
2✔
1324
                        n.currentHeight, spendRequest,
2✔
1325
                )
2✔
1326
                if err != nil {
2✔
1327
                        // The error is not fatal as this is an optimistic
×
1328
                        // optimization, so we'll avoid returning an error.
×
1329
                        Log.Debugf("Unable to update spend hint to %d for %v: %v",
×
1330
                                n.currentHeight, spendRequest, err)
×
1331
                }
×
1332

1333
                Log.Debugf("Updated spend hint to height=%v for unconfirmed "+
2✔
1334
                        "spend request %v", n.currentHeight, spendRequest)
2✔
1335
                return nil
2✔
1336
        }
1337

1338
        // Return an error if the witness data is not present in the spending
1339
        // transaction.
1340
        //
1341
        // NOTE: if the witness stack is empty, we will do a critical log which
1342
        // shuts down the node.
1343
        if !details.HasSpenderWitness() {
2✔
1344
                Log.Criticalf("Found spending tx for outpoint=%v, but the "+
×
1345
                        "transaction %v does not have witness",
×
1346
                        spendRequest.OutPoint, details.SpendingTx.TxHash())
×
1347

×
1348
                return ErrEmptyWitnessStack
×
1349
        }
×
1350

1351
        // If the historical rescan found the spending transaction for this
1352
        // request, but it's at a later height than the notifier (this can
1353
        // happen due to latency with the backend during a reorg), then we'll
1354
        // defer handling the notification until the notifier has caught up to
1355
        // such height.
1356
        if uint32(details.SpendingHeight) > n.currentHeight {
4✔
1357
                return nil
2✔
1358
        }
2✔
1359

1360
        // Now that we've determined the request has been spent, we'll commit
1361
        // its spending height as its hint in the cache and dispatch
1362
        // notifications to all of its respective clients.
1363
        err := n.spendHintCache.CommitSpendHint(
2✔
1364
                uint32(details.SpendingHeight), spendRequest,
2✔
1365
        )
2✔
1366
        if err != nil {
2✔
1367
                // The error is not fatal as this is an optimistic optimization,
×
1368
                // so we'll avoid returning an error.
×
1369
                Log.Debugf("Unable to update spend hint to %d for %v: %v",
×
1370
                        details.SpendingHeight, spendRequest, err)
×
1371
        }
×
1372

1373
        Log.Debugf("Updated spend hint to height=%v for confirmed spend "+
2✔
1374
                "request %v", details.SpendingHeight, spendRequest)
2✔
1375

2✔
1376
        spendSet.details = details
2✔
1377
        for _, ntfn := range spendSet.ntfns {
4✔
1378
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
2✔
1379
                if err != nil {
2✔
1380
                        return err
×
1381
                }
×
1382
        }
1383

1384
        return nil
2✔
1385
}
1386

1387
// dispatchSpendDetails dispatches a spend notification to the client.
1388
//
1389
// NOTE: This must be called with the TxNotifier's lock held.
1390
func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail) error {
2✔
1391
        // If there are no spend details to dispatch or if the notification has
2✔
1392
        // already been dispatched, then we can skip dispatching to this client.
2✔
1393
        if details == nil || ntfn.dispatched {
4✔
1394
                Log.Debugf("Skipping dispatch of spend details(%v) for "+
2✔
1395
                        "request %v, dispatched=%v", details, ntfn.SpendRequest,
2✔
1396
                        ntfn.dispatched)
2✔
1397
                return nil
2✔
1398
        }
2✔
1399

1400
        Log.Debugf("Dispatching confirmed spend notification for %v at "+
2✔
1401
                "current height=%d: %v", ntfn.SpendRequest, n.currentHeight,
2✔
1402
                details)
2✔
1403

2✔
1404
        select {
2✔
1405
        case ntfn.Event.Spend <- details:
2✔
1406
                ntfn.dispatched = true
2✔
1407
        case <-n.quit:
×
1408
                return ErrTxNotifierExiting
×
1409
        }
1410

1411
        spendHeight := uint32(details.SpendingHeight)
2✔
1412

2✔
1413
        // We also add to spendsByHeight to notify on chain reorgs.
2✔
1414
        reorgSafeHeight := spendHeight + n.reorgSafetyLimit
2✔
1415
        if reorgSafeHeight > n.currentHeight {
4✔
1416
                txSet, exists := n.spendsByHeight[spendHeight]
2✔
1417
                if !exists {
4✔
1418
                        txSet = make(map[SpendRequest]struct{})
2✔
1419
                        n.spendsByHeight[spendHeight] = txSet
2✔
1420
                }
2✔
1421
                txSet[ntfn.SpendRequest] = struct{}{}
2✔
1422
        }
1423

1424
        return nil
2✔
1425
}
1426

1427
// ConnectTip handles a new block extending the current chain. It will go
1428
// through every transaction and determine if it is relevant to any of its
1429
// clients. A transaction can be relevant in either of the following two ways:
1430
//
1431
//  1. One of the inputs in the transaction spends an outpoint/output script
1432
//     for which we currently have an active spend registration for.
1433
//
1434
//  2. The transaction has a txid or output script for which we currently have
1435
//     an active confirmation registration for.
1436
//
1437
// In the event that the transaction is relevant, a confirmation/spend
1438
// notification will be queued for dispatch to the relevant clients.
1439
// Confirmation notifications will only be dispatched for transactions/output
1440
// scripts that have met the required number of confirmations required by the
1441
// client.
1442
//
1443
// NOTE: In order to actually dispatch the relevant transaction notifications to
1444
// clients, NotifyHeight must be called with the same block height in order to
1445
// maintain correctness.
1446
func (n *TxNotifier) ConnectTip(block *btcutil.Block,
1447
        blockHeight uint32) error {
2✔
1448

2✔
1449
        select {
2✔
1450
        case <-n.quit:
×
1451
                return ErrTxNotifierExiting
×
1452
        default:
2✔
1453
        }
1454

1455
        n.Lock()
2✔
1456
        defer n.Unlock()
2✔
1457

2✔
1458
        if blockHeight != n.currentHeight+1 {
2✔
1459
                return fmt.Errorf("received blocks out of order: "+
×
1460
                        "current height=%d, new height=%d",
×
1461
                        n.currentHeight, blockHeight)
×
1462
        }
×
1463
        n.currentHeight++
2✔
1464
        n.reorgDepth = 0
2✔
1465

2✔
1466
        // First, we'll iterate over all the transactions found in this block to
2✔
1467
        // determine if it includes any relevant transactions to the TxNotifier.
2✔
1468
        if block != nil {
4✔
1469
                Log.Debugf("Filtering %d txns for %d spend requests at "+
2✔
1470
                        "height %d", len(block.Transactions()),
2✔
1471
                        len(n.spendNotifications), blockHeight)
2✔
1472

2✔
1473
                for _, tx := range block.Transactions() {
4✔
1474
                        n.filterTx(
2✔
1475
                                block, tx, blockHeight,
2✔
1476
                                n.handleConfDetailsAtTip,
2✔
1477
                                n.handleSpendDetailsAtTip,
2✔
1478
                        )
2✔
1479
                }
2✔
1480
        }
1481

1482
        // Now that we've determined which requests were confirmed and spent
1483
        // within the new block, we can update their entries in their respective
1484
        // caches, along with all of our unconfirmed and unspent requests.
1485
        n.updateHints(blockHeight)
2✔
1486

2✔
1487
        // Finally, we'll clear the entries from our set of notifications for
2✔
1488
        // requests that are no longer under the risk of being reorged out of
2✔
1489
        // the chain.
2✔
1490
        if blockHeight >= n.reorgSafetyLimit {
4✔
1491
                matureBlockHeight := blockHeight - n.reorgSafetyLimit
2✔
1492
                for confRequest := range n.confsByInitialHeight[matureBlockHeight] {
2✔
UNCOV
1493
                        confSet := n.confNotifications[confRequest]
×
UNCOV
1494
                        for _, ntfn := range confSet.ntfns {
×
UNCOV
1495
                                select {
×
UNCOV
1496
                                case ntfn.Event.Done <- struct{}{}:
×
1497
                                case <-n.quit:
×
1498
                                        return ErrTxNotifierExiting
×
1499
                                }
1500
                        }
1501

UNCOV
1502
                        delete(n.confNotifications, confRequest)
×
1503
                }
1504
                delete(n.confsByInitialHeight, matureBlockHeight)
2✔
1505

2✔
1506
                for spendRequest := range n.spendsByHeight[matureBlockHeight] {
2✔
UNCOV
1507
                        spendSet := n.spendNotifications[spendRequest]
×
UNCOV
1508
                        for _, ntfn := range spendSet.ntfns {
×
UNCOV
1509
                                select {
×
UNCOV
1510
                                case ntfn.Event.Done <- struct{}{}:
×
1511
                                case <-n.quit:
×
1512
                                        return ErrTxNotifierExiting
×
1513
                                }
1514
                        }
1515

UNCOV
1516
                        Log.Debugf("Deleting mature spend request %v at "+
×
UNCOV
1517
                                "height=%d", spendRequest, blockHeight)
×
UNCOV
1518
                        delete(n.spendNotifications, spendRequest)
×
1519
                }
1520
                delete(n.spendsByHeight, matureBlockHeight)
2✔
1521
        }
1522

1523
        return nil
2✔
1524
}
1525

1526
// filterTx determines whether the transaction spends or confirms any
1527
// outstanding pending requests. The onConf and onSpend callbacks can be used to
1528
// retrieve all the requests fulfilled by this transaction as they occur.
1529
func (n *TxNotifier) filterTx(block *btcutil.Block, tx *btcutil.Tx,
1530
        blockHeight uint32, onConf func(ConfRequest, *TxConfirmation),
1531
        onSpend func(SpendRequest, *SpendDetail)) {
2✔
1532

2✔
1533
        // In order to determine if this transaction is relevant to the
2✔
1534
        // notifier, we'll check its inputs for any outstanding spend
2✔
1535
        // requests.
2✔
1536
        txHash := tx.Hash()
2✔
1537
        if onSpend != nil {
4✔
1538
                // notifyDetails is a helper closure that will construct the
2✔
1539
                // spend details of a request and hand them off to the onSpend
2✔
1540
                // callback.
2✔
1541
                notifyDetails := func(spendRequest SpendRequest,
2✔
1542
                        prevOut wire.OutPoint, inputIdx uint32) {
4✔
1543

2✔
1544
                        Log.Debugf("Found spend of %v: spend_tx=%v, "+
2✔
1545
                                "block_height=%d", spendRequest, txHash,
2✔
1546
                                blockHeight)
2✔
1547

2✔
1548
                        onSpend(spendRequest, &SpendDetail{
2✔
1549
                                SpentOutPoint:     &prevOut,
2✔
1550
                                SpenderTxHash:     txHash,
2✔
1551
                                SpendingTx:        tx.MsgTx(),
2✔
1552
                                SpenderInputIndex: inputIdx,
2✔
1553
                                SpendingHeight:    int32(blockHeight),
2✔
1554
                        })
2✔
1555
                }
2✔
1556

1557
                for i, txIn := range tx.MsgTx().TxIn {
4✔
1558
                        // We'll re-derive the script of the output being spent
2✔
1559
                        // to determine if the inputs spends any registered
2✔
1560
                        // requests.
2✔
1561
                        prevOut := txIn.PreviousOutPoint
2✔
1562
                        pkScript, err := txscript.ComputePkScript(
2✔
1563
                                txIn.SignatureScript, txIn.Witness,
2✔
1564
                        )
2✔
1565
                        if err != nil {
2✔
1566
                                continue
×
1567
                        }
1568
                        spendRequest := SpendRequest{
2✔
1569
                                OutPoint: prevOut,
2✔
1570
                                PkScript: pkScript,
2✔
1571
                        }
2✔
1572

2✔
1573
                        // If we have any, we'll record their spend height so
2✔
1574
                        // that notifications get dispatched to the respective
2✔
1575
                        // clients.
2✔
1576
                        if _, ok := n.spendNotifications[spendRequest]; ok {
4✔
1577
                                notifyDetails(spendRequest, prevOut, uint32(i))
2✔
1578
                        }
2✔
1579

1580
                        // Now try with an empty taproot key pkScript, since we
1581
                        // cannot derive the spent pkScript directly from the
1582
                        // witness. But we have the outpoint, which should be
1583
                        // enough.
1584
                        spendRequest.PkScript = ZeroTaprootPkScript
2✔
1585
                        if _, ok := n.spendNotifications[spendRequest]; ok {
4✔
1586
                                notifyDetails(spendRequest, prevOut, uint32(i))
2✔
1587
                        }
2✔
1588

1589
                        // Restore the pkScript but try with a zero outpoint
1590
                        // instead (won't be possible for Taproot).
1591
                        spendRequest.PkScript = pkScript
2✔
1592
                        spendRequest.OutPoint = ZeroOutPoint
2✔
1593
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2✔
UNCOV
1594
                                notifyDetails(spendRequest, prevOut, uint32(i))
×
UNCOV
1595
                        }
×
1596
                }
1597
        }
1598

1599
        // We'll also check its outputs to determine if there are any
1600
        // outstanding confirmation requests.
1601
        if onConf != nil {
4✔
1602
                // notifyDetails is a helper closure that will construct the
2✔
1603
                // confirmation details of a request and hand them off to the
2✔
1604
                // onConf callback.
2✔
1605
                notifyDetails := func(confRequest ConfRequest) {
4✔
1606
                        Log.Debugf("Found initial confirmation of %v: "+
2✔
1607
                                "height=%d, hash=%v", confRequest,
2✔
1608
                                blockHeight, block.Hash())
2✔
1609

2✔
1610
                        details := &TxConfirmation{
2✔
1611
                                Tx:          tx.MsgTx(),
2✔
1612
                                BlockHash:   block.Hash(),
2✔
1613
                                BlockHeight: blockHeight,
2✔
1614
                                TxIndex:     uint32(tx.Index()),
2✔
1615
                                Block:       block.MsgBlock(),
2✔
1616
                        }
2✔
1617

2✔
1618
                        onConf(confRequest, details)
2✔
1619
                }
2✔
1620

1621
                for _, txOut := range tx.MsgTx().TxOut {
4✔
1622
                        // We'll parse the script of the output to determine if
2✔
1623
                        // we have any registered requests for it or the
2✔
1624
                        // transaction itself.
2✔
1625
                        pkScript, err := txscript.ParsePkScript(txOut.PkScript)
2✔
1626
                        if err != nil {
4✔
1627
                                continue
2✔
1628
                        }
1629
                        confRequest := ConfRequest{
2✔
1630
                                TxID:     *txHash,
2✔
1631
                                PkScript: pkScript,
2✔
1632
                        }
2✔
1633

2✔
1634
                        // If we have any, we'll record their confirmed height
2✔
1635
                        // so that notifications get dispatched when they
2✔
1636
                        // reaches the clients' desired number of confirmations.
2✔
1637
                        if _, ok := n.confNotifications[confRequest]; ok {
4✔
1638
                                notifyDetails(confRequest)
2✔
1639
                        }
2✔
1640
                        confRequest.TxID = ZeroHash
2✔
1641
                        if _, ok := n.confNotifications[confRequest]; ok {
2✔
UNCOV
1642
                                notifyDetails(confRequest)
×
UNCOV
1643
                        }
×
1644
                }
1645
        }
1646
}
1647

1648
// handleConfDetailsAtTip tracks the confirmation height of the txid/output
1649
// script in order to properly dispatch a confirmation notification after
1650
// meeting each request's desired number of confirmations for all current and
1651
// future registered clients.
1652
func (n *TxNotifier) handleConfDetailsAtTip(confRequest ConfRequest,
1653
        details *TxConfirmation) {
2✔
1654

2✔
1655
        // TODO(wilmer): cancel pending historical rescans if any?
2✔
1656
        confSet := n.confNotifications[confRequest]
2✔
1657

2✔
1658
        // If we already have details for this request, we don't want to add it
2✔
1659
        // again since we have already dispatched notifications for it.
2✔
1660
        if confSet.details != nil {
3✔
1661
                Log.Warnf("Ignoring address reuse for %s at height %d.",
1✔
1662
                        confRequest, details.BlockHeight)
1✔
1663
                return
1✔
1664
        }
1✔
1665

1666
        confSet.rescanStatus = rescanComplete
2✔
1667
        confSet.details = details
2✔
1668

2✔
1669
        for _, ntfn := range confSet.ntfns {
4✔
1670
                // In the event that this notification was aware that the
2✔
1671
                // transaction/output script was reorged out of the chain, we'll
2✔
1672
                // consume the reorg notification if it hasn't been done yet
2✔
1673
                // already.
2✔
1674
                select {
2✔
1675
                case <-ntfn.Event.NegativeConf:
1✔
1676
                default:
2✔
1677
                }
1678

1679
                // We'll note this client's required number of confirmations so
1680
                // that we can notify them when expected.
1681
                confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
2✔
1682
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
2✔
1683
                if !exists {
4✔
1684
                        ntfnSet = make(map[*ConfNtfn]struct{})
2✔
1685
                        n.ntfnsByConfirmHeight[confHeight] = ntfnSet
2✔
1686
                }
2✔
1687
                ntfnSet[ntfn] = struct{}{}
2✔
1688
        }
1689

1690
        // We'll also note the initial confirmation height in order to correctly
1691
        // handle dispatching notifications when the transaction/output script
1692
        // gets reorged out of the chain.
1693
        txSet, exists := n.confsByInitialHeight[details.BlockHeight]
2✔
1694
        if !exists {
4✔
1695
                txSet = make(map[ConfRequest]struct{})
2✔
1696
                n.confsByInitialHeight[details.BlockHeight] = txSet
2✔
1697
        }
2✔
1698
        txSet[confRequest] = struct{}{}
2✔
1699
}
1700

1701
// handleSpendDetailsAtTip tracks the spend height of the outpoint/output script
1702
// in order to properly dispatch a spend notification for all current and future
1703
// registered clients.
1704
func (n *TxNotifier) handleSpendDetailsAtTip(spendRequest SpendRequest,
1705
        details *SpendDetail) {
2✔
1706

2✔
1707
        // TODO(wilmer): cancel pending historical rescans if any?
2✔
1708
        spendSet := n.spendNotifications[spendRequest]
2✔
1709
        spendSet.rescanStatus = rescanComplete
2✔
1710
        spendSet.details = details
2✔
1711

2✔
1712
        for _, ntfn := range spendSet.ntfns {
4✔
1713
                // In the event that this notification was aware that the
2✔
1714
                // spending transaction of its outpoint/output script was
2✔
1715
                // reorged out of the chain, we'll consume the reorg
2✔
1716
                // notification if it hasn't been done yet already.
2✔
1717
                select {
2✔
1718
                case <-ntfn.Event.Reorg:
×
1719
                default:
2✔
1720
                }
1721
        }
1722

1723
        // We'll note the spending height of the request in order to correctly
1724
        // handle dispatching notifications when the spending transactions gets
1725
        // reorged out of the chain.
1726
        spendHeight := uint32(details.SpendingHeight)
2✔
1727
        opSet, exists := n.spendsByHeight[spendHeight]
2✔
1728
        if !exists {
4✔
1729
                opSet = make(map[SpendRequest]struct{})
2✔
1730
                n.spendsByHeight[spendHeight] = opSet
2✔
1731
        }
2✔
1732
        opSet[spendRequest] = struct{}{}
2✔
1733

2✔
1734
        Log.Debugf("Spend request %v spent at tip=%d", spendRequest,
2✔
1735
                spendHeight)
2✔
1736
}
1737

1738
// NotifyHeight dispatches confirmation and spend notifications to the clients
1739
// who registered for a notification which has been fulfilled at the passed
1740
// height.
1741
func (n *TxNotifier) NotifyHeight(height uint32) error {
2✔
1742
        n.Lock()
2✔
1743
        defer n.Unlock()
2✔
1744

2✔
1745
        // First, we'll dispatch an update to all of the notification clients
2✔
1746
        // for our watched requests with the number of confirmations left at
2✔
1747
        // this new height.
2✔
1748
        for _, confRequests := range n.confsByInitialHeight {
4✔
1749
                for confRequest := range confRequests {
4✔
1750
                        confSet := n.confNotifications[confRequest]
2✔
1751
                        for _, ntfn := range confSet.ntfns {
4✔
1752
                                // blockHeight is the height of the block which
2✔
1753
                                // contains the transaction.
2✔
1754
                                blockHeight := confSet.details.BlockHeight
2✔
1755
                                txConfHeight := blockHeight +
2✔
1756
                                        ntfn.NumConfirmations - 1
2✔
1757
                                numConfsLeft := txConfHeight - height
2✔
1758

2✔
1759
                                // Since we don't clear notifications until
2✔
1760
                                // transactions/output scripts are no longer
2✔
1761
                                // under the risk of being reorganized out of
2✔
1762
                                // the chain, we'll skip sending updates for
2✔
1763
                                // those that have already been confirmed.
2✔
1764
                                if int32(numConfsLeft) < 0 {
4✔
1765
                                        continue
2✔
1766
                                }
1767

1768
                                err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
2✔
1769
                                        NumConfsLeft: numConfsLeft,
2✔
1770
                                        BlockHeight:  blockHeight,
2✔
1771
                                })
2✔
1772
                                if err != nil {
2✔
1773
                                        return err
×
1774
                                }
×
1775
                        }
1776
                }
1777
        }
1778

1779
        // Then, we'll dispatch notifications for all the requests that have
1780
        // become confirmed at this new block height.
1781
        for ntfn := range n.ntfnsByConfirmHeight[height] {
4✔
1782
                confSet := n.confNotifications[ntfn.ConfRequest]
2✔
1783

2✔
1784
                // The default notification we assigned above includes the
2✔
1785
                // block along with the rest of the details. However not all
2✔
1786
                // clients want the block, so we make a copy here w/o the block
2✔
1787
                // if needed so we can give clients only what they ask for.
2✔
1788
                confDetails := *confSet.details
2✔
1789
                if !ntfn.includeBlock {
4✔
1790
                        confDetails.Block = nil
2✔
1791
                }
2✔
1792

1793
                // If the `confDetails` has already been sent before, we'll
1794
                // skip it and continue processing the next one.
1795
                if ntfn.dispatched {
2✔
1796
                        Log.Debugf("Skipped dispatched conf details for "+
×
1797
                                "request %v conf_id=%v", ntfn.ConfRequest,
×
1798
                                ntfn.ConfID)
×
1799

×
1800
                        continue
×
1801
                }
1802

1803
                Log.Debugf("Dispatching %v confirmation notification for "+
2✔
1804
                        "conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
2✔
1805
                        ntfn.ConfRequest)
2✔
1806

2✔
1807
                select {
2✔
1808
                case ntfn.Event.Confirmed <- &confDetails:
2✔
1809
                        ntfn.dispatched = true
2✔
1810
                case <-n.quit:
×
1811
                        return ErrTxNotifierExiting
×
1812
                }
1813
        }
1814
        delete(n.ntfnsByConfirmHeight, height)
2✔
1815

2✔
1816
        // Finally, we'll dispatch spend notifications for all the requests that
2✔
1817
        // were spent at this new block height.
2✔
1818
        for spendRequest := range n.spendsByHeight[height] {
4✔
1819
                spendSet := n.spendNotifications[spendRequest]
2✔
1820
                for _, ntfn := range spendSet.ntfns {
4✔
1821
                        err := n.dispatchSpendDetails(ntfn, spendSet.details)
2✔
1822
                        if err != nil {
2✔
1823
                                return err
×
1824
                        }
×
1825
                }
1826
        }
1827

1828
        return nil
2✔
1829
}
1830

1831
// DisconnectTip handles the tip of the current chain being disconnected during
1832
// a chain reorganization. If any watched requests were included in this block,
1833
// internal structures are updated to ensure confirmation/spend notifications
1834
// are consumed (if not already), and reorg notifications are dispatched
1835
// instead. Confirmation/spend notifications will be dispatched again upon block
1836
// inclusion.
1837
func (n *TxNotifier) DisconnectTip(blockHeight uint32) error {
2✔
1838
        select {
2✔
1839
        case <-n.quit:
×
1840
                return ErrTxNotifierExiting
×
1841
        default:
2✔
1842
        }
1843

1844
        n.Lock()
2✔
1845
        defer n.Unlock()
2✔
1846

2✔
1847
        if blockHeight != n.currentHeight {
2✔
1848
                return fmt.Errorf("received blocks out of order: "+
×
1849
                        "current height=%d, disconnected height=%d",
×
1850
                        n.currentHeight, blockHeight)
×
1851
        }
×
1852
        n.currentHeight--
2✔
1853
        n.reorgDepth++
2✔
1854

2✔
1855
        // With the block disconnected, we'll update the confirm and spend hints
2✔
1856
        // for our notification requests to reflect the new height, except for
2✔
1857
        // those that have confirmed/spent at previous heights.
2✔
1858
        n.updateHints(blockHeight)
2✔
1859

2✔
1860
        // We'll go through all of our watched confirmation requests and attempt
2✔
1861
        // to drain their notification channels to ensure sending notifications
2✔
1862
        // to the clients is always non-blocking.
2✔
1863
        for initialHeight, txHashes := range n.confsByInitialHeight {
3✔
1864
                for txHash := range txHashes {
2✔
1865
                        // If the transaction/output script has been reorged out
1✔
1866
                        // of the chain, we'll make sure to remove the cached
1✔
1867
                        // confirmation details to prevent notifying clients
1✔
1868
                        // with old information.
1✔
1869
                        confSet := n.confNotifications[txHash]
1✔
1870
                        if initialHeight == blockHeight {
2✔
1871
                                confSet.details = nil
1✔
1872
                        }
1✔
1873

1874
                        for _, ntfn := range confSet.ntfns {
2✔
1875
                                // First, we'll attempt to drain an update
1✔
1876
                                // from each notification to ensure sends to the
1✔
1877
                                // Updates channel are always non-blocking.
1✔
1878
                                select {
1✔
1879
                                case <-ntfn.Event.Updates:
1✔
1880
                                case <-n.quit:
×
1881
                                        return ErrTxNotifierExiting
×
1882
                                default:
1✔
1883
                                }
1884

1885
                                // We also reset the num of confs update.
1886
                                ntfn.numConfsLeft = ntfn.NumConfirmations
1✔
1887

1✔
1888
                                // Then, we'll check if the current
1✔
1889
                                // transaction/output script was included in the
1✔
1890
                                // block currently being disconnected. If it
1✔
1891
                                // was, we'll need to dispatch a reorg
1✔
1892
                                // notification to the client.
1✔
1893
                                if initialHeight == blockHeight {
2✔
1894
                                        err := n.dispatchConfReorg(
1✔
1895
                                                ntfn, blockHeight,
1✔
1896
                                        )
1✔
1897
                                        if err != nil {
1✔
1898
                                                return err
×
1899
                                        }
×
1900
                                }
1901
                        }
1902
                }
1903
        }
1904

1905
        // We'll also go through our watched spend requests and attempt to drain
1906
        // their dispatched notifications to ensure dispatching notifications to
1907
        // clients later on is always non-blocking. We're only interested in
1908
        // requests whose spending transaction was included at the height being
1909
        // disconnected.
1910
        for op := range n.spendsByHeight[blockHeight] {
4✔
1911
                // Since the spending transaction is being reorged out of the
2✔
1912
                // chain, we'll need to clear out the spending details of the
2✔
1913
                // request.
2✔
1914
                spendSet := n.spendNotifications[op]
2✔
1915
                spendSet.details = nil
2✔
1916

2✔
1917
                // For all requests which have had a spend notification
2✔
1918
                // dispatched, we'll attempt to drain it and send a reorg
2✔
1919
                // notification instead.
2✔
1920
                for _, ntfn := range spendSet.ntfns {
4✔
1921
                        if err := n.dispatchSpendReorg(ntfn); err != nil {
2✔
1922
                                return err
×
1923
                        }
×
1924
                }
1925
        }
1926

1927
        // Finally, we can remove the requests that were confirmed and/or spent
1928
        // at the height being disconnected. We'll still continue to track them
1929
        // until they have been confirmed/spent and are no longer under the risk
1930
        // of being reorged out of the chain again.
1931
        delete(n.confsByInitialHeight, blockHeight)
2✔
1932
        delete(n.spendsByHeight, blockHeight)
2✔
1933

2✔
1934
        return nil
2✔
1935
}
1936

1937
// updateHints attempts to update the confirm and spend hints for all relevant
1938
// requests respectively. The height parameter is used to determine which
1939
// requests we should update based on whether a new block is being
1940
// connected/disconnected.
1941
//
1942
// NOTE: This must be called with the TxNotifier's lock held and after its
1943
// height has already been reflected by a block being connected/disconnected.
1944
func (n *TxNotifier) updateHints(height uint32) {
2✔
1945
        // TODO(wilmer): update under one database transaction.
2✔
1946
        //
2✔
1947
        // To update the height hint for all the required confirmation requests
2✔
1948
        // under one database transaction, we'll gather the set of unconfirmed
2✔
1949
        // requests along with the ones that confirmed at the height being
2✔
1950
        // connected/disconnected.
2✔
1951
        confRequests := n.unconfirmedRequests()
2✔
1952
        for confRequest := range n.confsByInitialHeight[height] {
4✔
1953
                confRequests = append(confRequests, confRequest)
2✔
1954
        }
2✔
1955
        err := n.confirmHintCache.CommitConfirmHint(
2✔
1956
                n.currentHeight, confRequests...,
2✔
1957
        )
2✔
1958
        if err != nil {
2✔
1959
                // The error is not fatal as this is an optimistic optimization,
×
1960
                // so we'll avoid returning an error.
×
1961
                Log.Debugf("Unable to update confirm hints to %d for "+
×
1962
                        "%v: %v", n.currentHeight, confRequests, err)
×
1963
        }
×
1964

1965
        // Similarly, to update the height hint for all the required spend
1966
        // requests under one database transaction, we'll gather the set of
1967
        // unspent requests along with the ones that were spent at the height
1968
        // being connected/disconnected.
1969
        spendRequests := n.unspentRequests()
2✔
1970
        for spendRequest := range n.spendsByHeight[height] {
4✔
1971
                spendRequests = append(spendRequests, spendRequest)
2✔
1972
        }
2✔
1973
        err = n.spendHintCache.CommitSpendHint(n.currentHeight, spendRequests...)
2✔
1974
        if err != nil {
2✔
1975
                // The error is not fatal as this is an optimistic optimization,
×
1976
                // so we'll avoid returning an error.
×
1977
                Log.Debugf("Unable to update spend hints to %d for "+
×
1978
                        "%v: %v", n.currentHeight, spendRequests, err)
×
1979
        }
×
1980
}
1981

1982
// unconfirmedRequests returns the set of confirmation requests that are
1983
// still seen as unconfirmed by the TxNotifier.
1984
//
1985
// NOTE: This method must be called with the TxNotifier's lock held.
1986
func (n *TxNotifier) unconfirmedRequests() []ConfRequest {
2✔
1987
        var unconfirmed []ConfRequest
2✔
1988
        for confRequest, confNtfnSet := range n.confNotifications {
4✔
1989
                // If the notification is already aware of its confirmation
2✔
1990
                // details, or it's in the process of learning them, we'll skip
2✔
1991
                // it as we can't yet determine if it's confirmed or not.
2✔
1992
                if confNtfnSet.rescanStatus != rescanComplete ||
2✔
1993
                        confNtfnSet.details != nil {
4✔
1994
                        continue
2✔
1995
                }
1996

1997
                unconfirmed = append(unconfirmed, confRequest)
2✔
1998
        }
1999

2000
        return unconfirmed
2✔
2001
}
2002

2003
// unspentRequests returns the set of spend requests that are still seen as
2004
// unspent by the TxNotifier.
2005
//
2006
// NOTE: This method must be called with the TxNotifier's lock held.
2007
func (n *TxNotifier) unspentRequests() []SpendRequest {
2✔
2008
        var unspent []SpendRequest
2✔
2009
        for spendRequest, spendNtfnSet := range n.spendNotifications {
4✔
2010
                // If the notification is already aware of its spend details, or
2✔
2011
                // it's in the process of learning them, we'll skip it as we
2✔
2012
                // can't yet determine if it's unspent or not.
2✔
2013
                if spendNtfnSet.rescanStatus != rescanComplete ||
2✔
2014
                        spendNtfnSet.details != nil {
4✔
2015
                        continue
2✔
2016
                }
2017

2018
                unspent = append(unspent, spendRequest)
2✔
2019
        }
2020

2021
        return unspent
2✔
2022
}
2023

2024
// dispatchConfReorg dispatches a reorg notification to the client if the
2025
// confirmation notification was already delivered.
2026
//
2027
// NOTE: This must be called with the TxNotifier's lock held.
2028
func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
2029
        heightDisconnected uint32) error {
1✔
2030

1✔
2031
        // If the request's confirmation notification has yet to be dispatched,
1✔
2032
        // we'll need to clear its entry within the ntfnsByConfirmHeight index
1✔
2033
        // to prevent from notifying the client once the notifier reaches the
1✔
2034
        // confirmation height.
1✔
2035
        if !ntfn.dispatched {
2✔
2036
                confHeight := heightDisconnected + ntfn.NumConfirmations - 1
1✔
2037
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
1✔
2038

1✔
2039
                // We also signal the reorg to the notifier in case the
1✔
2040
                // subscriber is also interested in the reorgs before the
1✔
2041
                // transaction received its required confirmation.
1✔
2042
                //
1✔
2043
                // Because as soon as a new block is connected which has the
1✔
2044
                // transaction included again we preemptively read the buffered
1✔
2045
                // channel.
1✔
2046
                select {
1✔
2047
                case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
1✔
2048
                case <-n.quit:
×
2049
                        return ErrTxNotifierExiting
×
2050
                }
2051

2052
                if exists {
2✔
2053
                        delete(ntfnSet, ntfn)
1✔
2054
                }
1✔
2055
                return nil
1✔
2056
        }
2057

2058
        // Otherwise, the entry within the ntfnsByConfirmHeight has already been
2059
        // deleted, so we'll attempt to drain the confirmation notification to
2060
        // ensure sends to the Confirmed channel are always non-blocking.
2061
        select {
1✔
2062
        case <-ntfn.Event.Confirmed:
×
2063
        case <-n.quit:
×
2064
                return ErrTxNotifierExiting
×
2065
        default:
1✔
2066
        }
2067

2068
        ntfn.dispatched = false
1✔
2069

1✔
2070
        // Send a negative confirmation notification to the client indicating
1✔
2071
        // how many blocks have been disconnected successively.
1✔
2072
        select {
1✔
2073
        case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
1✔
2074
        case <-n.quit:
×
2075
                return ErrTxNotifierExiting
×
2076
        }
2077

2078
        return nil
1✔
2079
}
2080

2081
// dispatchSpendReorg dispatches a reorg notification to the client if a spend
2082
// notiification was already delivered.
2083
//
2084
// NOTE: This must be called with the TxNotifier's lock held.
2085
func (n *TxNotifier) dispatchSpendReorg(ntfn *SpendNtfn) error {
2✔
2086
        if !ntfn.dispatched {
2✔
2087
                return nil
×
2088
        }
×
2089

2090
        // Attempt to drain the spend notification to ensure sends to the Spend
2091
        // channel are always non-blocking.
2092
        select {
2✔
UNCOV
2093
        case <-ntfn.Event.Spend:
×
2094
        default:
2✔
2095
        }
2096

2097
        // Send a reorg notification to the client in order for them to
2098
        // correctly handle reorgs.
2099
        select {
2✔
2100
        case ntfn.Event.Reorg <- struct{}{}:
2✔
2101
        case <-n.quit:
×
2102
                return ErrTxNotifierExiting
×
2103
        }
2104

2105
        ntfn.dispatched = false
2✔
2106

2✔
2107
        return nil
2✔
2108
}
2109

2110
// TearDown is to be called when the owner of the TxNotifier is exiting. This
2111
// closes the event channels of all registered notifications that have not been
2112
// dispatched yet.
2113
func (n *TxNotifier) TearDown() {
2✔
2114
        close(n.quit)
2✔
2115

2✔
2116
        n.Lock()
2✔
2117
        defer n.Unlock()
2✔
2118

2✔
2119
        for _, confSet := range n.confNotifications {
4✔
2120
                for confID, ntfn := range confSet.ntfns {
4✔
2121
                        close(ntfn.Event.Confirmed)
2✔
2122
                        close(ntfn.Event.Updates)
2✔
2123
                        close(ntfn.Event.NegativeConf)
2✔
2124
                        close(ntfn.Event.Done)
2✔
2125
                        delete(confSet.ntfns, confID)
2✔
2126
                }
2✔
2127
        }
2128

2129
        for _, spendSet := range n.spendNotifications {
4✔
2130
                for spendID, ntfn := range spendSet.ntfns {
4✔
2131
                        close(ntfn.Event.Spend)
2✔
2132
                        close(ntfn.Event.Reorg)
2✔
2133
                        close(ntfn.Event.Done)
2✔
2134
                        delete(spendSet.ntfns, spendID)
2✔
2135
                }
2✔
2136
        }
2137
}
2138

2139
// notifyNumConfsLeft sends the number of confirmations left to the
2140
// notification subscriber through the Event.Updates channel, along with the
2141
// block height in which the transaction was included.
2142
//
2143
// NOTE: must be used with the TxNotifier's lock held.
2144
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn,
2145
        info TxUpdateInfo) error {
2✔
2146

2✔
2147
        // If the number left is no less than the recorded value, we can skip
2✔
2148
        // sending it as it means this same value has already been sent before.
2✔
2149
        if info.NumConfsLeft >= ntfn.numConfsLeft {
4✔
2150
                Log.Debugf("Skipped dispatched update (numConfsLeft=%v) for "+
2✔
2151
                        "request %v conf_id=%v", info.NumConfsLeft,
2✔
2152
                        ntfn.ConfRequest, ntfn.ConfID)
2✔
2153

2✔
2154
                return nil
2✔
2155
        }
2✔
2156

2157
        // Update the number of confirmations left to the notification.
2158
        ntfn.numConfsLeft = info.NumConfsLeft
2✔
2159

2✔
2160
        select {
2✔
2161
        case ntfn.Event.Updates <- info:
2✔
2162
        case <-n.quit:
×
2163
                return ErrTxNotifierExiting
×
2164
        }
2165

2166
        return nil
2✔
2167
}
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