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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

87.05
/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 {
350✔
214
                pkScript := r.PkScript.Script()
138✔
215
                for _, txOut := range tx.TxOut {
305✔
216
                        if bytes.Equal(txOut.PkScript, pkScript) {
219✔
217
                                return true
52✔
218
                        }
52✔
219
                }
220

221
                return false
86✔
222
        }
223

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

228
        return scriptMatches()
84✔
229
}
230

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

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

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

247
        // Event contains references to the channels that the notifications are to
248
        // 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 yet.
256
        dispatched bool
257

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

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

271
        // StartHeight specifies the block height at which to begin the
272
        // historical rescan.
273
        StartHeight uint32
274

275
        // EndHeight specifies the last block height (inclusive) that the
276
        // historical scan should consider.
277
        EndHeight uint32
278
}
279

280
// ConfRegistration encompasses all of the information required for callers to
281
// retrieve details about a confirmation event.
282
type ConfRegistration struct {
283
        // Event contains references to the channels that the notifications are
284
        // to be sent over.
285
        Event *ConfirmationEvent
286

287
        // HistoricalDispatch, if non-nil, signals to the client who registered
288
        // the notification that they are responsible for attempting to manually
289
        // rescan blocks for the txid/output script between the start and end
290
        // heights.
291
        HistoricalDispatch *HistoricalConfDispatch
292

293
        // Height is the height of the TxNotifier at the time the confirmation
294
        // notification was registered. This can be used so that backends can
295
        // request to be notified of confirmations from this point forwards.
296
        Height uint32
297
}
298

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

308
        // PkScript is the script of the outpoint. If a zero outpoint is set,
309
        // then this can be an arbitrary script.
310
        PkScript txscript.PkScript
311
}
312

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

323
        // We'll only set an outpoint for which we'll dispatch a spend
324
        // notification on this request if one was provided. Otherwise, we'll
325
        // default to dispatching on the spend of the script instead.
326
        if op != nil {
198✔
327
                r.OutPoint = *op
73✔
328
        }
73✔
329
        r.PkScript = outputScript
125✔
330

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

344
                // We have an outpoint, so we can set the pkScript to an all
345
                // zero Taproot key that we'll compare this spend request to.
UNCOV
346
                r.PkScript = ZeroTaprootPkScript
×
347
        }
348

349
        return r, nil
125✔
350
}
351

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

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

375
                return false, 0, nil
4✔
376
        }
377

378
        for i, txIn := range tx.TxIn {
8✔
379
                pkScript, err := txscript.ComputePkScript(
4✔
380
                        txIn.SignatureScript, txIn.Witness,
4✔
381
                )
4✔
382
                if err == txscript.ErrUnsupportedScriptType {
4✔
383
                        continue
×
384
                }
385
                if err != nil {
4✔
386
                        return false, 0, err
×
387
                }
×
388

389
                if bytes.Equal(pkScript.Script(), r.PkScript.Script()) {
4✔
390
                        return true, uint32(i), nil
×
391
                }
×
392
        }
393

394
        return false, 0, nil
4✔
395
}
396

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

405
        // SpendRequest represents either the outpoint or script we should
406
        // detect the spend of.
407
        SpendRequest
408

409
        // Event contains references to the channels that the notifications are
410
        // to be sent over.
411
        Event *SpendEvent
412

413
        // HeightHint is the earliest height in the chain that we expect to find
414
        // the spending transaction of the specified outpoint/output script.
415
        // This value will be overridden by the spend hint cache if it contains
416
        // an entry for it.
417
        HeightHint uint32
418

419
        // dispatched signals whether a spend notification has been dispatched
420
        // to the client.
421
        dispatched bool
422
}
423

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

433
        // StartHeight specified the block height at which to begin the
434
        // historical rescan.
435
        StartHeight uint32
436

437
        // EndHeight specifies the last block height (inclusive) that the
438
        // historical rescan should consider.
439
        EndHeight uint32
440
}
441

442
// SpendRegistration encompasses all of the information required for callers to
443
// retrieve details about a spend event.
444
type SpendRegistration struct {
445
        // Event contains references to the channels that the notifications are
446
        // to be sent over.
447
        Event *SpendEvent
448

449
        // HistoricalDispatch, if non-nil, signals to the client who registered
450
        // the notification that they are responsible for attempting to manually
451
        // rescan blocks for the txid/output script between the start and end
452
        // heights.
453
        HistoricalDispatch *HistoricalSpendDispatch
454

455
        // Height is the height of the TxNotifier at the time the spend
456
        // notification was registered. This can be used so that backends can
457
        // request to be notified of spends from this point forwards.
458
        Height uint32
459
}
460

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

470
        // currentHeight is the height of the tracked blockchain. It is used to
471
        // determine the number of confirmations a tx has and ensure blocks are
472
        // connected and disconnected in order.
473
        currentHeight uint32
474

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

482
        // reorgDepth is the depth of a chain organization that this system is
483
        // being informed of. This is incremented as long as a sequence of
484
        // blocks are disconnected without being interrupted by a new block.
485
        reorgDepth uint32
486

487
        // confNotifications is an index of confirmation notification requests
488
        // by transaction hash/output script.
489
        confNotifications map[ConfRequest]*confNtfnSet
490

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

498
        // ntfnsByConfirmHeight is an index of notification requests by the
499
        // height at which the transaction/output script will have sufficient
500
        // confirmations.
501
        ntfnsByConfirmHeight map[uint32]map[*ConfNtfn]struct{}
502

503
        // spendNotifications is an index of all active notification requests
504
        // per outpoint/output script.
505
        spendNotifications map[SpendRequest]*spendNtfnSet
506

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

513
        // confirmHintCache is a cache used to maintain the latest height hints
514
        // for transactions/output scripts. Each height hint represents the
515
        // earliest height at which they scripts could have been confirmed
516
        // within the chain.
517
        confirmHintCache ConfirmHintCache
518

519
        // spendHintCache is a cache used to maintain the latest height hints
520
        // for outpoints/output scripts. Each height hint represents the
521
        // earliest height at which they could have been spent within the chain.
522
        spendHintCache SpendHintCache
523

524
        // quit is closed in order to signal that the notifier is gracefully
525
        // exiting.
526
        quit chan struct{}
527

528
        sync.Mutex
529
}
530

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

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

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

224✔
559
        // An accompanying output script must always be provided.
224✔
560
        if len(pkScript) == 0 {
225✔
561
                return nil, ErrNoScript
1✔
562
        }
1✔
563

564
        // Enforce that we will not dispatch confirmations beyond the reorg
565
        // safety limit.
566
        if numConfs == 0 || numConfs > n.reorgSafetyLimit {
225✔
567
                return nil, ErrNumConfsOutOfRange
2✔
568
        }
2✔
569

570
        // A height hint must be provided to prevent scanning from the genesis
571
        // block.
572
        if heightHint == 0 {
222✔
573
                return nil, ErrNoHeightHint
1✔
574
        }
1✔
575

576
        // Ensure the output script is of a supported type.
577
        confRequest, err := NewConfRequest(txid, pkScript)
220✔
578
        if err != nil {
220✔
579
                return nil, err
×
580
        }
×
581

582
        confID := atomic.AddUint64(&n.confClientCounter, 1)
220✔
583
        return &ConfNtfn{
220✔
584
                ConfID:           confID,
220✔
585
                ConfRequest:      confRequest,
220✔
586
                NumConfirmations: numConfs,
220✔
587
                Event: NewConfirmationEvent(numConfs, func() {
223✔
588
                        n.CancelConf(confRequest, confID)
3✔
589
                }),
3✔
590
                HeightHint:   heightHint,
591
                includeBlock: opts.includeBlock,
592
        }, nil
593
}
594

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

225✔
607
        select {
225✔
608
        case <-n.quit:
1✔
609
                return nil, ErrTxNotifierExiting
1✔
610
        default:
224✔
611
        }
612

613
        opts := defaultNotifierOptions()
224✔
614
        for _, optFunc := range optFuncs {
276✔
615
                optFunc(opts)
52✔
616
        }
52✔
617

618
        // We'll start by performing a series of validation checks.
619
        ntfn, err := n.newConfNtfn(txid, pkScript, numConfs, heightHint, opts)
224✔
620
        if err != nil {
228✔
621
                return nil, err
4✔
622
        }
4✔
623

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

642
        Log.Infof("New confirmation subscription: conf_id=%d, %v, "+
220✔
643
                "num_confs=%v height_hint=%d", ntfn.ConfID, ntfn.ConfRequest,
220✔
644
                numConfs, startHeight)
220✔
645

220✔
646
        n.Lock()
220✔
647
        defer n.Unlock()
220✔
648

220✔
649
        confSet, ok := n.confNotifications[ntfn.ConfRequest]
220✔
650
        if !ok {
374✔
651
                // If this is the first registration for this request, construct
154✔
652
                // a confSet to coalesce all notifications for the same request.
154✔
653
                confSet = newConfNtfnSet()
154✔
654
                n.confNotifications[ntfn.ConfRequest] = confSet
154✔
655
        }
154✔
656
        confSet.ntfns[ntfn.ConfID] = ntfn
220✔
657

220✔
658
        switch confSet.rescanStatus {
220✔
659

660
        // A prior rescan has already completed and we are actively watching at
661
        // tip for this request.
662
        case rescanComplete:
23✔
663
                // If the confirmation details for this set of notifications has
23✔
664
                // already been found, we'll attempt to deliver them immediately
23✔
665
                // to this client.
23✔
666
                Log.Debugf("Attempting to dispatch confirmation for %v on "+
23✔
667
                        "registration since rescan has finished",
23✔
668
                        ntfn.ConfRequest)
23✔
669

23✔
670
                // The default notification we assigned above includes the
23✔
671
                // block along with the rest of the details. However not all
23✔
672
                // clients want the block, so we make a copy here w/o the block
23✔
673
                // if needed so we can give clients only what they ask for.
23✔
674
                confDetails := confSet.details
23✔
675
                if !ntfn.includeBlock && confDetails != nil {
34✔
676
                        confDetailsCopy := *confDetails
11✔
677
                        confDetailsCopy.Block = nil
11✔
678

11✔
679
                        confDetails = &confDetailsCopy
11✔
680
                }
11✔
681

682
                err := n.dispatchConfDetails(ntfn, confDetails)
23✔
683
                if err != nil {
23✔
684
                        return nil, err
×
685
                }
×
686

687
                return &ConfRegistration{
23✔
688
                        Event:              ntfn.Event,
23✔
689
                        HistoricalDispatch: nil,
23✔
690
                        Height:             n.currentHeight,
23✔
691
                }, nil
23✔
692

693
        // A rescan is already in progress, return here to prevent dispatching
694
        // another. When the rescan returns, this notification's details will be
695
        // updated as well.
696
        case rescanPending:
43✔
697
                Log.Debugf("Waiting for pending rescan to finish before "+
43✔
698
                        "notifying %v at tip", ntfn.ConfRequest)
43✔
699

43✔
700
                return &ConfRegistration{
43✔
701
                        Event:              ntfn.Event,
43✔
702
                        HistoricalDispatch: nil,
43✔
703
                        Height:             n.currentHeight,
43✔
704
                }, nil
43✔
705

706
        // If no rescan has been dispatched, attempt to do so now.
707
        case rescanNotStarted:
154✔
708
        }
709

710
        // If the provided or cached height hint indicates that the
711
        // transaction with the given txid/output script is to be confirmed at a
712
        // height greater than the notifier's current height, we'll refrain from
713
        // spawning a historical dispatch.
714
        if startHeight > n.currentHeight {
158✔
715
                Log.Debugf("Height hint is above current height, not "+
4✔
716
                        "dispatching historical confirmation rescan for %v",
4✔
717
                        ntfn.ConfRequest)
4✔
718

4✔
719
                // Set the rescan status to complete, which will allow the
4✔
720
                // notifier to start delivering messages for this set
4✔
721
                // immediately.
4✔
722
                confSet.rescanStatus = rescanComplete
4✔
723
                return &ConfRegistration{
4✔
724
                        Event:              ntfn.Event,
4✔
725
                        HistoricalDispatch: nil,
4✔
726
                        Height:             n.currentHeight,
4✔
727
                }, nil
4✔
728
        }
4✔
729

730
        Log.Debugf("Dispatching historical confirmation rescan for %v",
150✔
731
                ntfn.ConfRequest)
150✔
732

150✔
733
        // Construct the parameters for historical dispatch, scanning the range
150✔
734
        // of blocks between our best known height hint and the notifier's
150✔
735
        // current height. The notifier will begin also watching for
150✔
736
        // confirmations at tip starting with the next block.
150✔
737
        dispatch := &HistoricalConfDispatch{
150✔
738
                ConfRequest: ntfn.ConfRequest,
150✔
739
                StartHeight: startHeight,
150✔
740
                EndHeight:   n.currentHeight,
150✔
741
        }
150✔
742

150✔
743
        // Set this confSet's status to pending, ensuring subsequent
150✔
744
        // registrations don't also attempt a dispatch.
150✔
745
        confSet.rescanStatus = rescanPending
150✔
746

150✔
747
        return &ConfRegistration{
150✔
748
                Event:              ntfn.Event,
150✔
749
                HistoricalDispatch: dispatch,
150✔
750
                Height:             n.currentHeight,
150✔
751
        }, nil
150✔
752
}
753

754
// CancelConf cancels an existing request for a spend notification of an
755
// outpoint/output script. The request is identified by its spend ID.
756
func (n *TxNotifier) CancelConf(confRequest ConfRequest, confID uint64) {
3✔
757
        select {
3✔
758
        case <-n.quit:
×
759
                return
×
760
        default:
3✔
761
        }
762

763
        n.Lock()
3✔
764
        defer n.Unlock()
3✔
765

3✔
766
        confSet, ok := n.confNotifications[confRequest]
3✔
767
        if !ok {
3✔
768
                return
×
769
        }
×
770
        ntfn, ok := confSet.ntfns[confID]
3✔
771
        if !ok {
3✔
772
                return
×
773
        }
×
774

775
        Log.Debugf("Canceling confirmation notification: conf_id=%d, %v",
3✔
776
                confID, confRequest)
3✔
777

3✔
778
        // We'll close all the notification channels to let the client know
3✔
779
        // their cancel request has been fulfilled.
3✔
780
        close(ntfn.Event.Confirmed)
3✔
781
        close(ntfn.Event.Updates)
3✔
782
        close(ntfn.Event.NegativeConf)
3✔
783

3✔
784
        // Finally, we'll clean up any lingering references to this
3✔
785
        // notification.
3✔
786
        delete(confSet.ntfns, confID)
3✔
787

3✔
788
        // Remove the queued confirmation notification if the transaction has
3✔
789
        // already confirmed, but hasn't met its required number of
3✔
790
        // confirmations.
3✔
791
        if confSet.details != nil {
5✔
792
                confHeight := confSet.details.BlockHeight +
2✔
793
                        ntfn.NumConfirmations - 1
2✔
794
                delete(n.ntfnsByConfirmHeight[confHeight], ntfn)
2✔
795
        }
2✔
796
}
797

798
// UpdateConfDetails attempts to update the confirmation details for an active
799
// notification within the notifier. This should only be used in the case of a
800
// transaction/output script that has confirmed before the notifier's current
801
// height.
802
//
803
// NOTE: The notification should be registered first to ensure notifications are
804
// dispatched correctly.
805
func (n *TxNotifier) UpdateConfDetails(confRequest ConfRequest,
806
        details *TxConfirmation) error {
141✔
807

141✔
808
        select {
141✔
809
        case <-n.quit:
×
810
                return ErrTxNotifierExiting
×
811
        default:
141✔
812
        }
813

814
        // Ensure we hold the lock throughout handling the notification to
815
        // prevent the notifier from advancing its height underneath us.
816
        n.Lock()
141✔
817
        defer n.Unlock()
141✔
818

141✔
819
        // First, we'll determine whether we have an active confirmation
141✔
820
        // notification for the given txid/script.
141✔
821
        confSet, ok := n.confNotifications[confRequest]
141✔
822
        if !ok {
141✔
823
                return fmt.Errorf("confirmation notification for %v not found",
×
824
                        confRequest)
×
825
        }
×
826

827
        // If the confirmation details were already found at tip, all existing
828
        // notifications will have been dispatched or queued for dispatch. We
829
        // can exit early to avoid sending too many notifications on the
830
        // buffered channels.
831
        if confSet.details != nil {
143✔
832
                return nil
2✔
833
        }
2✔
834

835
        // The historical dispatch has been completed for this confSet. We'll
836
        // update the rescan status and cache any details that were found. If
837
        // the details are nil, that implies we did not find them and will
838
        // continue to watch for them at tip.
839
        confSet.rescanStatus = rescanComplete
139✔
840

139✔
841
        // The notifier has yet to reach the height at which the
139✔
842
        // transaction/output script was included in a block, so we should defer
139✔
843
        // until handling it then within ConnectTip.
139✔
844
        if details == nil {
256✔
845
                Log.Debugf("Confirmation details for %v not found during "+
117✔
846
                        "historical dispatch, waiting to dispatch at tip",
117✔
847
                        confRequest)
117✔
848

117✔
849
                // We'll commit the current height as the confirm hint to
117✔
850
                // prevent another potentially long rescan if we restart before
117✔
851
                // a new block comes in.
117✔
852
                err := n.confirmHintCache.CommitConfirmHint(
117✔
853
                        n.currentHeight, confRequest,
117✔
854
                )
117✔
855
                if err != nil {
117✔
856
                        // The error is not fatal as this is an optimistic
×
857
                        // optimization, so we'll avoid returning an error.
×
858
                        Log.Debugf("Unable to update confirm hint to %d for "+
×
859
                                "%v: %v", n.currentHeight, confRequest, err)
×
860
                }
×
861

862
                return nil
117✔
863
        }
864

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

2✔
869
                return nil
2✔
870
        }
2✔
871

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

20✔
874
        err := n.confirmHintCache.CommitConfirmHint(
20✔
875
                details.BlockHeight, confRequest,
20✔
876
        )
20✔
877
        if err != nil {
20✔
878
                // The error is not fatal, so we should not return an error to
×
879
                // the caller.
×
880
                Log.Errorf("Unable to update confirm hint to %d for %v: %v",
×
881
                        details.BlockHeight, confRequest, err)
×
882
        }
×
883

884
        // Cache the details found in the rescan and attempt to dispatch any
885
        // notifications that have not yet been delivered.
886
        confSet.details = details
20✔
887
        for _, ntfn := range confSet.ntfns {
45✔
888
                // The default notification we assigned above includes the
25✔
889
                // block along with the rest of the details. However not all
25✔
890
                // clients want the block, so we make a copy here w/o the block
25✔
891
                // if needed so we can give clients only what they ask for.
25✔
892
                confDetails := *details
25✔
893
                if !ntfn.includeBlock {
42✔
894
                        confDetails.Block = nil
17✔
895
                }
17✔
896

897
                err = n.dispatchConfDetails(ntfn, &confDetails)
25✔
898
                if err != nil {
25✔
899
                        return err
×
900
                }
×
901
        }
902

903
        return nil
20✔
904
}
905

906
// dispatchConfDetails attempts to cache and dispatch details to a particular
907
// client if the transaction/output script has sufficiently confirmed. If the
908
// provided details are nil, this method will be a no-op.
909
func (n *TxNotifier) dispatchConfDetails(
910
        ntfn *ConfNtfn, details *TxConfirmation) error {
48✔
911

48✔
912
        // If there are no conf details to dispatch or if the notification has
48✔
913
        // already been dispatched, then we can skip dispatching to this
48✔
914
        // client.
48✔
915
        if details == nil || ntfn.dispatched {
52✔
916
                Log.Debugf("Skipping dispatch of conf details(%v) for "+
4✔
917
                        "request %v, dispatched=%v", details, ntfn.ConfRequest,
4✔
918
                        ntfn.dispatched)
4✔
919

4✔
920
                return nil
4✔
921
        }
4✔
922

923
        // Now, we'll examine whether the transaction/output script of this
924
        // request has reached its required number of confirmations. If it has,
925
        // we'll dispatch a confirmation notification to the caller.
926
        confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
44✔
927
        if confHeight <= n.currentHeight {
79✔
928
                Log.Debugf("Dispatching %v confirmation notification for %v",
35✔
929
                        ntfn.NumConfirmations, ntfn.ConfRequest)
35✔
930

35✔
931
                // We'll send a 0 value to the Updates channel,
35✔
932
                // indicating that the transaction/output script has already
35✔
933
                // been confirmed.
35✔
934
                select {
35✔
935
                case ntfn.Event.Updates <- 0:
35✔
936
                case <-n.quit:
×
937
                        return ErrTxNotifierExiting
×
938
                }
939

940
                select {
35✔
941
                case ntfn.Event.Confirmed <- details:
35✔
942
                        ntfn.dispatched = true
35✔
943
                case <-n.quit:
×
944
                        return ErrTxNotifierExiting
×
945
                }
946
        } else {
9✔
947
                Log.Debugf("Queueing %v confirmation notification for %v at tip ",
9✔
948
                        ntfn.NumConfirmations, ntfn.ConfRequest)
9✔
949

9✔
950
                // Otherwise, we'll keep track of the notification
9✔
951
                // request by the height at which we should dispatch the
9✔
952
                // confirmation notification.
9✔
953
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
9✔
954
                if !exists {
18✔
955
                        ntfnSet = make(map[*ConfNtfn]struct{})
9✔
956
                        n.ntfnsByConfirmHeight[confHeight] = ntfnSet
9✔
957
                }
9✔
958
                ntfnSet[ntfn] = struct{}{}
9✔
959

9✔
960
                // We'll also send an update to the client of how many
9✔
961
                // confirmations are left for the transaction/output script to
9✔
962
                // be confirmed.
9✔
963
                numConfsLeft := confHeight - n.currentHeight
9✔
964
                select {
9✔
965
                case ntfn.Event.Updates <- numConfsLeft:
9✔
966
                case <-n.quit:
×
967
                        return ErrTxNotifierExiting
×
968
                }
969
        }
970

971
        // As a final check, we'll also watch the transaction/output script if
972
        // it's still possible for it to get reorged out of the chain.
973
        reorgSafeHeight := details.BlockHeight + n.reorgSafetyLimit
44✔
974
        if reorgSafeHeight > n.currentHeight {
88✔
975
                txSet, exists := n.confsByInitialHeight[details.BlockHeight]
44✔
976
                if !exists {
55✔
977
                        txSet = make(map[ConfRequest]struct{})
11✔
978
                        n.confsByInitialHeight[details.BlockHeight] = txSet
11✔
979
                }
11✔
980
                txSet[ntfn.ConfRequest] = struct{}{}
44✔
981
        }
982

983
        return nil
44✔
984
}
985

986
// newSpendNtfn validates all of the parameters required to successfully create
987
// and register a spend notification.
988
func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint,
989
        pkScript []byte, heightHint uint32) (*SpendNtfn, error) {
127✔
990

127✔
991
        // An accompanying output script must always be provided.
127✔
992
        if len(pkScript) == 0 {
128✔
993
                return nil, ErrNoScript
1✔
994
        }
1✔
995

996
        // A height hint must be provided to prevent scanning from the genesis
997
        // block.
998
        if heightHint == 0 {
127✔
999
                return nil, ErrNoHeightHint
1✔
1000
        }
1✔
1001

1002
        // Ensure the output script is of a supported type.
1003
        spendRequest, err := NewSpendRequest(outpoint, pkScript)
125✔
1004
        if err != nil {
125✔
UNCOV
1005
                return nil, err
×
UNCOV
1006
        }
×
1007

1008
        spendID := atomic.AddUint64(&n.spendClientCounter, 1)
125✔
1009
        return &SpendNtfn{
125✔
1010
                SpendID:      spendID,
125✔
1011
                SpendRequest: spendRequest,
125✔
1012
                Event: NewSpendEvent(func() {
133✔
1013
                        n.CancelSpend(spendRequest, spendID)
8✔
1014
                }),
8✔
1015
                HeightHint: heightHint,
1016
        }, nil
1017
}
1018

1019
// RegisterSpend handles a new spend notification request. The client will be
1020
// notified once the outpoint/output script is detected as spent within the
1021
// chain.
1022
//
1023
// NOTE: If the outpoint/output script has already been spent within the chain
1024
// before the notifier's current tip, the spend details must be provided with
1025
// the UpdateSpendDetails method, otherwise we will wait for the outpoint/output
1026
// script to be spent at tip, even though it already has.
1027
func (n *TxNotifier) RegisterSpend(outpoint *wire.OutPoint, pkScript []byte,
1028
        heightHint uint32) (*SpendRegistration, error) {
128✔
1029

128✔
1030
        select {
128✔
1031
        case <-n.quit:
1✔
1032
                return nil, ErrTxNotifierExiting
1✔
1033
        default:
127✔
1034
        }
1035

1036
        // We'll start by performing a series of validation checks.
1037
        ntfn, err := n.newSpendNtfn(outpoint, pkScript, heightHint)
127✔
1038
        if err != nil {
129✔
1039
                return nil, err
2✔
1040
        }
2✔
1041

1042
        // Before proceeding to register the notification, we'll query our spend
1043
        // hint cache to determine whether a better one exists.
1044
        startHeight := ntfn.HeightHint
125✔
1045
        hint, err := n.spendHintCache.QuerySpendHint(ntfn.SpendRequest)
125✔
1046
        if err == nil {
156✔
1047
                if hint > startHeight {
50✔
1048
                        Log.Debugf("Using height hint %d retrieved from cache "+
19✔
1049
                                "for %v instead of %d for spend subscription",
19✔
1050
                                hint, ntfn.SpendRequest, startHeight)
19✔
1051
                        startHeight = hint
19✔
1052
                }
19✔
1053
        } else if err != ErrSpendHintNotFound {
94✔
1054
                Log.Errorf("Unable to query spend hint for %v: %v",
×
1055
                        ntfn.SpendRequest, err)
×
1056
        }
×
1057

1058
        n.Lock()
125✔
1059
        defer n.Unlock()
125✔
1060

125✔
1061
        Log.Debugf("New spend subscription: spend_id=%d, %v, height_hint=%d",
125✔
1062
                ntfn.SpendID, ntfn.SpendRequest, startHeight)
125✔
1063

125✔
1064
        // Keep track of the notification request so that we can properly
125✔
1065
        // dispatch a spend notification later on.
125✔
1066
        spendSet, ok := n.spendNotifications[ntfn.SpendRequest]
125✔
1067
        if !ok {
171✔
1068
                // If this is the first registration for the request, we'll
46✔
1069
                // construct a spendNtfnSet to coalesce all notifications.
46✔
1070
                spendSet = newSpendNtfnSet()
46✔
1071
                n.spendNotifications[ntfn.SpendRequest] = spendSet
46✔
1072
        }
46✔
1073
        spendSet.ntfns[ntfn.SpendID] = ntfn
125✔
1074

125✔
1075
        // We'll now let the caller know whether a historical rescan is needed
125✔
1076
        // depending on the current rescan status.
125✔
1077
        switch spendSet.rescanStatus {
125✔
1078

1079
        // If the spending details for this request have already been determined
1080
        // and cached, then we can use them to immediately dispatch the spend
1081
        // notification to the client.
1082
        case rescanComplete:
69✔
1083
                Log.Debugf("Attempting to dispatch spend for %v on "+
69✔
1084
                        "registration since rescan has finished",
69✔
1085
                        ntfn.SpendRequest)
69✔
1086

69✔
1087
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
69✔
1088
                if err != nil {
69✔
1089
                        return nil, err
×
1090
                }
×
1091

1092
                return &SpendRegistration{
69✔
1093
                        Event:              ntfn.Event,
69✔
1094
                        HistoricalDispatch: nil,
69✔
1095
                        Height:             n.currentHeight,
69✔
1096
                }, nil
69✔
1097

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

10✔
1104
                return &SpendRegistration{
10✔
1105
                        Event:              ntfn.Event,
10✔
1106
                        HistoricalDispatch: nil,
10✔
1107
                        Height:             n.currentHeight,
10✔
1108
                }, nil
10✔
1109

1110
        // Otherwise, we'll fall through and let the caller know that a rescan
1111
        // should be dispatched to determine whether the request has already
1112
        // been spent.
1113
        case rescanNotStarted:
46✔
1114
        }
1115

1116
        // However, if the spend hint, either provided by the caller or
1117
        // retrieved from the cache, is found to be at a later height than the
1118
        // TxNotifier is aware of, then we'll refrain from dispatching a
1119
        // historical rescan and wait for the spend to come in at tip.
1120
        if startHeight > n.currentHeight {
70✔
1121
                Log.Debugf("Spend hint of %d for %v is above current height %d",
24✔
1122
                        startHeight, ntfn.SpendRequest, n.currentHeight)
24✔
1123

24✔
1124
                // We'll also set the rescan status as complete to ensure that
24✔
1125
                // spend hints for this request get updated upon
24✔
1126
                // connected/disconnected blocks.
24✔
1127
                spendSet.rescanStatus = rescanComplete
24✔
1128
                return &SpendRegistration{
24✔
1129
                        Event:              ntfn.Event,
24✔
1130
                        HistoricalDispatch: nil,
24✔
1131
                        Height:             n.currentHeight,
24✔
1132
                }, nil
24✔
1133
        }
24✔
1134

1135
        // We'll set the rescan status to pending to ensure subsequent
1136
        // notifications don't also attempt a historical dispatch.
1137
        spendSet.rescanStatus = rescanPending
22✔
1138

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

22✔
1142
        return &SpendRegistration{
22✔
1143
                Event: ntfn.Event,
22✔
1144
                HistoricalDispatch: &HistoricalSpendDispatch{
22✔
1145
                        SpendRequest: ntfn.SpendRequest,
22✔
1146
                        StartHeight:  startHeight,
22✔
1147
                        EndHeight:    n.currentHeight,
22✔
1148
                },
22✔
1149
                Height: n.currentHeight,
22✔
1150
        }, nil
22✔
1151
}
1152

1153
// CancelSpend cancels an existing request for a spend notification of an
1154
// outpoint/output script. The request is identified by its spend ID.
1155
func (n *TxNotifier) CancelSpend(spendRequest SpendRequest, spendID uint64) {
9✔
1156
        select {
9✔
1157
        case <-n.quit:
×
1158
                return
×
1159
        default:
9✔
1160
        }
1161

1162
        n.Lock()
9✔
1163
        defer n.Unlock()
9✔
1164

9✔
1165
        spendSet, ok := n.spendNotifications[spendRequest]
9✔
1166
        if !ok {
9✔
1167
                return
×
1168
        }
×
1169
        ntfn, ok := spendSet.ntfns[spendID]
9✔
1170
        if !ok {
9✔
1171
                return
×
1172
        }
×
1173

1174
        Log.Debugf("Canceling spend notification: spend_id=%d, %v", spendID,
9✔
1175
                spendRequest)
9✔
1176

9✔
1177
        // We'll close all the notification channels to let the client know
9✔
1178
        // their cancel request has been fulfilled.
9✔
1179
        close(ntfn.Event.Spend)
9✔
1180
        close(ntfn.Event.Reorg)
9✔
1181
        close(ntfn.Event.Done)
9✔
1182
        delete(spendSet.ntfns, spendID)
9✔
1183
}
1184

1185
// ProcessRelevantSpendTx processes a transaction provided externally. This will
1186
// check whether the transaction is relevant to the notifier if it spends any
1187
// outpoints/output scripts for which we currently have registered notifications
1188
// for. If it is relevant, spend notifications will be dispatched to the caller.
1189
func (n *TxNotifier) ProcessRelevantSpendTx(tx *btcutil.Tx,
1190
        blockHeight uint32) error {
41✔
1191

41✔
1192
        select {
41✔
1193
        case <-n.quit:
×
1194
                return ErrTxNotifierExiting
×
1195
        default:
41✔
1196
        }
1197

1198
        // Ensure we hold the lock throughout handling the notification to
1199
        // prevent the notifier from advancing its height underneath us.
1200
        n.Lock()
41✔
1201
        defer n.Unlock()
41✔
1202

41✔
1203
        // We'll use a channel to coalesce all the spend requests that this
41✔
1204
        // transaction fulfills.
41✔
1205
        type spend struct {
41✔
1206
                request *SpendRequest
41✔
1207
                details *SpendDetail
41✔
1208
        }
41✔
1209

41✔
1210
        // We'll set up the onSpend filter callback to gather all the fulfilled
41✔
1211
        // spends requests within this transaction.
41✔
1212
        var spends []spend
41✔
1213
        onSpend := func(request SpendRequest, details *SpendDetail) {
76✔
1214
                spends = append(spends, spend{&request, details})
35✔
1215
        }
35✔
1216
        n.filterTx(nil, tx, blockHeight, nil, onSpend)
41✔
1217

41✔
1218
        // After the transaction has been filtered, we can finally dispatch
41✔
1219
        // notifications for each request.
41✔
1220
        for _, spend := range spends {
76✔
1221
                err := n.updateSpendDetails(*spend.request, spend.details)
35✔
1222
                if err != nil {
35✔
1223
                        return err
×
1224
                }
×
1225
        }
1226

1227
        return nil
41✔
1228
}
1229

1230
// UpdateSpendDetails attempts to update the spend details for all active spend
1231
// notification requests for an outpoint/output script. This method should be
1232
// used once a historical scan of the chain has finished. If the historical scan
1233
// did not find a spending transaction for it, the spend details may be nil.
1234
//
1235
// NOTE: A notification request for the outpoint/output script must be
1236
// registered first to ensure notifications are delivered.
1237
func (n *TxNotifier) UpdateSpendDetails(spendRequest SpendRequest,
1238
        details *SpendDetail) error {
13✔
1239

13✔
1240
        select {
13✔
1241
        case <-n.quit:
×
1242
                return ErrTxNotifierExiting
×
1243
        default:
13✔
1244
        }
1245

1246
        // Ensure we hold the lock throughout handling the notification to
1247
        // prevent the notifier from advancing its height underneath us.
1248
        n.Lock()
13✔
1249
        defer n.Unlock()
13✔
1250

13✔
1251
        return n.updateSpendDetails(spendRequest, details)
13✔
1252
}
1253

1254
// updateSpendDetails attempts to update the spend details for all active spend
1255
// notification requests for an outpoint/output script. This method should be
1256
// used once a historical scan of the chain has finished. If the historical scan
1257
// did not find a spending transaction for it, the spend details may be nil.
1258
//
1259
// NOTE: This method must be called with the TxNotifier's lock held.
1260
func (n *TxNotifier) updateSpendDetails(spendRequest SpendRequest,
1261
        details *SpendDetail) error {
48✔
1262

48✔
1263
        // Mark the ongoing historical rescan for this request as finished. This
48✔
1264
        // will allow us to update the spend hints for it at tip.
48✔
1265
        spendSet, ok := n.spendNotifications[spendRequest]
48✔
1266
        if !ok {
49✔
1267
                return fmt.Errorf("spend notification for %v not found",
1✔
1268
                        spendRequest)
1✔
1269
        }
1✔
1270

1271
        // If the spend details have already been found either at tip, then the
1272
        // notifications should have already been dispatched, so we can exit
1273
        // early to prevent sending duplicate notifications.
1274
        if spendSet.details != nil {
63✔
1275
                return nil
16✔
1276
        }
16✔
1277

1278
        // Since the historical rescan has completed for this request, we'll
1279
        // mark its rescan status as complete in order to ensure that the
1280
        // TxNotifier can properly update its spend hints upon
1281
        // connected/disconnected blocks.
1282
        spendSet.rescanStatus = rescanComplete
31✔
1283

31✔
1284
        // If the historical rescan was not able to find a spending transaction
31✔
1285
        // for this request, then we can track the spend at tip.
31✔
1286
        if details == nil {
36✔
1287
                // We'll commit the current height as the spend hint to prevent
5✔
1288
                // another potentially long rescan if we restart before a new
5✔
1289
                // block comes in.
5✔
1290
                err := n.spendHintCache.CommitSpendHint(
5✔
1291
                        n.currentHeight, spendRequest,
5✔
1292
                )
5✔
1293
                if err != nil {
5✔
1294
                        // The error is not fatal as this is an optimistic
×
1295
                        // optimization, so we'll avoid returning an error.
×
1296
                        Log.Debugf("Unable to update spend hint to %d for %v: %v",
×
1297
                                n.currentHeight, spendRequest, err)
×
1298
                }
×
1299

1300
                Log.Debugf("Updated spend hint to height=%v for unconfirmed "+
5✔
1301
                        "spend request %v", n.currentHeight, spendRequest)
5✔
1302
                return nil
5✔
1303
        }
1304

1305
        // Return an error if the witness data is not present in the spending
1306
        // transaction.
1307
        //
1308
        // NOTE: if the witness stack is empty, we will do a critical log which
1309
        // shuts down the node.
1310
        if !details.HasSpenderWitness() {
26✔
1311
                Log.Criticalf("Found spending tx for outpoint=%v, but the "+
×
1312
                        "transaction %v does not have witness",
×
1313
                        spendRequest.OutPoint, details.SpendingTx.TxHash())
×
1314

×
1315
                return ErrEmptyWitnessStack
×
1316
        }
×
1317

1318
        // If the historical rescan found the spending transaction for this
1319
        // request, but it's at a later height than the notifier (this can
1320
        // happen due to latency with the backend during a reorg), then we'll
1321
        // defer handling the notification until the notifier has caught up to
1322
        // such height.
1323
        if uint32(details.SpendingHeight) > n.currentHeight {
48✔
1324
                return nil
22✔
1325
        }
22✔
1326

1327
        // Now that we've determined the request has been spent, we'll commit
1328
        // its spending height as its hint in the cache and dispatch
1329
        // notifications to all of its respective clients.
1330
        err := n.spendHintCache.CommitSpendHint(
4✔
1331
                uint32(details.SpendingHeight), spendRequest,
4✔
1332
        )
4✔
1333
        if err != nil {
4✔
1334
                // The error is not fatal as this is an optimistic optimization,
×
1335
                // so we'll avoid returning an error.
×
1336
                Log.Debugf("Unable to update spend hint to %d for %v: %v",
×
1337
                        details.SpendingHeight, spendRequest, err)
×
1338
        }
×
1339

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

4✔
1343
        spendSet.details = details
4✔
1344
        for _, ntfn := range spendSet.ntfns {
13✔
1345
                err := n.dispatchSpendDetails(ntfn, spendSet.details)
9✔
1346
                if err != nil {
9✔
1347
                        return err
×
1348
                }
×
1349
        }
1350

1351
        return nil
4✔
1352
}
1353

1354
// dispatchSpendDetails dispatches a spend notification to the client.
1355
//
1356
// NOTE: This must be called with the TxNotifier's lock held.
1357
func (n *TxNotifier) dispatchSpendDetails(ntfn *SpendNtfn, details *SpendDetail) error {
174✔
1358
        // If there are no spend details to dispatch or if the notification has
174✔
1359
        // already been dispatched, then we can skip dispatching to this client.
174✔
1360
        if details == nil || ntfn.dispatched {
223✔
1361
                Log.Debugf("Skipping dispatch of spend details(%v) for "+
49✔
1362
                        "request %v, dispatched=%v", details, ntfn.SpendRequest,
49✔
1363
                        ntfn.dispatched)
49✔
1364
                return nil
49✔
1365
        }
49✔
1366

1367
        Log.Debugf("Dispatching confirmed spend notification for %v at "+
125✔
1368
                "current height=%d: %v", ntfn.SpendRequest, n.currentHeight,
125✔
1369
                details)
125✔
1370

125✔
1371
        select {
125✔
1372
        case ntfn.Event.Spend <- details:
125✔
1373
                ntfn.dispatched = true
125✔
1374
        case <-n.quit:
×
1375
                return ErrTxNotifierExiting
×
1376
        }
1377

1378
        spendHeight := uint32(details.SpendingHeight)
125✔
1379

125✔
1380
        // We also add to spendsByHeight to notify on chain reorgs.
125✔
1381
        reorgSafeHeight := spendHeight + n.reorgSafetyLimit
125✔
1382
        if reorgSafeHeight > n.currentHeight {
250✔
1383
                txSet, exists := n.spendsByHeight[spendHeight]
125✔
1384
                if !exists {
129✔
1385
                        txSet = make(map[SpendRequest]struct{})
4✔
1386
                        n.spendsByHeight[spendHeight] = txSet
4✔
1387
                }
4✔
1388
                txSet[ntfn.SpendRequest] = struct{}{}
125✔
1389
        }
1390

1391
        return nil
125✔
1392
}
1393

1394
// ConnectTip handles a new block extending the current chain. It will go
1395
// through every transaction and determine if it is relevant to any of its
1396
// clients. A transaction can be relevant in either of the following two ways:
1397
//
1398
//  1. One of the inputs in the transaction spends an outpoint/output script
1399
//     for which we currently have an active spend registration for.
1400
//
1401
//  2. The transaction has a txid or output script for which we currently have
1402
//     an active confirmation registration for.
1403
//
1404
// In the event that the transaction is relevant, a confirmation/spend
1405
// notification will be queued for dispatch to the relevant clients.
1406
// Confirmation notifications will only be dispatched for transactions/output
1407
// scripts that have met the required number of confirmations required by the
1408
// client.
1409
//
1410
// NOTE: In order to actually dispatch the relevant transaction notifications to
1411
// clients, NotifyHeight must be called with the same block height in order to
1412
// maintain correctness.
1413
func (n *TxNotifier) ConnectTip(block *btcutil.Block,
1414
        blockHeight uint32) error {
1,558✔
1415

1,558✔
1416
        select {
1,558✔
1417
        case <-n.quit:
×
1418
                return ErrTxNotifierExiting
×
1419
        default:
1,558✔
1420
        }
1421

1422
        n.Lock()
1,558✔
1423
        defer n.Unlock()
1,558✔
1424

1,558✔
1425
        if blockHeight != n.currentHeight+1 {
1,558✔
1426
                return fmt.Errorf("received blocks out of order: "+
×
1427
                        "current height=%d, new height=%d",
×
1428
                        n.currentHeight, blockHeight)
×
1429
        }
×
1430
        n.currentHeight++
1,558✔
1431
        n.reorgDepth = 0
1,558✔
1432

1,558✔
1433
        // First, we'll iterate over all the transactions found in this block to
1,558✔
1434
        // determine if it includes any relevant transactions to the TxNotifier.
1,558✔
1435
        if block != nil {
3,113✔
1436
                Log.Debugf("Filtering %d txns for %d spend requests at "+
1,555✔
1437
                        "height %d", len(block.Transactions()),
1,555✔
1438
                        len(n.spendNotifications), blockHeight)
1,555✔
1439

1,555✔
1440
                for _, tx := range block.Transactions() {
3,779✔
1441
                        n.filterTx(
2,224✔
1442
                                block, tx, blockHeight,
2,224✔
1443
                                n.handleConfDetailsAtTip,
2,224✔
1444
                                n.handleSpendDetailsAtTip,
2,224✔
1445
                        )
2,224✔
1446
                }
2,224✔
1447
        }
1448

1449
        // Now that we've determined which requests were confirmed and spent
1450
        // within the new block, we can update their entries in their respective
1451
        // caches, along with all of our unconfirmed and unspent requests.
1452
        n.updateHints(blockHeight)
1,558✔
1453

1,558✔
1454
        // Finally, we'll clear the entries from our set of notifications for
1,558✔
1455
        // requests that are no longer under the risk of being reorged out of
1,558✔
1456
        // the chain.
1,558✔
1457
        if blockHeight >= n.reorgSafetyLimit {
3,004✔
1458
                matureBlockHeight := blockHeight - n.reorgSafetyLimit
1,446✔
1459
                for confRequest := range n.confsByInitialHeight[matureBlockHeight] {
1,462✔
1460
                        confSet := n.confNotifications[confRequest]
16✔
1461
                        for _, ntfn := range confSet.ntfns {
33✔
1462
                                select {
17✔
1463
                                case ntfn.Event.Done <- struct{}{}:
17✔
1464
                                case <-n.quit:
×
1465
                                        return ErrTxNotifierExiting
×
1466
                                }
1467
                        }
1468

1469
                        delete(n.confNotifications, confRequest)
16✔
1470
                }
1471
                delete(n.confsByInitialHeight, matureBlockHeight)
1,446✔
1472

1,446✔
1473
                for spendRequest := range n.spendsByHeight[matureBlockHeight] {
1,448✔
1474
                        spendSet := n.spendNotifications[spendRequest]
2✔
1475
                        for _, ntfn := range spendSet.ntfns {
4✔
1476
                                select {
2✔
1477
                                case ntfn.Event.Done <- struct{}{}:
2✔
1478
                                case <-n.quit:
×
1479
                                        return ErrTxNotifierExiting
×
1480
                                }
1481
                        }
1482

1483
                        Log.Debugf("Deleting mature spend request %v at "+
2✔
1484
                                "height=%d", spendRequest, blockHeight)
2✔
1485
                        delete(n.spendNotifications, spendRequest)
2✔
1486
                }
1487
                delete(n.spendsByHeight, matureBlockHeight)
1,446✔
1488
        }
1489

1490
        return nil
1,558✔
1491
}
1492

1493
// filterTx determines whether the transaction spends or confirms any
1494
// outstanding pending requests. The onConf and onSpend callbacks can be used to
1495
// retrieve all the requests fulfilled by this transaction as they occur.
1496
func (n *TxNotifier) filterTx(block *btcutil.Block, tx *btcutil.Tx,
1497
        blockHeight uint32, onConf func(ConfRequest, *TxConfirmation),
1498
        onSpend func(SpendRequest, *SpendDetail)) {
2,265✔
1499

2,265✔
1500
        // In order to determine if this transaction is relevant to the
2,265✔
1501
        // notifier, we'll check its inputs for any outstanding spend
2,265✔
1502
        // requests.
2,265✔
1503
        txHash := tx.Hash()
2,265✔
1504
        if onSpend != nil {
4,530✔
1505
                // notifyDetails is a helper closure that will construct the
2,265✔
1506
                // spend details of a request and hand them off to the onSpend
2,265✔
1507
                // callback.
2,265✔
1508
                notifyDetails := func(spendRequest SpendRequest,
2,265✔
1509
                        prevOut wire.OutPoint, inputIdx uint32) {
2,350✔
1510

85✔
1511
                        Log.Debugf("Found spend of %v: spend_tx=%v, "+
85✔
1512
                                "block_height=%d", spendRequest, txHash,
85✔
1513
                                blockHeight)
85✔
1514

85✔
1515
                        onSpend(spendRequest, &SpendDetail{
85✔
1516
                                SpentOutPoint:     &prevOut,
85✔
1517
                                SpenderTxHash:     txHash,
85✔
1518
                                SpendingTx:        tx.MsgTx(),
85✔
1519
                                SpenderInputIndex: inputIdx,
85✔
1520
                                SpendingHeight:    int32(blockHeight),
85✔
1521
                        })
85✔
1522
                }
85✔
1523

1524
                for i, txIn := range tx.MsgTx().TxIn {
4,773✔
1525
                        // We'll re-derive the script of the output being spent
2,508✔
1526
                        // to determine if the inputs spends any registered
2,508✔
1527
                        // requests.
2,508✔
1528
                        prevOut := txIn.PreviousOutPoint
2,508✔
1529
                        pkScript, err := txscript.ComputePkScript(
2,508✔
1530
                                txIn.SignatureScript, txIn.Witness,
2,508✔
1531
                        )
2,508✔
1532
                        if err != nil {
2,508✔
1533
                                continue
×
1534
                        }
1535
                        spendRequest := SpendRequest{
2,508✔
1536
                                OutPoint: prevOut,
2,508✔
1537
                                PkScript: pkScript,
2,508✔
1538
                        }
2,508✔
1539

2,508✔
1540
                        // If we have any, we'll record their spend height so
2,508✔
1541
                        // that notifications get dispatched to the respective
2,508✔
1542
                        // clients.
2,508✔
1543
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,555✔
1544
                                notifyDetails(spendRequest, prevOut, uint32(i))
47✔
1545
                        }
47✔
1546

1547
                        // Now try with an empty taproot key pkScript, since we
1548
                        // cannot derive the spent pkScript directly from the
1549
                        // witness. But we have the outpoint, which should be
1550
                        // enough.
1551
                        spendRequest.PkScript = ZeroTaprootPkScript
2,508✔
1552
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,508✔
UNCOV
1553
                                notifyDetails(spendRequest, prevOut, uint32(i))
×
UNCOV
1554
                        }
×
1555

1556
                        // Restore the pkScript but try with a zero outpoint
1557
                        // instead (won't be possible for Taproot).
1558
                        spendRequest.PkScript = pkScript
2,508✔
1559
                        spendRequest.OutPoint = ZeroOutPoint
2,508✔
1560
                        if _, ok := n.spendNotifications[spendRequest]; ok {
2,546✔
1561
                                notifyDetails(spendRequest, prevOut, uint32(i))
38✔
1562
                        }
38✔
1563
                }
1564
        }
1565

1566
        // We'll also check its outputs to determine if there are any
1567
        // outstanding confirmation requests.
1568
        if onConf != nil {
4,489✔
1569
                // notifyDetails is a helper closure that will construct the
2,224✔
1570
                // confirmation details of a request and hand them off to the
2,224✔
1571
                // onConf callback.
2,224✔
1572
                notifyDetails := func(confRequest ConfRequest) {
2,363✔
1573
                        Log.Debugf("Found initial confirmation of %v: "+
139✔
1574
                                "height=%d, hash=%v", confRequest,
139✔
1575
                                blockHeight, block.Hash())
139✔
1576

139✔
1577
                        details := &TxConfirmation{
139✔
1578
                                Tx:          tx.MsgTx(),
139✔
1579
                                BlockHash:   block.Hash(),
139✔
1580
                                BlockHeight: blockHeight,
139✔
1581
                                TxIndex:     uint32(tx.Index()),
139✔
1582
                                Block:       block.MsgBlock(),
139✔
1583
                        }
139✔
1584

139✔
1585
                        onConf(confRequest, details)
139✔
1586
                }
139✔
1587

1588
                for _, txOut := range tx.MsgTx().TxOut {
5,454✔
1589
                        // We'll parse the script of the output to determine if
3,230✔
1590
                        // we have any registered requests for it or the
3,230✔
1591
                        // transaction itself.
3,230✔
1592
                        pkScript, err := txscript.ParsePkScript(txOut.PkScript)
3,230✔
1593
                        if err != nil {
3,384✔
1594
                                continue
154✔
1595
                        }
1596
                        confRequest := ConfRequest{
3,076✔
1597
                                TxID:     *txHash,
3,076✔
1598
                                PkScript: pkScript,
3,076✔
1599
                        }
3,076✔
1600

3,076✔
1601
                        // If we have any, we'll record their confirmed height
3,076✔
1602
                        // so that notifications get dispatched when they
3,076✔
1603
                        // reaches the clients' desired number of confirmations.
3,076✔
1604
                        if _, ok := n.confNotifications[confRequest]; ok {
3,150✔
1605
                                notifyDetails(confRequest)
74✔
1606
                        }
74✔
1607
                        confRequest.TxID = ZeroHash
3,076✔
1608
                        if _, ok := n.confNotifications[confRequest]; ok {
3,141✔
1609
                                notifyDetails(confRequest)
65✔
1610
                        }
65✔
1611
                }
1612
        }
1613
}
1614

1615
// handleConfDetailsAtTip tracks the confirmation height of the txid/output
1616
// script in order to properly dispatch a confirmation notification after
1617
// meeting each request's desired number of confirmations for all current and
1618
// future registered clients.
1619
func (n *TxNotifier) handleConfDetailsAtTip(confRequest ConfRequest,
1620
        details *TxConfirmation) {
139✔
1621

139✔
1622
        // TODO(wilmer): cancel pending historical rescans if any?
139✔
1623
        confSet := n.confNotifications[confRequest]
139✔
1624

139✔
1625
        // If we already have details for this request, we don't want to add it
139✔
1626
        // again since we have already dispatched notifications for it.
139✔
1627
        if confSet.details != nil {
142✔
1628
                Log.Warnf("Ignoring address reuse for %s at height %d.",
3✔
1629
                        confRequest, details.BlockHeight)
3✔
1630
                return
3✔
1631
        }
3✔
1632

1633
        confSet.rescanStatus = rescanComplete
136✔
1634
        confSet.details = details
136✔
1635

136✔
1636
        for _, ntfn := range confSet.ntfns {
313✔
1637
                // In the event that this notification was aware that the
177✔
1638
                // transaction/output script was reorged out of the chain, we'll
177✔
1639
                // consume the reorg notification if it hasn't been done yet
177✔
1640
                // already.
177✔
1641
                select {
177✔
UNCOV
1642
                case <-ntfn.Event.NegativeConf:
×
1643
                default:
177✔
1644
                }
1645

1646
                // We'll note this client's required number of confirmations so
1647
                // that we can notify them when expected.
1648
                confHeight := details.BlockHeight + ntfn.NumConfirmations - 1
177✔
1649
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
177✔
1650
                if !exists {
320✔
1651
                        ntfnSet = make(map[*ConfNtfn]struct{})
143✔
1652
                        n.ntfnsByConfirmHeight[confHeight] = ntfnSet
143✔
1653
                }
143✔
1654
                ntfnSet[ntfn] = struct{}{}
177✔
1655
        }
1656

1657
        // We'll also note the initial confirmation height in order to correctly
1658
        // handle dispatching notifications when the transaction/output script
1659
        // gets reorged out of the chain.
1660
        txSet, exists := n.confsByInitialHeight[details.BlockHeight]
136✔
1661
        if !exists {
227✔
1662
                txSet = make(map[ConfRequest]struct{})
91✔
1663
                n.confsByInitialHeight[details.BlockHeight] = txSet
91✔
1664
        }
91✔
1665
        txSet[confRequest] = struct{}{}
136✔
1666
}
1667

1668
// handleSpendDetailsAtTip tracks the spend height of the outpoint/output script
1669
// in order to properly dispatch a spend notification for all current and future
1670
// registered clients.
1671
func (n *TxNotifier) handleSpendDetailsAtTip(spendRequest SpendRequest,
1672
        details *SpendDetail) {
50✔
1673

50✔
1674
        // TODO(wilmer): cancel pending historical rescans if any?
50✔
1675
        spendSet := n.spendNotifications[spendRequest]
50✔
1676
        spendSet.rescanStatus = rescanComplete
50✔
1677
        spendSet.details = details
50✔
1678

50✔
1679
        for _, ntfn := range spendSet.ntfns {
145✔
1680
                // In the event that this notification was aware that the
95✔
1681
                // spending transaction of its outpoint/output script was
95✔
1682
                // reorged out of the chain, we'll consume the reorg
95✔
1683
                // notification if it hasn't been done yet already.
95✔
1684
                select {
95✔
1685
                case <-ntfn.Event.Reorg:
×
1686
                default:
95✔
1687
                }
1688
        }
1689

1690
        // We'll note the spending height of the request in order to correctly
1691
        // handle dispatching notifications when the spending transactions gets
1692
        // reorged out of the chain.
1693
        spendHeight := uint32(details.SpendingHeight)
50✔
1694
        opSet, exists := n.spendsByHeight[spendHeight]
50✔
1695
        if !exists {
100✔
1696
                opSet = make(map[SpendRequest]struct{})
50✔
1697
                n.spendsByHeight[spendHeight] = opSet
50✔
1698
        }
50✔
1699
        opSet[spendRequest] = struct{}{}
50✔
1700

50✔
1701
        Log.Debugf("Spend request %v spent at tip=%d", spendRequest,
50✔
1702
                spendHeight)
50✔
1703
}
1704

1705
// NotifyHeight dispatches confirmation and spend notifications to the clients
1706
// who registered for a notification which has been fulfilled at the passed
1707
// height.
1708
func (n *TxNotifier) NotifyHeight(height uint32) error {
1,458✔
1709
        n.Lock()
1,458✔
1710
        defer n.Unlock()
1,458✔
1711

1,458✔
1712
        // First, we'll dispatch an update to all of the notification clients
1,458✔
1713
        // for our watched requests with the number of confirmations left at
1,458✔
1714
        // this new height.
1,458✔
1715
        for _, confRequests := range n.confsByInitialHeight {
9,027✔
1716
                for confRequest := range confRequests {
20,929✔
1717
                        confSet := n.confNotifications[confRequest]
13,360✔
1718
                        for _, ntfn := range confSet.ntfns {
30,072✔
1719
                                txConfHeight := confSet.details.BlockHeight +
16,712✔
1720
                                        ntfn.NumConfirmations - 1
16,712✔
1721
                                numConfsLeft := txConfHeight - height
16,712✔
1722

16,712✔
1723
                                // Since we don't clear notifications until
16,712✔
1724
                                // transactions/output scripts are no longer
16,712✔
1725
                                // under the risk of being reorganized out of
16,712✔
1726
                                // the chain, we'll skip sending updates for
16,712✔
1727
                                // those that have already been confirmed.
16,712✔
1728
                                if int32(numConfsLeft) < 0 {
32,739✔
1729
                                        continue
16,027✔
1730
                                }
1731

1732
                                select {
685✔
1733
                                case ntfn.Event.Updates <- numConfsLeft:
685✔
1734
                                case <-n.quit:
×
1735
                                        return ErrTxNotifierExiting
×
1736
                                }
1737
                        }
1738
                }
1739
        }
1740

1741
        // Then, we'll dispatch notifications for all the requests that have
1742
        // become confirmed at this new block height.
1743
        for ntfn := range n.ntfnsByConfirmHeight[height] {
1,632✔
1744
                confSet := n.confNotifications[ntfn.ConfRequest]
174✔
1745

174✔
1746
                Log.Debugf("Dispatching %v confirmation notification for %v",
174✔
1747
                        ntfn.NumConfirmations, ntfn.ConfRequest)
174✔
1748

174✔
1749
                // The default notification we assigned above includes the
174✔
1750
                // block along with the rest of the details. However not all
174✔
1751
                // clients want the block, so we make a copy here w/o the block
174✔
1752
                // if needed so we can give clients only what they ask for.
174✔
1753
                confDetails := *confSet.details
174✔
1754
                if !ntfn.includeBlock {
308✔
1755
                        confDetails.Block = nil
134✔
1756
                }
134✔
1757

1758
                select {
174✔
1759
                case ntfn.Event.Confirmed <- &confDetails:
174✔
1760
                        ntfn.dispatched = true
174✔
1761
                case <-n.quit:
×
1762
                        return ErrTxNotifierExiting
×
1763
                }
1764
        }
1765
        delete(n.ntfnsByConfirmHeight, height)
1,458✔
1766

1,458✔
1767
        // Finally, we'll dispatch spend notifications for all the requests that
1,458✔
1768
        // were spent at this new block height.
1,458✔
1769
        for spendRequest := range n.spendsByHeight[height] {
1,508✔
1770
                spendSet := n.spendNotifications[spendRequest]
50✔
1771
                for _, ntfn := range spendSet.ntfns {
146✔
1772
                        err := n.dispatchSpendDetails(ntfn, spendSet.details)
96✔
1773
                        if err != nil {
96✔
1774
                                return err
×
1775
                        }
×
1776
                }
1777
        }
1778

1779
        return nil
1,458✔
1780
}
1781

1782
// DisconnectTip handles the tip of the current chain being disconnected during
1783
// a chain reorganization. If any watched requests were included in this block,
1784
// internal structures are updated to ensure confirmation/spend notifications
1785
// are consumed (if not already), and reorg notifications are dispatched
1786
// instead. Confirmation/spend notifications will be dispatched again upon block
1787
// inclusion.
1788
func (n *TxNotifier) DisconnectTip(blockHeight uint32) error {
92✔
1789
        select {
92✔
1790
        case <-n.quit:
×
1791
                return ErrTxNotifierExiting
×
1792
        default:
92✔
1793
        }
1794

1795
        n.Lock()
92✔
1796
        defer n.Unlock()
92✔
1797

92✔
1798
        if blockHeight != n.currentHeight {
92✔
1799
                return fmt.Errorf("received blocks out of order: "+
×
1800
                        "current height=%d, disconnected height=%d",
×
1801
                        n.currentHeight, blockHeight)
×
1802
        }
×
1803
        n.currentHeight--
92✔
1804
        n.reorgDepth++
92✔
1805

92✔
1806
        // With the block disconnected, we'll update the confirm and spend hints
92✔
1807
        // for our notification requests to reflect the new height, except for
92✔
1808
        // those that have confirmed/spent at previous heights.
92✔
1809
        n.updateHints(blockHeight)
92✔
1810

92✔
1811
        // We'll go through all of our watched confirmation requests and attempt
92✔
1812
        // to drain their notification channels to ensure sending notifications
92✔
1813
        // to the clients is always non-blocking.
92✔
1814
        for initialHeight, txHashes := range n.confsByInitialHeight {
381✔
1815
                for txHash := range txHashes {
771✔
1816
                        // If the transaction/output script has been reorged out
482✔
1817
                        // of the chain, we'll make sure to remove the cached
482✔
1818
                        // confirmation details to prevent notifying clients
482✔
1819
                        // with old information.
482✔
1820
                        confSet := n.confNotifications[txHash]
482✔
1821
                        if initialHeight == blockHeight {
494✔
1822
                                confSet.details = nil
12✔
1823
                        }
12✔
1824

1825
                        for _, ntfn := range confSet.ntfns {
1,092✔
1826
                                // First, we'll attempt to drain an update
610✔
1827
                                // from each notification to ensure sends to the
610✔
1828
                                // Updates channel are always non-blocking.
610✔
1829
                                select {
610✔
1830
                                case <-ntfn.Event.Updates:
323✔
1831
                                case <-n.quit:
×
1832
                                        return ErrTxNotifierExiting
×
1833
                                default:
287✔
1834
                                }
1835

1836
                                // Then, we'll check if the current
1837
                                // transaction/output script was included in the
1838
                                // block currently being disconnected. If it
1839
                                // was, we'll need to dispatch a reorg
1840
                                // notification to the client.
1841
                                if initialHeight == blockHeight {
622✔
1842
                                        err := n.dispatchConfReorg(
12✔
1843
                                                ntfn, blockHeight,
12✔
1844
                                        )
12✔
1845
                                        if err != nil {
12✔
1846
                                                return err
×
1847
                                        }
×
1848
                                }
1849
                        }
1850
                }
1851
        }
1852

1853
        // We'll also go through our watched spend requests and attempt to drain
1854
        // their dispatched notifications to ensure dispatching notifications to
1855
        // clients later on is always non-blocking. We're only interested in
1856
        // requests whose spending transaction was included at the height being
1857
        // disconnected.
1858
        for op := range n.spendsByHeight[blockHeight] {
104✔
1859
                // Since the spending transaction is being reorged out of the
12✔
1860
                // chain, we'll need to clear out the spending details of the
12✔
1861
                // request.
12✔
1862
                spendSet := n.spendNotifications[op]
12✔
1863
                spendSet.details = nil
12✔
1864

12✔
1865
                // For all requests which have had a spend notification
12✔
1866
                // dispatched, we'll attempt to drain it and send a reorg
12✔
1867
                // notification instead.
12✔
1868
                for _, ntfn := range spendSet.ntfns {
24✔
1869
                        if err := n.dispatchSpendReorg(ntfn); err != nil {
12✔
1870
                                return err
×
1871
                        }
×
1872
                }
1873
        }
1874

1875
        // Finally, we can remove the requests that were confirmed and/or spent
1876
        // at the height being disconnected. We'll still continue to track them
1877
        // until they have been confirmed/spent and are no longer under the risk
1878
        // of being reorged out of the chain again.
1879
        delete(n.confsByInitialHeight, blockHeight)
92✔
1880
        delete(n.spendsByHeight, blockHeight)
92✔
1881

92✔
1882
        return nil
92✔
1883
}
1884

1885
// updateHints attempts to update the confirm and spend hints for all relevant
1886
// requests respectively. The height parameter is used to determine which
1887
// requests we should update based on whether a new block is being
1888
// connected/disconnected.
1889
//
1890
// NOTE: This must be called with the TxNotifier's lock held and after its
1891
// height has already been reflected by a block being connected/disconnected.
1892
func (n *TxNotifier) updateHints(height uint32) {
1,650✔
1893
        // TODO(wilmer): update under one database transaction.
1,650✔
1894
        //
1,650✔
1895
        // To update the height hint for all the required confirmation requests
1,650✔
1896
        // under one database transaction, we'll gather the set of unconfirmed
1,650✔
1897
        // requests along with the ones that confirmed at the height being
1,650✔
1898
        // connected/disconnected.
1,650✔
1899
        confRequests := n.unconfirmedRequests()
1,650✔
1900
        for confRequest := range n.confsByInitialHeight[height] {
1,798✔
1901
                confRequests = append(confRequests, confRequest)
148✔
1902
        }
148✔
1903
        err := n.confirmHintCache.CommitConfirmHint(
1,650✔
1904
                n.currentHeight, confRequests...,
1,650✔
1905
        )
1,650✔
1906
        if err != nil {
1,650✔
1907
                // The error is not fatal as this is an optimistic optimization,
×
1908
                // so we'll avoid returning an error.
×
1909
                Log.Debugf("Unable to update confirm hints to %d for "+
×
1910
                        "%v: %v", n.currentHeight, confRequests, err)
×
1911
        }
×
1912

1913
        // Similarly, to update the height hint for all the required spend
1914
        // requests under one database transaction, we'll gather the set of
1915
        // unspent requests along with the ones that were spent at the height
1916
        // being connected/disconnected.
1917
        spendRequests := n.unspentRequests()
1,650✔
1918
        for spendRequest := range n.spendsByHeight[height] {
1,712✔
1919
                spendRequests = append(spendRequests, spendRequest)
62✔
1920
        }
62✔
1921
        err = n.spendHintCache.CommitSpendHint(n.currentHeight, spendRequests...)
1,650✔
1922
        if err != nil {
1,650✔
1923
                // The error is not fatal as this is an optimistic optimization,
×
1924
                // so we'll avoid returning an error.
×
1925
                Log.Debugf("Unable to update spend hints to %d for "+
×
1926
                        "%v: %v", n.currentHeight, spendRequests, err)
×
1927
        }
×
1928
}
1929

1930
// unconfirmedRequests returns the set of confirmation requests that are
1931
// still seen as unconfirmed by the TxNotifier.
1932
//
1933
// NOTE: This method must be called with the TxNotifier's lock held.
1934
func (n *TxNotifier) unconfirmedRequests() []ConfRequest {
1,650✔
1935
        var unconfirmed []ConfRequest
1,650✔
1936
        for confRequest, confNtfnSet := range n.confNotifications {
16,299✔
1937
                // If the notification is already aware of its confirmation
14,649✔
1938
                // details, or it's in the process of learning them, we'll skip
14,649✔
1939
                // it as we can't yet determine if it's confirmed or not.
14,649✔
1940
                if confNtfnSet.rescanStatus != rescanComplete ||
14,649✔
1941
                        confNtfnSet.details != nil {
28,608✔
1942
                        continue
13,959✔
1943
                }
1944

1945
                unconfirmed = append(unconfirmed, confRequest)
690✔
1946
        }
1947

1948
        return unconfirmed
1,650✔
1949
}
1950

1951
// unspentRequests returns the set of spend requests that are still seen as
1952
// unspent by the TxNotifier.
1953
//
1954
// NOTE: This method must be called with the TxNotifier's lock held.
1955
func (n *TxNotifier) unspentRequests() []SpendRequest {
1,650✔
1956
        var unspent []SpendRequest
1,650✔
1957
        for spendRequest, spendNtfnSet := range n.spendNotifications {
3,234✔
1958
                // If the notification is already aware of its spend details, or
1,584✔
1959
                // it's in the process of learning them, we'll skip it as we
1,584✔
1960
                // can't yet determine if it's unspent or not.
1,584✔
1961
                if spendNtfnSet.rescanStatus != rescanComplete ||
1,584✔
1962
                        spendNtfnSet.details != nil {
3,126✔
1963
                        continue
1,542✔
1964
                }
1965

1966
                unspent = append(unspent, spendRequest)
42✔
1967
        }
1968

1969
        return unspent
1,650✔
1970
}
1971

1972
// dispatchConfReorg dispatches a reorg notification to the client if the
1973
// confirmation notification was already delivered.
1974
//
1975
// NOTE: This must be called with the TxNotifier's lock held.
1976
func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
1977
        heightDisconnected uint32) error {
12✔
1978

12✔
1979
        // If the request's confirmation notification has yet to be dispatched,
12✔
1980
        // we'll need to clear its entry within the ntfnsByConfirmHeight index
12✔
1981
        // to prevent from notifying the client once the notifier reaches the
12✔
1982
        // confirmation height.
12✔
1983
        if !ntfn.dispatched {
22✔
1984
                confHeight := heightDisconnected + ntfn.NumConfirmations - 1
10✔
1985
                ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
10✔
1986
                if exists {
20✔
1987
                        delete(ntfnSet, ntfn)
10✔
1988
                }
10✔
1989
                return nil
10✔
1990
        }
1991

1992
        // Otherwise, the entry within the ntfnsByConfirmHeight has already been
1993
        // deleted, so we'll attempt to drain the confirmation notification to
1994
        // ensure sends to the Confirmed channel are always non-blocking.
1995
        select {
2✔
1996
        case <-ntfn.Event.Confirmed:
×
1997
        case <-n.quit:
×
1998
                return ErrTxNotifierExiting
×
1999
        default:
2✔
2000
        }
2001

2002
        ntfn.dispatched = false
2✔
2003

2✔
2004
        // Send a negative confirmation notification to the client indicating
2✔
2005
        // how many blocks have been disconnected successively.
2✔
2006
        select {
2✔
2007
        case ntfn.Event.NegativeConf <- int32(n.reorgDepth):
2✔
2008
        case <-n.quit:
×
2009
                return ErrTxNotifierExiting
×
2010
        }
2011

2012
        return nil
2✔
2013
}
2014

2015
// dispatchSpendReorg dispatches a reorg notification to the client if a spend
2016
// notiification was already delivered.
2017
//
2018
// NOTE: This must be called with the TxNotifier's lock held.
2019
func (n *TxNotifier) dispatchSpendReorg(ntfn *SpendNtfn) error {
12✔
2020
        if !ntfn.dispatched {
12✔
2021
                return nil
×
2022
        }
×
2023

2024
        // Attempt to drain the spend notification to ensure sends to the Spend
2025
        // channel are always non-blocking.
2026
        select {
12✔
2027
        case <-ntfn.Event.Spend:
1✔
2028
        default:
11✔
2029
        }
2030

2031
        // Send a reorg notification to the client in order for them to
2032
        // correctly handle reorgs.
2033
        select {
12✔
2034
        case ntfn.Event.Reorg <- struct{}{}:
12✔
2035
        case <-n.quit:
×
2036
                return ErrTxNotifierExiting
×
2037
        }
2038

2039
        ntfn.dispatched = false
12✔
2040

12✔
2041
        return nil
12✔
2042
}
2043

2044
// TearDown is to be called when the owner of the TxNotifier is exiting. This
2045
// closes the event channels of all registered notifications that have not been
2046
// dispatched yet.
2047
func (n *TxNotifier) TearDown() {
22✔
2048
        close(n.quit)
22✔
2049

22✔
2050
        n.Lock()
22✔
2051
        defer n.Unlock()
22✔
2052

22✔
2053
        for _, confSet := range n.confNotifications {
147✔
2054
                for confID, ntfn := range confSet.ntfns {
306✔
2055
                        close(ntfn.Event.Confirmed)
181✔
2056
                        close(ntfn.Event.Updates)
181✔
2057
                        close(ntfn.Event.NegativeConf)
181✔
2058
                        close(ntfn.Event.Done)
181✔
2059
                        delete(confSet.ntfns, confID)
181✔
2060
                }
181✔
2061
        }
2062

2063
        for _, spendSet := range n.spendNotifications {
55✔
2064
                for spendID, ntfn := range spendSet.ntfns {
130✔
2065
                        close(ntfn.Event.Spend)
97✔
2066
                        close(ntfn.Event.Reorg)
97✔
2067
                        close(ntfn.Event.Done)
97✔
2068
                        delete(spendSet.ntfns, spendID)
97✔
2069
                }
97✔
2070
        }
2071
}
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