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

lightningnetwork / lnd / 17459550471

04 Sep 2025 09:29AM UTC coverage: 66.646% (+0.003%) from 66.643%
17459550471

Pull #10189

github

web-flow
Merge 0b7074c3a into d08afa3ea
Pull Request #10189: bugfix error matching sweeper

13 of 18 new or added lines in 4 files covered. (72.22%)

74 existing lines in 24 files now uncovered.

136098 of 204211 relevant lines covered (66.65%)

21413.37 hits per line

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

88.21
/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 {
157✔
132
        return &confNtfnSet{
157✔
133
                ntfns:        make(map[uint64]*ConfNtfn),
157✔
134
                rescanStatus: rescanNotStarted,
157✔
135
        }
157✔
136
}
157✔
137

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

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

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

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

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

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

179
// NewConfRequest creates a request for a confirmation notification of either a
180
// txid or output script. A nil txid or an allocated ZeroHash can be used to
181
// dispatch the confirmation notification on the script.
182
func NewConfRequest(txid *chainhash.Hash, pkScript []byte) (ConfRequest, error) {
235✔
183
        var r ConfRequest
235✔
184
        outputScript, err := txscript.ParsePkScript(pkScript)
235✔
185
        if err != nil {
235✔
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 {
376✔
193
                r.TxID = *txid
141✔
194
        }
141✔
195
        r.PkScript = outputScript
235✔
196

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

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

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

221
                return false
86✔
222
        }
223

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

228
        return scriptMatches()
84✔
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) {
128✔
322
        var r SpendRequest
128✔
323
        outputScript, err := txscript.ParsePkScript(pkScript)
128✔
324
        if err != nil {
128✔
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 {
204✔
332
                r.OutPoint = *op
76✔
333
        }
76✔
334
        r.PkScript = outputScript
128✔
335

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

354
        return r, nil
128✔
355
}
356

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

4✔
364
        if r.OutPoint == ZeroOutPoint {
4✔
NEW
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 {
7✔
371
                scriptStr = "<zero> (taproot pk script not supported)"
3✔
372
        }
3✔
373

374
        return fmt.Sprintf("outpoint=%s, script=%s", outpointStr, scriptStr)
4✔
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.
383
func (r SpendRequest) MatchesTx(tx *wire.MsgTx) (bool, uint32, error) {
9✔
384
        if r.OutPoint != ZeroOutPoint {
14✔
385
                for i, txIn := range tx.TxIn {
10✔
386
                        if txIn.PreviousOutPoint == r.OutPoint {
6✔
387
                                return true, uint32(i), nil
1✔
388
                        }
1✔
389
                }
390

391
                return false, 0, nil
5✔
392
        }
393

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

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

410
        return false, 0, nil
4✔
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 {
50✔
554

50✔
555
        return &TxNotifier{
50✔
556
                currentHeight:        startHeight,
50✔
557
                reorgSafetyLimit:     reorgSafetyLimit,
50✔
558
                confNotifications:    make(map[ConfRequest]*confNtfnSet),
50✔
559
                confsByInitialHeight: make(map[uint32]map[ConfRequest]struct{}),
50✔
560
                ntfnsByConfirmHeight: make(map[uint32]map[*ConfNtfn]struct{}),
50✔
561
                spendNotifications:   make(map[SpendRequest]*spendNtfnSet),
50✔
562
                spendsByHeight:       make(map[uint32]map[SpendRequest]struct{}),
50✔
563
                confirmHintCache:     confirmHintCache,
50✔
564
                spendHintCache:       spendHintCache,
50✔
565
                quit:                 make(chan struct{}),
50✔
566
        }
50✔
567
}
50✔
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) {
227✔
574

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

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

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

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

598
        confID := atomic.AddUint64(&n.confClientCounter, 1)
223✔
599
        return &ConfNtfn{
223✔
600
                ConfID:           confID,
223✔
601
                ConfRequest:      confRequest,
223✔
602
                NumConfirmations: numConfs,
223✔
603
                Event: NewConfirmationEvent(numConfs, func() {
229✔
604
                        n.CancelConf(confRequest, confID)
6✔
605
                }),
6✔
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) {
228✔
623

228✔
624
        select {
228✔
625
        case <-n.quit:
1✔
626
                return nil, ErrTxNotifierExiting
1✔
627
        default:
227✔
628
        }
629

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

635
        // We'll start by performing a series of validation checks.
636
        ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint, opts)
227✔
637
        if err != nil {
231✔
638
                return nil, err
4✔
639
        }
4✔
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
223✔
646
        hint, err := n.confirmHintCache.QueryConfirmHint(ntfn.ConfRequest)
223✔
647
        if err == nil {
247✔
648
                if hint > startHeight {
46✔
649
                        Log.Debugf("Using height hint %d retrieved from cache "+
22✔
650
                                "for %v instead of %d for conf subscription",
22✔
651
                                hint, ntfn.ConfRequest, startHeight)
22✔
652
                        startHeight = hint
22✔
653
                }
22✔
654
        } else if err != ErrConfirmHintNotFound {
202✔
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, "+
223✔
660
                "num_confs=%v height_hint=%d", ntfn.ConfID, ntfn.ConfRequest,
223✔
661
                numConfs, startHeight)
223✔
662

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

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

223✔
675
        switch confSet.rescanStatus {
223✔
676

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

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

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

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

708
                return &ConfRegistration{
26✔
709
                        Event:              ntfn.Event,
26✔
710
                        HistoricalDispatch: nil,
26✔
711
                        Height:             n.currentHeight,
26✔
712
                }, nil
26✔
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:
46✔
718
                Log.Debugf("Waiting for pending rescan to finish before "+
46✔
719
                        "notifying %v at tip", ntfn.ConfRequest)
46✔
720

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

727
        // If no rescan has been dispatched, attempt to do so now.
728
        case rescanNotStarted:
157✔
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 {
163✔
736
                Log.Debugf("Height hint is above current height, not "+
6✔
737
                        "dispatching historical confirmation rescan for %v",
6✔
738
                        ntfn.ConfRequest)
6✔
739

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

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

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

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

153✔
768
        return &ConfRegistration{
153✔
769
                Event:              ntfn.Event,
153✔
770
                HistoricalDispatch: dispatch,
153✔
771
                Height:             n.currentHeight,
153✔
772
        }, nil
153✔
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) {
6✔
778
        select {
6✔
779
        case <-n.quit:
×
780
                return
×
781
        default:
6✔
782
        }
783

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

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

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

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

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

6✔
809
        // Remove the queued confirmation notification if the transaction has
6✔
810
        // already confirmed, but hasn't met its required number of
6✔
811
        // confirmations.
6✔
812
        if confSet.details != nil {
11✔
813
                confHeight := confSet.details.BlockHeight +
5✔
814
                        ntfn.NumConfirmations - 1
5✔
815
                delete(n.ntfnsByConfirmHeight[confHeight], ntfn)
5✔
816
        }
5✔
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 {
144✔
828

144✔
829
        select {
144✔
830
        case <-n.quit:
×
831
                return ErrTxNotifierExiting
×
832
        default:
144✔
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()
144✔
838
        defer n.Unlock()
144✔
839

144✔
840
        // First, we'll determine whether we have an active confirmation
144✔
841
        // notification for the given txid/script.
144✔
842
        confSet, ok := n.confNotifications[confRequest]
144✔
843
        if !ok {
144✔
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 {
148✔
853
                return nil
4✔
854
        }
4✔
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
141✔
861

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

119✔
870
                // We'll commit the current height as the confirm hint to
119✔
871
                // prevent another potentially long rescan if we restart before
119✔
872
                // a new block comes in.
119✔
873
                err := n.confirmHintCache.CommitConfirmHint(
119✔
874
                        n.currentHeight, confRequest,
119✔
875
                )
119✔
876
                if err != nil {
119✔
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
119✔
884
        }
885

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

2✔
890
                return nil
2✔
891
        }
2✔
892

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

23✔
895
        err := n.confirmHintCache.CommitConfirmHint(
23✔
896
                details.BlockHeight, confRequest,
23✔
897
        )
23✔
898
        if err != nil {
23✔
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
23✔
908
        for _, ntfn := range confSet.ntfns {
51✔
909
                // The default notification we assigned above includes the
28✔
910
                // block along with the rest of the details. However not all
28✔
911
                // clients want the block, so we make a copy here w/o the block
28✔
912
                // if needed so we can give clients only what they ask for.
28✔
913
                confDetails := *details
28✔
914
                if !ntfn.includeBlock {
48✔
915
                        confDetails.Block = nil
20✔
916
                }
20✔
917

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

924
        return nil
23✔
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 {
112✔
932

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

20✔
940
                return nil
20✔
941
        }
20✔
942

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

51✔
947
                return nil
51✔
948
        }
51✔
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
47✔
954
        if confHeight <= n.currentHeight {
85✔
955
                Log.Debugf("Dispatching %v confirmation notification for "+
38✔
956
                        "conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
38✔
957
                        ntfn.ConfRequest)
38✔
958

38✔
959
                // We'll send a 0 value to the Updates channel,
38✔
960
                // indicating that the transaction/output script has already
38✔
961
                // been confirmed.
38✔
962
                err := n.notifyNumConfsLeft(ntfn, 0)
38✔
963
                if err != nil {
38✔
964
                        return err
×
965
                }
×
966

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

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

12✔
987
                // We'll also send an update to the client of how many
12✔
988
                // confirmations are left for the transaction/output script to
12✔
989
                // be confirmed.
12✔
990
                numConfsLeft := confHeight - n.currentHeight
12✔
991
                err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
12✔
992
                if err != nil {
12✔
993
                        return err
×
994
                }
×
995
        }
996

997
        // As a final check, we'll also watch the transaction/output script if
998
        // it's still possible for it to get reorged out of the chain.
999
        reorgSafeHeight := details.BlockHeight + n.reorgSafetyLimit
47✔
1000
        if reorgSafeHeight > n.currentHeight {
94✔
1001
                txSet, exists := n.confsByInitialHeight[details.BlockHeight]
47✔
1002
                if !exists {
61✔
1003
                        txSet = make(map[ConfRequest]struct{})
14✔
1004
                        n.confsByInitialHeight[details.BlockHeight] = txSet
14✔
1005
                }
14✔
1006
                txSet[ntfn.ConfRequest] = struct{}{}
47✔
1007
        }
1008

1009
        return nil
47✔
1010
}
1011

1012
// newSpendNtfn validates all of the parameters required to successfully create
1013
// and register a spend notification.
1014
func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint,
1015
        pkScript []byte, heightHint uint32) (*SpendNtfn, error) {
130✔
1016

130✔
1017
        // An accompanying output script must always be provided.
130✔
1018
        if len(pkScript) == 0 {
131✔
1019
                return nil, ErrNoScript
1✔
1020
        }
1✔
1021

1022
        // A height hint must be provided to prevent scanning from the genesis
1023
        // block.
1024
        if heightHint == 0 {
130✔
1025
                return nil, ErrNoHeightHint
1✔
1026
        }
1✔
1027

1028
        // Ensure the output script is of a supported type.
1029
        spendRequest, err := NewSpendRequest(outpoint, pkScript)
128✔
1030
        if err != nil {
131✔
1031
                return nil, err
3✔
1032
        }
3✔
1033

1034
        spendID := atomic.AddUint64(&n.spendClientCounter, 1)
128✔
1035
        return &SpendNtfn{
128✔
1036
                SpendID:      spendID,
128✔
1037
                SpendRequest: spendRequest,
128✔
1038
                Event: NewSpendEvent(func() {
139✔
1039
                        n.CancelSpend(spendRequest, spendID)
11✔
1040
                }),
11✔
1041
                HeightHint: heightHint,
1042
        }, nil
1043
}
1044

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

131✔
1056
        select {
131✔
1057
        case <-n.quit:
1✔
1058
                return nil, ErrTxNotifierExiting
1✔
1059
        default:
130✔
1060
        }
1061

1062
        // We'll start by performing a series of validation checks.
1063
        ntfn, err := n.newSpendNtfn(outpoint, pkScript, heightHint)
130✔
1064
        if err != nil {
135✔
1065
                return nil, err
5✔
1066
        }
5✔
1067

1068
        // Before proceeding to register the notification, we'll query our spend
1069
        // hint cache to determine whether a better one exists.
1070
        startHeight := ntfn.HeightHint
128✔
1071
        hint, err := n.spendHintCache.QuerySpendHint(ntfn.SpendRequest)
128✔
1072
        if err == nil {
162✔
1073
                if hint > startHeight {
56✔
1074
                        Log.Debugf("Using height hint %d retrieved from cache "+
22✔
1075
                                "for %v instead of %d for spend subscription",
22✔
1076
                                hint, ntfn.SpendRequest, startHeight)
22✔
1077
                        startHeight = hint
22✔
1078
                }
22✔
1079
        } else if err != ErrSpendHintNotFound {
97✔
1080
                Log.Errorf("Unable to query spend hint for %v: %v",
×
1081
                        ntfn.SpendRequest, err)
×
1082
        }
×
1083

1084
        n.Lock()
128✔
1085
        defer n.Unlock()
128✔
1086

128✔
1087
        Log.Debugf("New spend subscription: spend_id=%d, %v, height_hint=%d",
128✔
1088
                ntfn.SpendID, ntfn.SpendRequest, startHeight)
128✔
1089

128✔
1090
        // Keep track of the notification request so that we can properly
128✔
1091
        // dispatch a spend notification later on.
128✔
1092
        spendSet, ok := n.spendNotifications[ntfn.SpendRequest]
128✔
1093
        if !ok {
177✔
1094
                // If this is the first registration for the request, we'll
49✔
1095
                // construct a spendNtfnSet to coalesce all notifications.
49✔
1096
                spendSet = newSpendNtfnSet()
49✔
1097
                n.spendNotifications[ntfn.SpendRequest] = spendSet
49✔
1098
        }
49✔
1099
        spendSet.ntfns[ntfn.SpendID] = ntfn
128✔
1100

128✔
1101
        // We'll now let the caller know whether a historical rescan is needed
128✔
1102
        // depending on the current rescan status.
128✔
1103
        switch spendSet.rescanStatus {
128✔
1104

1105
        // If the spending details for this request have already been determined
1106
        // and cached, then we can use them to immediately dispatch the spend
1107
        // notification to the client.
1108
        case rescanComplete:
72✔
1109
                Log.Debugf("Attempting to dispatch spend for %v on "+
72✔
1110
                        "registration since rescan has finished",
72✔
1111
                        ntfn.SpendRequest)
72✔
1112

72✔
1113
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
72✔
1114
                if err != nil {
72✔
1115
                        return nil, err
×
1116
                }
×
1117

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

1124
        // If there is an active rescan to determine whether the request has
1125
        // been spent, then we won't trigger another one.
1126
        case rescanPending:
13✔
1127
                Log.Debugf("Waiting for pending rescan to finish before "+
13✔
1128
                        "notifying %v at tip", ntfn.SpendRequest)
13✔
1129

13✔
1130
                return &SpendRegistration{
13✔
1131
                        Event:              ntfn.Event,
13✔
1132
                        HistoricalDispatch: nil,
13✔
1133
                        Height:             n.currentHeight,
13✔
1134
                }, nil
13✔
1135

1136
        // Otherwise, we'll fall through and let the caller know that a rescan
1137
        // should be dispatched to determine whether the request has already
1138
        // been spent.
1139
        case rescanNotStarted:
49✔
1140
        }
1141

1142
        // However, if the spend hint, either provided by the caller or
1143
        // retrieved from the cache, is found to be at a later height than the
1144
        // TxNotifier is aware of, then we'll refrain from dispatching a
1145
        // historical rescan and wait for the spend to come in at tip.
1146
        if startHeight > n.currentHeight {
72✔
1147
                Log.Debugf("Spend hint of %d for %v is above current height %d",
23✔
1148
                        startHeight, ntfn.SpendRequest, n.currentHeight)
23✔
1149

23✔
1150
                // We'll also set the rescan status as complete to ensure that
23✔
1151
                // spend hints for this request get updated upon
23✔
1152
                // connected/disconnected blocks.
23✔
1153
                spendSet.rescanStatus = rescanComplete
23✔
1154
                return &SpendRegistration{
23✔
1155
                        Event:              ntfn.Event,
23✔
1156
                        HistoricalDispatch: nil,
23✔
1157
                        Height:             n.currentHeight,
23✔
1158
                }, nil
23✔
1159
        }
23✔
1160

1161
        // We'll set the rescan status to pending to ensure subsequent
1162
        // notifications don't also attempt a historical dispatch.
1163
        spendSet.rescanStatus = rescanPending
26✔
1164

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

26✔
1168
        return &SpendRegistration{
26✔
1169
                Event: ntfn.Event,
26✔
1170
                HistoricalDispatch: &HistoricalSpendDispatch{
26✔
1171
                        SpendRequest: ntfn.SpendRequest,
26✔
1172
                        StartHeight:  startHeight,
26✔
1173
                        EndHeight:    n.currentHeight,
26✔
1174
                },
26✔
1175
                Height: n.currentHeight,
26✔
1176
        }, nil
26✔
1177
}
1178

1179
// CancelSpend cancels an existing request for a spend notification of an
1180
// outpoint/output script. The request is identified by its spend ID.
1181
func (n *TxNotifier) CancelSpend(spendRequest SpendRequest, spendID uint64) {
12✔
1182
        select {
12✔
1183
        case <-n.quit:
×
1184
                return
×
1185
        default:
12✔
1186
        }
1187

1188
        n.Lock()
12✔
1189
        defer n.Unlock()
12✔
1190

12✔
1191
        spendSet, ok := n.spendNotifications[spendRequest]
12✔
1192
        if !ok {
12✔
1193
                return
×
1194
        }
×
1195
        ntfn, ok := spendSet.ntfns[spendID]
12✔
1196
        if !ok {
12✔
1197
                return
×
1198
        }
×
1199

1200
        Log.Debugf("Canceling spend notification: spend_id=%d, %v", spendID,
12✔
1201
                spendRequest)
12✔
1202

12✔
1203
        // We'll close all the notification channels to let the client know
12✔
1204
        // their cancel request has been fulfilled.
12✔
1205
        close(ntfn.Event.Spend)
12✔
1206
        close(ntfn.Event.Reorg)
12✔
1207
        close(ntfn.Event.Done)
12✔
1208
        delete(spendSet.ntfns, spendID)
12✔
1209
}
1210

1211
// ProcessRelevantSpendTx processes a transaction provided externally. This will
1212
// check whether the transaction is relevant to the notifier if it spends any
1213
// outpoints/output scripts for which we currently have registered notifications
1214
// for. If it is relevant, spend notifications will be dispatched to the caller.
1215
func (n *TxNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx,
1216
        blockHeight uint32) error {
44✔
1217

44✔
1218
        select {
44✔
1219
        case <-n.quit:
×
1220
                return ErrTxNotifierExiting
×
1221
        default:
44✔
1222
        }
1223

1224
        // Ensure we hold the lock throughout handling the notification to
1225
        // prevent the notifier from advancing its height underneath us.
1226
        n.Lock()
44✔
1227
        defer n.Unlock()
44✔
1228

44✔
1229
        // We'll use a channel to coalesce all the spend requests that this
44✔
1230
        // transaction fulfills.
44✔
1231
        type spend struct {
44✔
1232
                request *SpendRequest
44✔
1233
                details *SpendDetail
44✔
1234
        }
44✔
1235

44✔
1236
        // We'll set up the onSpend filter callback to gather all the fulfilled
44✔
1237
        // spends requests within this transaction.
44✔
1238
        var spends []spend
44✔
1239
        onSpend := func(request SpendRequest, details *SpendDetail) {
82✔
1240
                spends = append(spends, spend{&request, details})
38✔
1241
        }
38✔
1242
        n.filterTx(nil, tx, blockHeight, nil, onSpend)
44✔
1243

44✔
1244
        // After the transaction has been filtered, we can finally dispatch
44✔
1245
        // notifications for each request.
44✔
1246
        for _, spend := range spends {
82✔
1247
                err := n.updateSpendDetails(*spend.request, spend.details)
38✔
1248
                if err != nil {
38✔
1249
                        return err
×
1250
                }
×
1251
        }
1252

1253
        return nil
44✔
1254
}
1255

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

17✔
1266
        select {
17✔
1267
        case <-n.quit:
×
1268
                return ErrTxNotifierExiting
×
1269
        default:
17✔
1270
        }
1271

1272
        // Ensure we hold the lock throughout handling the notification to
1273
        // prevent the notifier from advancing its height underneath us.
1274
        n.Lock()
17✔
1275
        defer n.Unlock()
17✔
1276

17✔
1277
        return n.updateSpendDetails(spendRequest, details)
17✔
1278
}
1279

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

52✔
1289
        // Mark the ongoing historical rescan for this request as finished. This
52✔
1290
        // will allow us to update the spend hints for it at tip.
52✔
1291
        spendSet, ok := n.spendNotifications[spendRequest]
52✔
1292
        if !ok {
53✔
1293
                return fmt.Errorf("spend notification for %v not found",
1✔
1294
                        spendRequest)
1✔
1295
        }
1✔
1296

1297
        // If the spend details have already been found either at tip, then the
1298
        // notifications should have already been dispatched, so we can exit
1299
        // early to prevent sending duplicate notifications.
1300
        if spendSet.details != nil {
71✔
1301
                return nil
20✔
1302
        }
20✔
1303

1304
        // Since the historical rescan has completed for this request, we'll
1305
        // mark its rescan status as complete in order to ensure that the
1306
        // TxNotifier can properly update its spend hints upon
1307
        // connected/disconnected blocks.
1308
        spendSet.rescanStatus = rescanComplete
34✔
1309

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

1326
                Log.Debugf("Updated spend hint to height=%v for unconfirmed "+
9✔
1327
                        "spend request %v", n.currentHeight, spendRequest)
9✔
1328
                return nil
9✔
1329
        }
1330

1331
        // Return an error if the witness data is not present in the spending
1332
        // transaction.
1333
        //
1334
        // NOTE: if the witness stack is empty, we will do a critical log which
1335
        // shuts down the node.
1336
        if !details.HasSpenderWitness() {
28✔
1337
                Log.Criticalf("Found spending tx for outpoint=%v, but the "+
×
1338
                        "transaction %v does not have witness",
×
1339
                        spendRequest.OutPoint, details.SpendingTx.TxHash())
×
1340

×
1341
                return ErrEmptyWitnessStack
×
1342
        }
×
1343

1344
        // If the historical rescan found the spending transaction for this
1345
        // request, but it's at a later height than the notifier (this can
1346
        // happen due to latency with the backend during a reorg), then we'll
1347
        // defer handling the notification until the notifier has caught up to
1348
        // such height.
1349
        if uint32(details.SpendingHeight) > n.currentHeight {
52✔
1350
                return nil
24✔
1351
        }
24✔
1352

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

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

7✔
1369
        spendSet.details = details
7✔
1370
        for _, ntfn := range spendSet.ntfns {
19✔
1371
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
12✔
1372
                if err != nil {
12✔
1373
                        return err
×
1374
                }
×
1375
        }
1376

1377
        return nil
7✔
1378
}
1379

1380
// dispatchSpendDetails dispatches a spend notification to the client.
1381
//
1382
// NOTE: This must be called with the TxNotifier's lock held.
1383
func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail) error {
176✔
1384
        // If there are no spend details to dispatch or if the notification has
176✔
1385
        // already been dispatched, then we can skip dispatching to this client.
176✔
1386
        if details == nil || ntfn.dispatched {
227✔
1387
                Log.Debugf("Skipping dispatch of spend details(%v) for "+
51✔
1388
                        "request %v, dispatched=%v", details, ntfn.SpendRequest,
51✔
1389
                        ntfn.dispatched)
51✔
1390
                return nil
51✔
1391
        }
51✔
1392

1393
        Log.Debugf("Dispatching confirmed spend notification for %v at "+
128✔
1394
                "current height=%d: %v", ntfn.SpendRequest, n.currentHeight,
128✔
1395
                details)
128✔
1396

128✔
1397
        select {
128✔
1398
        case ntfn.Event.Spend <- details:
128✔
1399
                ntfn.dispatched = true
128✔
1400
        case <-n.quit:
×
1401
                return ErrTxNotifierExiting
×
1402
        }
1403

1404
        spendHeight := uint32(details.SpendingHeight)
128✔
1405

128✔
1406
        // We also add to spendsByHeight to notify on chain reorgs.
128✔
1407
        reorgSafeHeight := spendHeight + n.reorgSafetyLimit
128✔
1408
        if reorgSafeHeight > n.currentHeight {
256✔
1409
                txSet, exists := n.spendsByHeight[spendHeight]
128✔
1410
                if !exists {
135✔
1411
                        txSet = make(map[SpendRequest]struct{})
7✔
1412
                        n.spendsByHeight[spendHeight] = txSet
7✔
1413
                }
7✔
1414
                txSet[ntfn.SpendRequest] = struct{}{}
128✔
1415
        }
1416

1417
        return nil
128✔
1418
}
1419

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

1,589✔
1442
        select {
1,589✔
1443
        case <-n.quit:
×
1444
                return ErrTxNotifierExiting
×
1445
        default:
1,589✔
1446
        }
1447

1448
        n.Lock()
1,589✔
1449
        defer n.Unlock()
1,589✔
1450

1,589✔
1451
        if blockHeight != n.currentHeight+1 {
1,589✔
1452
                return fmt.Errorf("received blocks out of order: "+
×
1453
                        "current height=%d, new height=%d",
×
1454
                        n.currentHeight, blockHeight)
×
1455
        }
×
1456
        n.currentHeight++
1,589✔
1457
        n.reorgDepth = 0
1,589✔
1458

1,589✔
1459
        // First, we'll iterate over all the transactions found in this block to
1,589✔
1460
        // determine if it includes any relevant transactions to the TxNotifier.
1,589✔
1461
        if block != nil {
3,175✔
1462
                Log.Debugf("Filtering %d txns for %d spend requests at "+
1,586✔
1463
                        "height %d", len(block.Transactions()),
1,586✔
1464
                        len(n.spendNotifications), blockHeight)
1,586✔
1465

1,586✔
1466
                for _, tx := range block.Transactions() {
3,848✔
1467
                        n.filterTx(
2,262✔
1468
                                block, tx, blockHeight,
2,262✔
1469
                                n.handleConfDetailsAtTip,
2,262✔
1470
                                n.handleSpendDetailsAtTip,
2,262✔
1471
                        )
2,262✔
1472
                }
2,262✔
1473
        }
1474

1475
        // Now that we've determined which requests were confirmed and spent
1476
        // within the new block, we can update their entries in their respective
1477
        // caches, along with all of our unconfirmed and unspent requests.
1478
        n.updateHints(blockHeight)
1,589✔
1479

1,589✔
1480
        // Finally, we'll clear the entries from our set of notifications for
1,589✔
1481
        // requests that are no longer under the risk of being reorged out of
1,589✔
1482
        // the chain.
1,589✔
1483
        if blockHeight >= n.reorgSafetyLimit {
3,066✔
1484
                matureBlockHeight := blockHeight - n.reorgSafetyLimit
1,477✔
1485
                for confRequest := range n.confsByInitialHeight[matureBlockHeight] {
1,493✔
1486
                        confSet := n.confNotifications[confRequest]
16✔
1487
                        for _, ntfn := range confSet.ntfns {
33✔
1488
                                select {
17✔
1489
                                case ntfn.Event.Done <- struct{}{}:
17✔
1490
                                case <-n.quit:
×
1491
                                        return ErrTxNotifierExiting
×
1492
                                }
1493
                        }
1494

1495
                        delete(n.confNotifications, confRequest)
16✔
1496
                }
1497
                delete(n.confsByInitialHeight, matureBlockHeight)
1,477✔
1498

1,477✔
1499
                for spendRequest := range n.spendsByHeight[matureBlockHeight] {
1,479✔
1500
                        spendSet := n.spendNotifications[spendRequest]
2✔
1501
                        for _, ntfn := range spendSet.ntfns {
4✔
1502
                                select {
2✔
1503
                                case ntfn.Event.Done <- struct{}{}:
2✔
1504
                                case <-n.quit:
×
1505
                                        return ErrTxNotifierExiting
×
1506
                                }
1507
                        }
1508

1509
                        Log.Debugf("Deleting mature spend request %v at "+
2✔
1510
                                "height=%d", spendRequest, blockHeight)
2✔
1511
                        delete(n.spendNotifications, spendRequest)
2✔
1512
                }
1513
                delete(n.spendsByHeight, matureBlockHeight)
1,477✔
1514
        }
1515

1516
        return nil
1,589✔
1517
}
1518

1519
// filterTx determines whether the transaction spends or confirms any
1520
// outstanding pending requests. The onConf and onSpend callbacks can be used to
1521
// retrieve all the requests fulfilled by this transaction as they occur.
1522
func (n *TxNotifier) filterTx(block *btcutil.Block, tx *btcutil.Tx,
1523
        blockHeight uint32, onConf func(ConfRequest, *TxConfirmation),
1524
        onSpend func(SpendRequest, *SpendDetail)) {
2,303✔
1525

2,303✔
1526
        // In order to determine if this transaction is relevant to the
2,303✔
1527
        // notifier, we'll check its inputs for any outstanding spend
2,303✔
1528
        // requests.
2,303✔
1529
        txHash := tx.Hash()
2,303✔
1530
        if onSpend != nil {
4,606✔
1531
                // notifyDetails is a helper closure that will construct the
2,303✔
1532
                // spend details of a request and hand them off to the onSpend
2,303✔
1533
                // callback.
2,303✔
1534
                notifyDetails := func(spendRequest SpendRequest,
2,303✔
1535
                        prevOut wire.OutPoint, inputIdx uint32) {
2,391✔
1536

88✔
1537
                        Log.Debugf("Found spend of %v: spend_tx=%v, "+
88✔
1538
                                "block_height=%d", spendRequest, txHash,
88✔
1539
                                blockHeight)
88✔
1540

88✔
1541
                        onSpend(spendRequest, &SpendDetail{
88✔
1542
                                SpentOutPoint:     &prevOut,
88✔
1543
                                SpenderTxHash:     txHash,
88✔
1544
                                SpendingTx:        tx.MsgTx(),
88✔
1545
                                SpenderInputIndex: inputIdx,
88✔
1546
                                SpendingHeight:    int32(blockHeight),
88✔
1547
                        })
88✔
1548
                }
88✔
1549

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

2,541✔
1566
                        // If we have any, we'll record their spend height so
2,541✔
1567
                        // that notifications get dispatched to the respective
2,541✔
1568
                        // clients.
2,541✔
1569
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,591✔
1570
                                notifyDetails(spendRequest, prevOut, uint32(i))
50✔
1571
                        }
50✔
1572

1573
                        // Now try with an empty taproot key pkScript, since we
1574
                        // cannot derive the spent pkScript directly from the
1575
                        // witness. But we have the outpoint, which should be
1576
                        // enough.
1577
                        spendRequest.PkScript = ZeroTaprootPkScript
2,541✔
1578
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,544✔
1579
                                notifyDetails(spendRequest, prevOut, uint32(i))
3✔
1580
                        }
3✔
1581

1582
                        // Restore the pkScript but try with a zero outpoint
1583
                        // instead (won't be possible for Taproot).
1584
                        spendRequest.PkScript = pkScript
2,541✔
1585
                        spendRequest.OutPoint = ZeroOutPoint
2,541✔
1586
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,579✔
1587
                                notifyDetails(spendRequest, prevOut, uint32(i))
38✔
1588
                        }
38✔
1589
                }
1590
        }
1591

1592
        // We'll also check its outputs to determine if there are any
1593
        // outstanding confirmation requests.
1594
        if onConf != nil {
4,565✔
1595
                // notifyDetails is a helper closure that will construct the
2,262✔
1596
                // confirmation details of a request and hand them off to the
2,262✔
1597
                // onConf callback.
2,262✔
1598
                notifyDetails := func(confRequest ConfRequest) {
2,404✔
1599
                        Log.Debugf("Found initial confirmation of %v: "+
142✔
1600
                                "height=%d, hash=%v", confRequest,
142✔
1601
                                blockHeight, block.Hash())
142✔
1602

142✔
1603
                        details := &TxConfirmation{
142✔
1604
                                Tx:          tx.MsgTx(),
142✔
1605
                                BlockHash:   block.Hash(),
142✔
1606
                                BlockHeight: blockHeight,
142✔
1607
                                TxIndex:     uint32(tx.Index()),
142✔
1608
                                Block:       block.MsgBlock(),
142✔
1609
                        }
142✔
1610

142✔
1611
                        onConf(confRequest, details)
142✔
1612
                }
142✔
1613

1614
                for _, txOut := range tx.MsgTx().TxOut {
5,534✔
1615
                        // We'll parse the script of the output to determine if
3,272✔
1616
                        // we have any registered requests for it or the
3,272✔
1617
                        // transaction itself.
3,272✔
1618
                        pkScript, err := txscript.ParsePkScript(txOut.PkScript)
3,272✔
1619
                        if err != nil {
3,417✔
1620
                                continue
145✔
1621
                        }
1622
                        confRequest := ConfRequest{
3,130✔
1623
                                TxID:     *txHash,
3,130✔
1624
                                PkScript: pkScript,
3,130✔
1625
                        }
3,130✔
1626

3,130✔
1627
                        // If we have any, we'll record their confirmed height
3,130✔
1628
                        // so that notifications get dispatched when they
3,130✔
1629
                        // reaches the clients' desired number of confirmations.
3,130✔
1630
                        if _, ok := n.confNotifications[confRequest]; ok {
3,207✔
1631
                                notifyDetails(confRequest)
77✔
1632
                        }
77✔
1633
                        confRequest.TxID = ZeroHash
3,130✔
1634
                        if _, ok := n.confNotifications[confRequest]; ok {
3,195✔
1635
                                notifyDetails(confRequest)
65✔
1636
                        }
65✔
1637
                }
1638
        }
1639
}
1640

1641
// handleConfDetailsAtTip tracks the confirmation height of the txid/output
1642
// script in order to properly dispatch a confirmation notification after
1643
// meeting each request's desired number of confirmations for all current and
1644
// future registered clients.
1645
func (n *TxNotifier) handleConfDetailsAtTip(confRequest ConfRequest,
1646
        details *TxConfirmation) {
142✔
1647

142✔
1648
        // TODO(wilmer): cancel pending historical rescans if any?
142✔
1649
        confSet := n.confNotifications[confRequest]
142✔
1650

142✔
1651
        // If we already have details for this request, we don't want to add it
142✔
1652
        // again since we have already dispatched notifications for it.
142✔
1653
        if confSet.details != nil {
147✔
1654
                Log.Warnf("Ignoring address reuse for %s at height %d.",
5✔
1655
                        confRequest, details.BlockHeight)
5✔
1656
                return
5✔
1657
        }
5✔
1658

1659
        confSet.rescanStatus = rescanComplete
139✔
1660
        confSet.details = details
139✔
1661

139✔
1662
        for _, ntfn := range confSet.ntfns {
319✔
1663
                // In the event that this notification was aware that the
180✔
1664
                // transaction/output script was reorged out of the chain, we'll
180✔
1665
                // consume the reorg notification if it hasn't been done yet
180✔
1666
                // already.
180✔
1667
                select {
180✔
1668
                case <-ntfn.Event.NegativeConf:
2✔
1669
                default:
180✔
1670
                }
1671

1672
                // We'll note this client's required number of confirmations so
1673
                // that we can notify them when expected.
1674
                confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
180✔
1675
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
180✔
1676
                if !exists {
326✔
1677
                        ntfnSet = make(map[*ConfNtfn]struct{})
146✔
1678
                        n.ntfnsByConfirmHeight[confHeight] = ntfnSet
146✔
1679
                }
146✔
1680
                ntfnSet[ntfn] = struct{}{}
180✔
1681
        }
1682

1683
        // We'll also note the initial confirmation height in order to correctly
1684
        // handle dispatching notifications when the transaction/output script
1685
        // gets reorged out of the chain.
1686
        txSet, exists := n.confsByInitialHeight[details.BlockHeight]
139✔
1687
        if !exists {
233✔
1688
                txSet = make(map[ConfRequest]struct{})
94✔
1689
                n.confsByInitialHeight[details.BlockHeight] = txSet
94✔
1690
        }
94✔
1691
        txSet[confRequest] = struct{}{}
139✔
1692
}
1693

1694
// handleSpendDetailsAtTip tracks the spend height of the outpoint/output script
1695
// in order to properly dispatch a spend notification for all current and future
1696
// registered clients.
1697
func (n *TxNotifier) handleSpendDetailsAtTip(spendRequest SpendRequest,
1698
        details *SpendDetail) {
53✔
1699

53✔
1700
        // TODO(wilmer): cancel pending historical rescans if any?
53✔
1701
        spendSet := n.spendNotifications[spendRequest]
53✔
1702
        spendSet.rescanStatus = rescanComplete
53✔
1703
        spendSet.details = details
53✔
1704

53✔
1705
        for _, ntfn := range spendSet.ntfns {
151✔
1706
                // In the event that this notification was aware that the
98✔
1707
                // spending transaction of its outpoint/output script was
98✔
1708
                // reorged out of the chain, we'll consume the reorg
98✔
1709
                // notification if it hasn't been done yet already.
98✔
1710
                select {
98✔
1711
                case <-ntfn.Event.Reorg:
×
1712
                default:
98✔
1713
                }
1714
        }
1715

1716
        // We'll note the spending height of the request in order to correctly
1717
        // handle dispatching notifications when the spending transactions gets
1718
        // reorged out of the chain.
1719
        spendHeight := uint32(details.SpendingHeight)
53✔
1720
        opSet, exists := n.spendsByHeight[spendHeight]
53✔
1721
        if !exists {
106✔
1722
                opSet = make(map[SpendRequest]struct{})
53✔
1723
                n.spendsByHeight[spendHeight] = opSet
53✔
1724
        }
53✔
1725
        opSet[spendRequest] = struct{}{}
53✔
1726

53✔
1727
        Log.Debugf("Spend request %v spent at tip=%d", spendRequest,
53✔
1728
                spendHeight)
53✔
1729
}
1730

1731
// NotifyHeight dispatches confirmation and spend notifications to the clients
1732
// who registered for a notification which has been fulfilled at the passed
1733
// height.
1734
func (n *TxNotifier) NotifyHeight(height uint32) error {
1,489✔
1735
        n.Lock()
1,489✔
1736
        defer n.Unlock()
1,489✔
1737

1,489✔
1738
        // First, we'll dispatch an update to all of the notification clients
1,489✔
1739
        // for our watched requests with the number of confirmations left at
1,489✔
1740
        // this new height.
1,489✔
1741
        for _, confRequests := range n.confsByInitialHeight {
9,061✔
1742
                for confRequest := range confRequests {
20,935✔
1743
                        confSet := n.confNotifications[confRequest]
13,363✔
1744
                        for _, ntfn := range confSet.ntfns {
30,078✔
1745
                                txConfHeight := confSet.details.BlockHeight +
16,715✔
1746
                                        ntfn.NumConfirmations - 1
16,715✔
1747
                                numConfsLeft := txConfHeight - height
16,715✔
1748

16,715✔
1749
                                // Since we don't clear notifications until
16,715✔
1750
                                // transactions/output scripts are no longer
16,715✔
1751
                                // under the risk of being reorganized out of
16,715✔
1752
                                // the chain, we'll skip sending updates for
16,715✔
1753
                                // those that have already been confirmed.
16,715✔
1754
                                if int32(numConfsLeft) < 0 {
32,745✔
1755
                                        continue
16,030✔
1756
                                }
1757

1758
                                err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
688✔
1759
                                if err != nil {
688✔
1760
                                        return err
×
1761
                                }
×
1762
                        }
1763
                }
1764
        }
1765

1766
        // Then, we'll dispatch notifications for all the requests that have
1767
        // become confirmed at this new block height.
1768
        for ntfn := range n.ntfnsByConfirmHeight[height] {
1,666✔
1769
                confSet := n.confNotifications[ntfn.ConfRequest]
177✔
1770

177✔
1771
                // The default notification we assigned above includes the
177✔
1772
                // block along with the rest of the details. However not all
177✔
1773
                // clients want the block, so we make a copy here w/o the block
177✔
1774
                // if needed so we can give clients only what they ask for.
177✔
1775
                confDetails := *confSet.details
177✔
1776
                if !ntfn.includeBlock {
314✔
1777
                        confDetails.Block = nil
137✔
1778
                }
137✔
1779

1780
                // If the `confDetails` has already been sent before, we'll
1781
                // skip it and continue processing the next one.
1782
                if ntfn.dispatched {
177✔
1783
                        Log.Debugf("Skipped dispatched conf details for "+
×
1784
                                "request %v conf_id=%v", ntfn.ConfRequest,
×
1785
                                ntfn.ConfID)
×
1786

×
1787
                        continue
×
1788
                }
1789

1790
                Log.Debugf("Dispatching %v confirmation notification for "+
177✔
1791
                        "conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
177✔
1792
                        ntfn.ConfRequest)
177✔
1793

177✔
1794
                select {
177✔
1795
                case ntfn.Event.Confirmed <- &confDetails:
177✔
1796
                        ntfn.dispatched = true
177✔
1797
                case <-n.quit:
×
1798
                        return ErrTxNotifierExiting
×
1799
                }
1800
        }
1801
        delete(n.ntfnsByConfirmHeight, height)
1,489✔
1802

1,489✔
1803
        // Finally, we'll dispatch spend notifications for all the requests that
1,489✔
1804
        // were spent at this new block height.
1,489✔
1805
        for spendRequest := range n.spendsByHeight[height] {
1,542✔
1806
                spendSet := n.spendNotifications[spendRequest]
53✔
1807
                for _, ntfn := range spendSet.ntfns {
151✔
1808
                        err := n.dispatchSpendDetails(ntfn, spendSet.details)
98✔
1809
                        if err != nil {
98✔
1810
                                return err
×
1811
                        }
×
1812
                }
1813
        }
1814

1815
        return nil
1,489✔
1816
}
1817

1818
// DisconnectTip handles the tip of the current chain being disconnected during
1819
// a chain reorganization. If any watched requests were included in this block,
1820
// internal structures are updated to ensure confirmation/spend notifications
1821
// are consumed (if not already), and reorg notifications are dispatched
1822
// instead. Confirmation/spend notifications will be dispatched again upon block
1823
// inclusion.
1824
func (n *TxNotifier) DisconnectTip(blockHeight uint32) error {
96✔
1825
        select {
96✔
1826
        case <-n.quit:
×
1827
                return ErrTxNotifierExiting
×
1828
        default:
96✔
1829
        }
1830

1831
        n.Lock()
96✔
1832
        defer n.Unlock()
96✔
1833

96✔
1834
        if blockHeight != n.currentHeight {
96✔
1835
                return fmt.Errorf("received blocks out of order: "+
×
1836
                        "current height=%d, disconnected height=%d",
×
1837
                        n.currentHeight, blockHeight)
×
1838
        }
×
1839
        n.currentHeight--
96✔
1840
        n.reorgDepth++
96✔
1841

96✔
1842
        // With the block disconnected, we'll update the confirm and spend hints
96✔
1843
        // for our notification requests to reflect the new height, except for
96✔
1844
        // those that have confirmed/spent at previous heights.
96✔
1845
        n.updateHints(blockHeight)
96✔
1846

96✔
1847
        // We'll go through all of our watched confirmation requests and attempt
96✔
1848
        // to drain their notification channels to ensure sending notifications
96✔
1849
        // to the clients is always non-blocking.
96✔
1850
        for initialHeight, txHashes := range n.confsByInitialHeight {
387✔
1851
                for txHash := range txHashes {
775✔
1852
                        // If the transaction/output script has been reorged out
484✔
1853
                        // of the chain, we'll make sure to remove the cached
484✔
1854
                        // confirmation details to prevent notifying clients
484✔
1855
                        // with old information.
484✔
1856
                        confSet := n.confNotifications[txHash]
484✔
1857
                        if initialHeight == blockHeight {
498✔
1858
                                confSet.details = nil
14✔
1859
                        }
14✔
1860

1861
                        for _, ntfn := range confSet.ntfns {
1,096✔
1862
                                // First, we'll attempt to drain an update
612✔
1863
                                // from each notification to ensure sends to the
612✔
1864
                                // Updates channel are always non-blocking.
612✔
1865
                                select {
612✔
1866
                                case <-ntfn.Event.Updates:
325✔
1867
                                case <-n.quit:
×
1868
                                        return ErrTxNotifierExiting
×
1869
                                default:
289✔
1870
                                }
1871

1872
                                // We also reset the num of confs update.
1873
                                ntfn.numConfsLeft = ntfn.NumConfirmations
612✔
1874

612✔
1875
                                // Then, we'll check if the current
612✔
1876
                                // transaction/output script was included in the
612✔
1877
                                // block currently being disconnected. If it
612✔
1878
                                // was, we'll need to dispatch a reorg
612✔
1879
                                // notification to the client.
612✔
1880
                                if initialHeight == blockHeight {
626✔
1881
                                        err := n.dispatchConfReorg(
14✔
1882
                                                ntfn, blockHeight,
14✔
1883
                                        )
14✔
1884
                                        if err != nil {
14✔
1885
                                                return err
×
1886
                                        }
×
1887
                                }
1888
                        }
1889
                }
1890
        }
1891

1892
        // We'll also go through our watched spend requests and attempt to drain
1893
        // their dispatched notifications to ensure dispatching notifications to
1894
        // clients later on is always non-blocking. We're only interested in
1895
        // requests whose spending transaction was included at the height being
1896
        // disconnected.
1897
        for op := range n.spendsByHeight[blockHeight] {
111✔
1898
                // Since the spending transaction is being reorged out of the
15✔
1899
                // chain, we'll need to clear out the spending details of the
15✔
1900
                // request.
15✔
1901
                spendSet := n.spendNotifications[op]
15✔
1902
                spendSet.details = nil
15✔
1903

15✔
1904
                // For all requests which have had a spend notification
15✔
1905
                // dispatched, we'll attempt to drain it and send a reorg
15✔
1906
                // notification instead.
15✔
1907
                for _, ntfn := range spendSet.ntfns {
30✔
1908
                        if err := n.dispatchSpendReorg(ntfn); err != nil {
15✔
1909
                                return err
×
1910
                        }
×
1911
                }
1912
        }
1913

1914
        // Finally, we can remove the requests that were confirmed and/or spent
1915
        // at the height being disconnected. We'll still continue to track them
1916
        // until they have been confirmed/spent and are no longer under the risk
1917
        // of being reorged out of the chain again.
1918
        delete(n.confsByInitialHeight, blockHeight)
96✔
1919
        delete(n.spendsByHeight, blockHeight)
96✔
1920

96✔
1921
        return nil
96✔
1922
}
1923

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

1952
        // Similarly, to update the height hint for all the required spend
1953
        // requests under one database transaction, we'll gather the set of
1954
        // unspent requests along with the ones that were spent at the height
1955
        // being connected/disconnected.
1956
        spendRequests := n.unspentRequests()
1,682✔
1957
        for spendRequest := range n.spendsByHeight[height] {
1,747✔
1958
                spendRequests = append(spendRequests, spendRequest)
65✔
1959
        }
65✔
1960
        err = n.spendHintCache.CommitSpendHint(n.currentHeight, spendRequests...)
1,682✔
1961
        if err != nil {
1,682✔
1962
                // The error is not fatal as this is an optimistic optimization,
×
1963
                // so we'll avoid returning an error.
×
1964
                Log.Debugf("Unable to update spend hints to %d for "+
×
1965
                        "%v: %v", n.currentHeight, spendRequests, err)
×
1966
        }
×
1967
}
1968

1969
// unconfirmedRequests returns the set of confirmation requests that are
1970
// still seen as unconfirmed by the TxNotifier.
1971
//
1972
// NOTE: This method must be called with the TxNotifier's lock held.
1973
func (n *TxNotifier) unconfirmedRequests() []ConfRequest {
1,682✔
1974
        var unconfirmed []ConfRequest
1,682✔
1975
        for confRequest, confNtfnSet := range n.confNotifications {
16,335✔
1976
                // If the notification is already aware of its confirmation
14,653✔
1977
                // details, or it's in the process of learning them, we'll skip
14,653✔
1978
                // it as we can't yet determine if it's confirmed or not.
14,653✔
1979
                if confNtfnSet.rescanStatus != rescanComplete ||
14,653✔
1980
                        confNtfnSet.details != nil {
28,615✔
1981
                        continue
13,962✔
1982
                }
1983

1984
                unconfirmed = append(unconfirmed, confRequest)
694✔
1985
        }
1986

1987
        return unconfirmed
1,682✔
1988
}
1989

1990
// unspentRequests returns the set of spend requests that are still seen as
1991
// unspent by the TxNotifier.
1992
//
1993
// NOTE: This method must be called with the TxNotifier's lock held.
1994
func (n *TxNotifier) unspentRequests() []SpendRequest {
1,682✔
1995
        var unspent []SpendRequest
1,682✔
1996
        for spendRequest, spendNtfnSet := range n.spendNotifications {
3,268✔
1997
                // If the notification is already aware of its spend details, or
1,586✔
1998
                // it's in the process of learning them, we'll skip it as we
1,586✔
1999
                // can't yet determine if it's unspent or not.
1,586✔
2000
                if spendNtfnSet.rescanStatus != rescanComplete ||
1,586✔
2001
                        spendNtfnSet.details != nil {
3,131✔
2002
                        continue
1,545✔
2003
                }
2004

2005
                unspent = append(unspent, spendRequest)
44✔
2006
        }
2007

2008
        return unspent
1,682✔
2009
}
2010

2011
// dispatchConfReorg dispatches a reorg notification to the client if the
2012
// confirmation notification was already delivered.
2013
//
2014
// NOTE: This must be called with the TxNotifier's lock held.
2015
func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
2016
        heightDisconnected uint32) error {
14✔
2017

14✔
2018
        // If the request's confirmation notification has yet to be dispatched,
14✔
2019
        // we'll need to clear its entry within the ntfnsByConfirmHeight index
14✔
2020
        // to prevent from notifying the client once the notifier reaches the
14✔
2021
        // confirmation height.
14✔
2022
        if !ntfn.dispatched {
26✔
2023
                confHeight := heightDisconnected + ntfn.NumConfirmations - 1
12✔
2024
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
12✔
2025
                if exists {
24✔
2026
                        delete(ntfnSet, ntfn)
12✔
2027
                }
12✔
2028
                return nil
12✔
2029
        }
2030

2031
        // Otherwise, the entry within the ntfnsByConfirmHeight has already been
2032
        // deleted, so we'll attempt to drain the confirmation notification to
2033
        // ensure sends to the Confirmed channel are always non-blocking.
2034
        select {
4✔
2035
        case <-ntfn.Event.Confirmed:
×
2036
        case <-n.quit:
×
2037
                return ErrTxNotifierExiting
×
2038
        default:
4✔
2039
        }
2040

2041
        ntfn.dispatched = false
4✔
2042

4✔
2043
        // Send a negative confirmation notification to the client indicating
4✔
2044
        // how many blocks have been disconnected successively.
4✔
2045
        select {
4✔
2046
        case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
4✔
2047
        case <-n.quit:
×
2048
                return ErrTxNotifierExiting
×
2049
        }
2050

2051
        return nil
4✔
2052
}
2053

2054
// dispatchSpendReorg dispatches a reorg notification to the client if a spend
2055
// notiification was already delivered.
2056
//
2057
// NOTE: This must be called with the TxNotifier's lock held.
2058
func (n *TxNotifier) dispatchSpendReorg(ntfn *SpendNtfn) error {
15✔
2059
        if !ntfn.dispatched {
15✔
2060
                return nil
×
2061
        }
×
2062

2063
        // Attempt to drain the spend notification to ensure sends to the Spend
2064
        // channel are always non-blocking.
2065
        select {
15✔
2066
        case <-ntfn.Event.Spend:
1✔
2067
        default:
14✔
2068
        }
2069

2070
        // Send a reorg notification to the client in order for them to
2071
        // correctly handle reorgs.
2072
        select {
15✔
2073
        case ntfn.Event.Reorg <- struct{}{}:
15✔
2074
        case <-n.quit:
×
2075
                return ErrTxNotifierExiting
×
2076
        }
2077

2078
        ntfn.dispatched = false
15✔
2079

15✔
2080
        return nil
15✔
2081
}
2082

2083
// TearDown is to be called when the owner of the TxNotifier is exiting. This
2084
// closes the event channels of all registered notifications that have not been
2085
// dispatched yet.
2086
func (n *TxNotifier) TearDown() {
25✔
2087
        close(n.quit)
25✔
2088

25✔
2089
        n.Lock()
25✔
2090
        defer n.Unlock()
25✔
2091

25✔
2092
        for _, confSet := range n.confNotifications {
153✔
2093
                for confID, ntfn := range confSet.ntfns {
312✔
2094
                        close(ntfn.Event.Confirmed)
184✔
2095
                        close(ntfn.Event.Updates)
184✔
2096
                        close(ntfn.Event.NegativeConf)
184✔
2097
                        close(ntfn.Event.Done)
184✔
2098
                        delete(confSet.ntfns, confID)
184✔
2099
                }
184✔
2100
        }
2101

2102
        for _, spendSet := range n.spendNotifications {
61✔
2103
                for spendID, ntfn := range spendSet.ntfns {
136✔
2104
                        close(ntfn.Event.Spend)
100✔
2105
                        close(ntfn.Event.Reorg)
100✔
2106
                        close(ntfn.Event.Done)
100✔
2107
                        delete(spendSet.ntfns, spendID)
100✔
2108
                }
100✔
2109
        }
2110
}
2111

2112
// notifyNumConfsLeft sends the number of confirmations left to the
2113
// notification subscriber through the Event.Updates channel.
2114
//
2115
// NOTE: must be used with the TxNotifier's lock held.
2116
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn, num uint32) error {
732✔
2117
        // If the number left is no less than the recorded value, we can skip
732✔
2118
        // sending it as it means this same value has already been sent before.
732✔
2119
        if num >= ntfn.numConfsLeft {
735✔
2120
                Log.Debugf("Skipped dispatched update (numConfsLeft=%v) for "+
3✔
2121
                        "request %v conf_id=%v", num, ntfn.ConfRequest,
3✔
2122
                        ntfn.ConfID)
3✔
2123

3✔
2124
                return nil
3✔
2125
        }
3✔
2126

2127
        // Update the number of confirmations left to the notification.
2128
        ntfn.numConfsLeft = num
732✔
2129

732✔
2130
        select {
732✔
2131
        case ntfn.Event.Updates <- num:
732✔
2132
        case <-n.quit:
×
2133
                return ErrTxNotifierExiting
×
2134
        }
2135

2136
        return nil
732✔
2137
}
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