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

lightningnetwork / lnd / 13157733617

05 Feb 2025 12:49PM UTC coverage: 57.712% (-1.1%) from 58.82%
13157733617

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19472 existing lines in 252 files now uncovered.

103634 of 179570 relevant lines covered (57.71%)

24840.31 hits per line

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

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

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

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

221
                return false
85✔
222
        }
223

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

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

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

354
        return r, nil
125✔
355
}
356

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

533
        sync.Mutex
534
}
535

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

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

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

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

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

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

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

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

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

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

619
        opts := defaultNotifierOptions()
224✔
620
        for _, optFunc := range optFuncs {
276✔
621
                optFunc(opts)
52✔
622
        }
52✔
623

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

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

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

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

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

220✔
664
        switch confSet.rescanStatus {
220✔
665

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

872
                return nil
114✔
873
        }
874

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

2✔
879
                return nil
2✔
880
        }
2✔
881

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

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

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

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

913
        return nil
20✔
914
}
915

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

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

21✔
929
                return nil
21✔
930
        }
21✔
931

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

48✔
936
                return nil
48✔
937
        }
48✔
938

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

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

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

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

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

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

998
        return nil
44✔
999
}
1000

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1242
        return nil
41✔
1243
}
1244

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

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

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

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

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

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

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

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

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

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

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

×
1330
                return ErrEmptyWitnessStack
×
1331
        }
×
1332

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

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

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

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

1366
        return nil
4✔
1367
}
1368

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

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

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

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

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

1406
        return nil
125✔
1407
}
1408

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

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

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

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

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

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

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

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

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

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

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

1505
        return nil
1,586✔
1506
}
1507

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
1776
                        continue
×
1777
                }
1778

1779
                Log.Debugf("Dispatching %v confirmation notification for "+
174✔
1780
                        "conf_id=%v, %v", ntfn.NumConfirmations, ntfn.ConfID,
174✔
1781
                        ntfn.ConfRequest)
174✔
1782

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

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

1804
        return nil
1,486✔
1805
}
1806

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

1820
        n.Lock()
92✔
1821
        defer n.Unlock()
92✔
1822

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

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

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

1850
                        for _, ntfn := range confSet.ntfns {
1,092✔
1851
                                // First, we'll attempt to drain an update
610✔
1852
                                // from each notification to ensure sends to the
610✔
1853
                                // Updates channel are always non-blocking.
610✔
1854
                                select {
610✔
1855
                                case <-ntfn.Event.Updates:
323✔
1856
                                case <-n.quit:
×
1857
                                        return ErrTxNotifierExiting
×
1858
                                default:
287✔
1859
                                }
1860

1861
                                // We also reset the num of confs update.
1862
                                ntfn.numConfsLeft = ntfn.NumConfirmations
610✔
1863

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

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

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

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

92✔
1910
        return nil
92✔
1911
}
1912

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

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

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

1973
                unconfirmed = append(unconfirmed, confRequest)
692✔
1974
        }
1975

1976
        return unconfirmed
1,678✔
1977
}
1978

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

1994
                unspent = append(unspent, spendRequest)
42✔
1995
        }
1996

1997
        return unspent
1,678✔
1998
}
1999

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

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

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

2030
        ntfn.dispatched = false
2✔
2031

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

2040
        return nil
2✔
2041
}
2042

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

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

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

2067
        ntfn.dispatched = false
12✔
2068

12✔
2069
        return nil
12✔
2070
}
2071

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

22✔
2078
        n.Lock()
22✔
2079
        defer n.Unlock()
22✔
2080

22✔
2081
        for _, confSet := range n.confNotifications {
147✔
2082
                for confID, ntfn := range confSet.ntfns {
306✔
2083
                        close(ntfn.Event.Confirmed)
181✔
2084
                        close(ntfn.Event.Updates)
181✔
2085
                        close(ntfn.Event.NegativeConf)
181✔
2086
                        close(ntfn.Event.Done)
181✔
2087
                        delete(confSet.ntfns, confID)
181✔
2088
                }
181✔
2089
        }
2090

2091
        for _, spendSet := range n.spendNotifications {
55✔
2092
                for spendID, ntfn := range spendSet.ntfns {
130✔
2093
                        close(ntfn.Event.Spend)
97✔
2094
                        close(ntfn.Event.Reorg)
97✔
2095
                        close(ntfn.Event.Done)
97✔
2096
                        delete(spendSet.ntfns, spendID)
97✔
2097
                }
97✔
2098
        }
2099
}
2100

2101
// notifyNumConfsLeft sends the number of confirmations left to the
2102
// notification subscriber through the Event.Updates channel.
2103
//
2104
// NOTE: must be used with the TxNotifier's lock held.
2105
func (n *TxNotifier) notifyNumConfsLeft(ntfn *ConfNtfn, num uint32) error {
729✔
2106
        // If the number left is no less than the recorded value, we can skip
729✔
2107
        // sending it as it means this same value has already been sent before.
729✔
2108
        if num >= ntfn.numConfsLeft {
729✔
UNCOV
2109
                Log.Debugf("Skipped dispatched update (numConfsLeft=%v) for "+
×
UNCOV
2110
                        "request %v conf_id=%v", num, ntfn.ConfRequest,
×
UNCOV
2111
                        ntfn.ConfID)
×
UNCOV
2112

×
UNCOV
2113
                return nil
×
UNCOV
2114
        }
×
2115

2116
        // Update the number of confirmations left to the notification.
2117
        ntfn.numConfsLeft = num
729✔
2118

729✔
2119
        select {
729✔
2120
        case ntfn.Event.Updates <- num:
729✔
2121
        case <-n.quit:
×
2122
                return ErrTxNotifierExiting
×
2123
        }
2124

2125
        return nil
729✔
2126
}
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