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

lightningnetwork / lnd / 12281843618

11 Dec 2024 05:38PM UTC coverage: 49.477% (-0.06%) from 49.54%
12281843618

Pull #9242

github

aakselrod
docs: update release-notes for 0.19.0
Pull Request #9242: Reapply #8644

6 of 27 new or added lines in 2 files covered. (22.22%)

170 existing lines in 20 files now uncovered.

100257 of 202632 relevant lines covered (49.48%)

1.54 hits per line

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

63.61
/chainntnfs/interface.go
1
package chainntnfs
2

3
import (
4
        "bytes"
5
        "encoding/hex"
6
        "errors"
7
        "fmt"
8
        "strings"
9
        "sync"
10

11
        "github.com/btcsuite/btcd/blockchain"
12
        "github.com/btcsuite/btcd/btcjson"
13
        "github.com/btcsuite/btcd/btcutil"
14
        "github.com/btcsuite/btcd/chaincfg/chainhash"
15
        "github.com/btcsuite/btcd/wire"
16
        "github.com/lightningnetwork/lnd/fn/v2"
17
)
18

19
var (
20
        // ErrChainNotifierShuttingDown is used when we are trying to
21
        // measure a spend notification when notifier is already stopped.
22
        ErrChainNotifierShuttingDown = errors.New("chain notifier shutting down")
23
)
24

25
// TxConfStatus denotes the status of a transaction's lookup.
26
type TxConfStatus uint8
27

28
const (
29
        // TxFoundMempool denotes that the transaction was found within the
30
        // backend node's mempool.
31
        TxFoundMempool TxConfStatus = iota
32

33
        // TxFoundIndex denotes that the transaction was found within the
34
        // backend node's txindex.
35
        TxFoundIndex
36

37
        // TxNotFoundIndex denotes that the transaction was not found within the
38
        // backend node's txindex.
39
        TxNotFoundIndex
40

41
        // TxFoundManually denotes that the transaction was found within the
42
        // chain by scanning for it manually.
43
        TxFoundManually
44

45
        // TxNotFoundManually denotes that the transaction was not found within
46
        // the chain by scanning for it manually.
47
        TxNotFoundManually
48
)
49

50
// String returns the string representation of the TxConfStatus.
51
func (t TxConfStatus) String() string {
×
52
        switch t {
×
53
        case TxFoundMempool:
×
54
                return "TxFoundMempool"
×
55

56
        case TxFoundIndex:
×
57
                return "TxFoundIndex"
×
58

59
        case TxNotFoundIndex:
×
60
                return "TxNotFoundIndex"
×
61

62
        case TxFoundManually:
×
63
                return "TxFoundManually"
×
64

65
        case TxNotFoundManually:
×
66
                return "TxNotFoundManually"
×
67

68
        default:
×
69
                return "unknown"
×
70
        }
71
}
72

73
// notifierOptions is a set of functional options that allow callers to further
74
// modify the type of chain event notifications they receive.
75
type notifierOptions struct {
76
        // includeBlock if true, then the dispatched confirmation notification
77
        // will include the block that mined the transaction.
78
        includeBlock bool
79
}
80

81
// defaultNotifierOptions returns the set of default options for the notifier.
82
func defaultNotifierOptions() *notifierOptions {
3✔
83
        return &notifierOptions{}
3✔
84
}
3✔
85

86
// NotifierOption is a functional option that allows a caller to modify the
87
// events received from the notifier.
88
type NotifierOption func(*notifierOptions)
89

90
// WithIncludeBlock is an optional argument that allows the calelr to specify
91
// that the block that mined a transaction should be included in the response.
92
func WithIncludeBlock() NotifierOption {
3✔
93
        return func(o *notifierOptions) {
6✔
94
                o.includeBlock = true
3✔
95
        }
3✔
96
}
97

98
// ChainNotifier represents a trusted source to receive notifications concerning
99
// targeted events on the Bitcoin blockchain. The interface specification is
100
// intentionally general in order to support a wide array of chain notification
101
// implementations such as: btcd's websockets notifications, Bitcoin Core's
102
// ZeroMQ notifications, various Bitcoin API services, Electrum servers, etc.
103
//
104
// Concrete implementations of ChainNotifier should be able to support multiple
105
// concurrent client requests, as well as multiple concurrent notification events.
106
type ChainNotifier interface {
107
        // RegisterConfirmationsNtfn registers an intent to be notified once
108
        // txid reaches numConfs confirmations. We also pass in the pkScript as
109
        // the default light client instead needs to match on scripts created in
110
        // the block. If a nil txid is passed in, then not only should we match
111
        // on the script, but we should also dispatch once the transaction
112
        // containing the script reaches numConfs confirmations. This can be
113
        // useful in instances where we only know the script in advance, but not
114
        // the transaction containing it.
115
        //
116
        // The returned ConfirmationEvent should properly notify the client once
117
        // the specified number of confirmations has been reached for the txid,
118
        // as well as if the original tx gets re-org'd out of the mainchain. The
119
        // heightHint parameter is provided as a convenience to light clients.
120
        // It heightHint denotes the earliest height in the blockchain in which
121
        // the target txid _could_ have been included in the chain. This can be
122
        // used to bound the search space when checking to see if a notification
123
        // can immediately be dispatched due to historical data.
124
        //
125
        // NOTE: Dispatching notifications to multiple clients subscribed to
126
        // the same (txid, numConfs) tuple MUST be supported.
127
        RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte,
128
                numConfs, heightHint uint32,
129
                opts ...NotifierOption) (*ConfirmationEvent, error)
130

131
        // RegisterSpendNtfn registers an intent to be notified once the target
132
        // outpoint is successfully spent within a transaction. The script that
133
        // the outpoint creates must also be specified. This allows this
134
        // interface to be implemented by BIP 158-like filtering. If a nil
135
        // outpoint is passed in, then not only should we match on the script,
136
        // but we should also dispatch once a transaction spends the output
137
        // containing said script. This can be useful in instances where we only
138
        // know the script in advance, but not the outpoint itself.
139
        //
140
        // The returned SpendEvent will receive a send on the 'Spend'
141
        // transaction once a transaction spending the input is detected on the
142
        // blockchain. The heightHint parameter is provided as a convenience to
143
        // light clients. It denotes the earliest height in the blockchain in
144
        // which the target output could have been spent.
145
        //
146
        // NOTE: The notification should only be triggered when the spending
147
        // transaction receives a single confirmation.
148
        //
149
        // NOTE: Dispatching notifications to multiple clients subscribed to a
150
        // spend of the same outpoint MUST be supported.
151
        RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte,
152
                heightHint uint32) (*SpendEvent, error)
153

154
        // RegisterBlockEpochNtfn registers an intent to be notified of each
155
        // new block connected to the tip of the main chain. The returned
156
        // BlockEpochEvent struct contains a channel which will be sent upon
157
        // for each new block discovered.
158
        //
159
        // Clients have the option of passing in their best known block.
160
        // If they specify a block, the ChainNotifier checks whether the client
161
        // is behind on blocks. If they are, the ChainNotifier sends a backlog
162
        // of block notifications for the missed blocks. If they do not provide
163
        // one, then a notification will be dispatched immediately for the
164
        // current tip of the chain upon a successful registration.
165
        RegisterBlockEpochNtfn(*BlockEpoch) (*BlockEpochEvent, error)
166

167
        // Start the ChainNotifier. Once started, the implementation should be
168
        // ready, and able to receive notification registrations from clients.
169
        Start() error
170

171
        // Started returns true if this instance has been started, and false otherwise.
172
        Started() bool
173

174
        // Stops the concrete ChainNotifier. Once stopped, the ChainNotifier
175
        // should disallow any future requests from potential clients.
176
        // Additionally, all pending client notifications will be canceled
177
        // by closing the related channels on the *Event's.
178
        Stop() error
179
}
180

181
// TxConfirmation carries some additional block-level details of the exact
182
// block that specified transactions was confirmed within.
183
type TxConfirmation struct {
184
        // BlockHash is the hash of the block that confirmed the original
185
        // transition.
186
        BlockHash *chainhash.Hash
187

188
        // BlockHeight is the height of the block in which the transaction was
189
        // confirmed within.
190
        BlockHeight uint32
191

192
        // TxIndex is the index within the block of the ultimate confirmed
193
        // transaction.
194
        TxIndex uint32
195

196
        // Tx is the transaction for which the notification was requested for.
197
        Tx *wire.MsgTx
198

199
        // Block is the block that contains the transaction referenced above.
200
        //
201
        // NOTE: This is only specified if the confirmation request opts to
202
        // have the response include the block itself.
203
        Block *wire.MsgBlock
204
}
205

206
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
207
// callers can be notified of: the instance the target txid reaches the targeted
208
// number of confirmations, how many confirmations are left for the target txid
209
// to be fully confirmed at every new block height, and also in the event that
210
// the original txid becomes disconnected from the blockchain as a result of a
211
// re-org.
212
//
213
// Once the txid reaches the specified number of confirmations, the 'Confirmed'
214
// channel will be sent upon fulfilling the notification.
215
//
216
// If the event that the original transaction becomes re-org'd out of the main
217
// chain, the 'NegativeConf' will be sent upon with a value representing the
218
// depth of the re-org.
219
//
220
// NOTE: If the caller wishes to cancel their registered spend notification,
221
// the Cancel closure MUST be called.
222
type ConfirmationEvent struct {
223
        // Confirmed is a channel that will be sent upon once the transaction
224
        // has been fully confirmed. The struct sent will contain all the
225
        // details of the channel's confirmation.
226
        //
227
        // NOTE: This channel must be buffered.
228
        Confirmed chan *TxConfirmation
229

230
        // Updates is a channel that will sent upon, at every incremental
231
        // confirmation, how many confirmations are left to declare the
232
        // transaction as fully confirmed.
233
        //
234
        // NOTE: This channel must be buffered with the number of required
235
        // confirmations.
236
        Updates chan uint32
237

238
        // NegativeConf is a channel that will be sent upon if the transaction
239
        // confirms, but is later reorged out of the chain. The integer sent
240
        // through the channel represents the reorg depth.
241
        //
242
        // NOTE: This channel must be buffered.
243
        NegativeConf chan int32
244

245
        // Done is a channel that gets sent upon once the confirmation request
246
        // is no longer under the risk of being reorged out of the chain.
247
        //
248
        // NOTE: This channel must be buffered.
249
        Done chan struct{}
250

251
        // Cancel is a closure that should be executed by the caller in the case
252
        // that they wish to prematurely abandon their registered confirmation
253
        // notification.
254
        Cancel func()
255
}
256

257
// NewConfirmationEvent constructs a new ConfirmationEvent with newly opened
258
// channels.
259
func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
3✔
260
        return &ConfirmationEvent{
3✔
261
                // We cannot rely on the subscriber to immediately read from
3✔
262
                // the channel so we need to create a larger buffer to avoid
3✔
263
                // blocking the notifier.
3✔
264
                Confirmed:    make(chan *TxConfirmation, 1),
3✔
265
                Updates:      make(chan uint32, numConfs),
3✔
266
                NegativeConf: make(chan int32, 1),
3✔
267
                Done:         make(chan struct{}, 1),
3✔
268
                Cancel:       cancel,
3✔
269
        }
3✔
270
}
3✔
271

272
// SpendDetail contains details pertaining to a spent output. This struct itself
273
// is the spentness notification. It includes the original outpoint which triggered
274
// the notification, the hash of the transaction spending the output, the
275
// spending transaction itself, and finally the input index which spent the
276
// target output.
277
type SpendDetail struct {
278
        SpentOutPoint     *wire.OutPoint
279
        SpenderTxHash     *chainhash.Hash
280
        SpendingTx        *wire.MsgTx
281
        SpenderInputIndex uint32
282
        SpendingHeight    int32
283
}
284

285
// HasSpenderWitness returns true if the spending transaction has non-empty
286
// witness.
287
func (s *SpendDetail) HasSpenderWitness() bool {
3✔
288
        tx := s.SpendingTx
3✔
289

3✔
290
        // If there are no inputs, then there is no witness.
3✔
291
        if len(tx.TxIn) == 0 {
3✔
292
                return false
×
293
        }
×
294

295
        // If the spender input index is larger than the number of inputs, then
296
        // we don't have a witness and this is an error case so we log it.
297
        if uint32(len(tx.TxIn)) <= s.SpenderInputIndex {
3✔
298
                Log.Errorf("SpenderInputIndex %d is out of range for tx %v",
×
299
                        s.SpenderInputIndex, tx.TxHash())
×
300

×
301
                return false
×
302
        }
×
303

304
        // If the witness is empty, then there is no witness.
305
        if len(tx.TxIn[s.SpenderInputIndex].Witness) == 0 {
3✔
306
                return false
×
307
        }
×
308

309
        // If the witness is non-empty, then we have a witness.
310
        return true
3✔
311
}
312

313
// String returns a string representation of SpendDetail.
314
func (s *SpendDetail) String() string {
3✔
315
        return fmt.Sprintf("%v[%d] spending %v at height=%v", s.SpenderTxHash,
3✔
316
                s.SpenderInputIndex, s.SpentOutPoint, s.SpendingHeight)
3✔
317
}
3✔
318

319
// SpendEvent encapsulates a spentness notification. Its only field 'Spend' will
320
// be sent upon once the target output passed into RegisterSpendNtfn has been
321
// spent on the blockchain.
322
//
323
// NOTE: If the caller wishes to cancel their registered spend notification,
324
// the Cancel closure MUST be called.
325
type SpendEvent struct {
326
        // Spend is a receive only channel which will be sent upon once the
327
        // target outpoint has been spent.
328
        //
329
        // NOTE: This channel must be buffered.
330
        Spend chan *SpendDetail
331

332
        // Reorg is a channel that will be sent upon once we detect the spending
333
        // transaction of the outpoint in question has been reorged out of the
334
        // chain.
335
        //
336
        // NOTE: This channel must be buffered.
337
        Reorg chan struct{}
338

339
        // Done is a channel that gets sent upon once the confirmation request
340
        // is no longer under the risk of being reorged out of the chain.
341
        //
342
        // NOTE: This channel must be buffered.
343
        Done chan struct{}
344

345
        // Cancel is a closure that should be executed by the caller in the case
346
        // that they wish to prematurely abandon their registered spend
347
        // notification.
348
        Cancel func()
349
}
350

351
// NewSpendEvent constructs a new SpendEvent with newly opened channels.
352
func NewSpendEvent(cancel func()) *SpendEvent {
3✔
353
        return &SpendEvent{
3✔
354
                Spend:  make(chan *SpendDetail, 1),
3✔
355
                Reorg:  make(chan struct{}, 1),
3✔
356
                Done:   make(chan struct{}, 1),
3✔
357
                Cancel: cancel,
3✔
358
        }
3✔
359
}
3✔
360

361
// BlockEpoch represents metadata concerning each new block connected to the
362
// main chain.
363
type BlockEpoch struct {
364
        // Hash is the block hash of the latest block to be added to the tip of
365
        // the main chain.
366
        Hash *chainhash.Hash
367

368
        // Height is the height of the latest block to be added to the tip of
369
        // the main chain.
370
        Height int32
371

372
        // BlockHeader is the block header of this new height.
373
        BlockHeader *wire.BlockHeader
374
}
375

376
// BlockEpochEvent encapsulates an on-going stream of block epoch
377
// notifications. Its only field 'Epochs' will be sent upon for each new block
378
// connected to the main-chain.
379
//
380
// NOTE: If the caller wishes to cancel their registered block epoch
381
// notification, the Cancel closure MUST be called.
382
type BlockEpochEvent struct {
383
        // Epochs is a receive only channel that will be sent upon each time a
384
        // new block is connected to the end of the main chain.
385
        //
386
        // NOTE: This channel must be buffered.
387
        Epochs <-chan *BlockEpoch
388

389
        // Cancel is a closure that should be executed by the caller in the case
390
        // that they wish to abandon their registered block epochs notification.
391
        Cancel func()
392
}
393

394
// NotifierDriver represents a "driver" for a particular interface. A driver is
395
// identified by a globally unique string identifier along with a 'New()'
396
// method which is responsible for initializing a particular ChainNotifier
397
// concrete implementation.
398
type NotifierDriver struct {
399
        // NotifierType is a string which uniquely identifies the ChainNotifier
400
        // that this driver, drives.
401
        NotifierType string
402

403
        // New creates a new instance of a concrete ChainNotifier
404
        // implementation given a variadic set up arguments. The function takes
405
        // a variadic number of interface parameters in order to provide
406
        // initialization flexibility, thereby accommodating several potential
407
        // ChainNotifier implementations.
408
        New func(args ...interface{}) (ChainNotifier, error)
409
}
410

411
var (
412
        notifiers   = make(map[string]*NotifierDriver)
413
        registerMtx sync.Mutex
414
)
415

416
// RegisteredNotifiers returns a slice of all currently registered notifiers.
417
//
418
// NOTE: This function is safe for concurrent access.
419
func RegisteredNotifiers() []*NotifierDriver {
×
420
        registerMtx.Lock()
×
421
        defer registerMtx.Unlock()
×
422

×
423
        drivers := make([]*NotifierDriver, 0, len(notifiers))
×
424
        for _, driver := range notifiers {
×
425
                drivers = append(drivers, driver)
×
426
        }
×
427

428
        return drivers
×
429
}
430

431
// RegisterNotifier registers a NotifierDriver which is capable of driving a
432
// concrete ChainNotifier interface. In the case that this driver has already
433
// been registered, an error is returned.
434
//
435
// NOTE: This function is safe for concurrent access.
436
func RegisterNotifier(driver *NotifierDriver) error {
3✔
437
        registerMtx.Lock()
3✔
438
        defer registerMtx.Unlock()
3✔
439

3✔
440
        if _, ok := notifiers[driver.NotifierType]; ok {
3✔
441
                return fmt.Errorf("notifier already registered")
×
442
        }
×
443

444
        notifiers[driver.NotifierType] = driver
3✔
445

3✔
446
        return nil
3✔
447
}
448

449
// SupportedNotifiers returns a slice of strings that represent the database
450
// drivers that have been registered and are therefore supported.
451
//
452
// NOTE: This function is safe for concurrent access.
453
func SupportedNotifiers() []string {
×
454
        registerMtx.Lock()
×
455
        defer registerMtx.Unlock()
×
456

×
457
        supportedNotifiers := make([]string, 0, len(notifiers))
×
458
        for driverName := range notifiers {
×
459
                supportedNotifiers = append(supportedNotifiers, driverName)
×
460
        }
×
461

462
        return supportedNotifiers
×
463
}
464

465
// ChainConn enables notifiers to pass in their chain backend to interface
466
// functions that require it.
467
type ChainConn interface {
468
        // GetBlockHeader returns the block header for a hash.
469
        GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error)
470

471
        // GetBlockHeaderVerbose returns the verbose block header for a hash.
472
        GetBlockHeaderVerbose(blockHash *chainhash.Hash) (
473
                *btcjson.GetBlockHeaderVerboseResult, error)
474

475
        // GetBlockHash returns the hash from a block height.
476
        GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
477
}
478

479
// GetCommonBlockAncestorHeight takes in:
480
// (1) the hash of a block that has been reorged out of the main chain
481
// (2) the hash of the block of the same height from the main chain
482
// It returns the height of the nearest common ancestor between the two hashes,
483
// or an error
484
func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash,
485
        chainHash chainhash.Hash) (int32, error) {
2✔
486

2✔
487
        for reorgHash != chainHash {
3✔
488
                reorgHeader, err := chainConn.GetBlockHeader(&reorgHash)
1✔
489
                if err != nil {
1✔
490
                        return 0, fmt.Errorf("unable to get header for "+
×
491
                                "hash=%v: %w", reorgHash, err)
×
492
                }
×
493
                chainHeader, err := chainConn.GetBlockHeader(&chainHash)
1✔
494
                if err != nil {
1✔
495
                        return 0, fmt.Errorf("unable to get header for "+
×
496
                                "hash=%v: %w", chainHash, err)
×
497
                }
×
498
                reorgHash = reorgHeader.PrevBlock
1✔
499
                chainHash = chainHeader.PrevBlock
1✔
500
        }
501

502
        verboseHeader, err := chainConn.GetBlockHeaderVerbose(&chainHash)
2✔
503
        if err != nil {
2✔
504
                return 0, fmt.Errorf("unable to get verbose header for "+
×
505
                        "hash=%v: %w", chainHash, err)
×
506
        }
×
507

508
        return verboseHeader.Height, nil
2✔
509
}
510

511
// GetClientMissedBlocks uses a client's best block to determine what blocks
512
// it missed being notified about, and returns them in a slice. Its
513
// backendStoresReorgs parameter tells it whether or not the notifier's
514
// chainConn stores information about blocks that have been reorged out of the
515
// chain, which allows GetClientMissedBlocks to find out whether the client's
516
// best block has been reorged out of the chain, rewind to the common ancestor
517
// and return blocks starting right after the common ancestor.
518
func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch,
519
        notifierBestHeight int32, backendStoresReorgs bool) ([]BlockEpoch, error) {
3✔
520

3✔
521
        startingHeight := clientBestBlock.Height
3✔
522
        if backendStoresReorgs {
5✔
523
                // If a reorg causes the client's best hash to be incorrect,
2✔
524
                // retrieve the closest common ancestor and dispatch
2✔
525
                // notifications from there.
2✔
526
                hashAtBestHeight, err := chainConn.GetBlockHash(
2✔
527
                        int64(clientBestBlock.Height))
2✔
528
                if err != nil {
2✔
529
                        return nil, fmt.Errorf("unable to find blockhash for "+
×
530
                                "height=%d: %v", clientBestBlock.Height, err)
×
531
                }
×
532

533
                startingHeight, err = GetCommonBlockAncestorHeight(
2✔
534
                        chainConn, *clientBestBlock.Hash, *hashAtBestHeight,
2✔
535
                )
2✔
536
                if err != nil {
2✔
537
                        return nil, fmt.Errorf("unable to find common ancestor: "+
×
538
                                "%v", err)
×
539
                }
×
540
        }
541

542
        // We want to start dispatching historical notifications from the block
543
        // right after the client's best block, to avoid a redundant notification.
544
        missedBlocks, err := getMissedBlocks(
3✔
545
                chainConn, startingHeight+1, notifierBestHeight+1,
3✔
546
        )
3✔
547
        if err != nil {
3✔
UNCOV
548
                return nil, fmt.Errorf("unable to get missed blocks: %w", err)
×
UNCOV
549
        }
×
550

551
        return missedBlocks, nil
3✔
552
}
553

554
// RewindChain handles internal state updates for the notifier's TxNotifier. It
555
// has no effect if given a height greater than or equal to our current best
556
// known height. It returns the new best block for the notifier.
557
func RewindChain(chainConn ChainConn, txNotifier *TxNotifier,
558
        currBestBlock BlockEpoch, targetHeight int32) (BlockEpoch, error) {
2✔
559

2✔
560
        newBestBlock := BlockEpoch{
2✔
561
                Height:      currBestBlock.Height,
2✔
562
                Hash:        currBestBlock.Hash,
2✔
563
                BlockHeader: currBestBlock.BlockHeader,
2✔
564
        }
2✔
565

2✔
566
        for height := currBestBlock.Height; height > targetHeight; height-- {
4✔
567
                hash, err := chainConn.GetBlockHash(int64(height - 1))
2✔
568
                if err != nil {
3✔
569
                        return newBestBlock, fmt.Errorf("unable to "+
1✔
570
                                "find blockhash for disconnected height=%d: %v",
1✔
571
                                height, err)
1✔
572
                }
1✔
573
                header, err := chainConn.GetBlockHeader(hash)
2✔
574
                if err != nil {
2✔
575
                        return newBestBlock, fmt.Errorf("unable to get block "+
×
576
                                "header for height=%v", height-1)
×
577
                }
×
578

579
                Log.Infof("Block disconnected from main chain: "+
2✔
580
                        "height=%v, sha=%v", height, newBestBlock.Hash)
2✔
581

2✔
582
                err = txNotifier.DisconnectTip(uint32(height))
2✔
583
                if err != nil {
2✔
584
                        return newBestBlock, fmt.Errorf("unable to "+
×
585
                                " disconnect tip for height=%d: %v",
×
586
                                height, err)
×
587
                }
×
588
                newBestBlock.Height = height - 1
2✔
589
                newBestBlock.Hash = hash
2✔
590
                newBestBlock.BlockHeader = header
2✔
591
        }
592

593
        return newBestBlock, nil
2✔
594
}
595

596
// HandleMissedBlocks is called when the chain backend for a notifier misses a
597
// series of blocks, handling a reorg if necessary. Its backendStoresReorgs
598
// parameter tells it whether or not the notifier's chainConn stores
599
// information about blocks that have been reorged out of the chain, which allows
600
// HandleMissedBlocks to check whether the notifier's best block has been
601
// reorged out, and rewind the chain accordingly. It returns the best block for
602
// the notifier and a slice of the missed blocks. The new best block needs to be
603
// returned in case a chain rewind occurs and partially completes before
604
// erroring. In the case where there is no rewind, the notifier's
605
// current best block is returned.
606
func HandleMissedBlocks(chainConn ChainConn, txNotifier *TxNotifier,
607
        currBestBlock BlockEpoch, newHeight int32,
608
        backendStoresReorgs bool) (BlockEpoch, []BlockEpoch, error) {
2✔
609

2✔
610
        startingHeight := currBestBlock.Height
2✔
611

2✔
612
        if backendStoresReorgs {
3✔
613
                // If a reorg causes our best hash to be incorrect, rewind the
1✔
614
                // chain so our best block is set to the closest common
1✔
615
                // ancestor, then dispatch notifications from there.
1✔
616
                hashAtBestHeight, err := chainConn.GetBlockHash(
1✔
617
                        int64(currBestBlock.Height),
1✔
618
                )
1✔
619
                if err != nil {
1✔
620
                        return currBestBlock, nil, fmt.Errorf("unable to find "+
×
621
                                "blockhash for height=%d: %v",
×
622
                                currBestBlock.Height, err)
×
623
                }
×
624

625
                startingHeight, err = GetCommonBlockAncestorHeight(
1✔
626
                        chainConn, *currBestBlock.Hash, *hashAtBestHeight,
1✔
627
                )
1✔
628
                if err != nil {
1✔
629
                        return currBestBlock, nil, fmt.Errorf("unable to find "+
×
630
                                "common ancestor: %v", err)
×
631
                }
×
632

633
                currBestBlock, err = RewindChain(
1✔
634
                        chainConn, txNotifier, currBestBlock, startingHeight,
1✔
635
                )
1✔
636
                if err != nil {
1✔
637
                        return currBestBlock, nil, fmt.Errorf("unable to "+
×
638
                                "rewind chain: %v", err)
×
639
                }
×
640
        }
641

642
        // We want to start dispatching historical notifications from the block
643
        // right after our best block, to avoid a redundant notification.
644
        missedBlocks, err := getMissedBlocks(chainConn, startingHeight+1, newHeight)
2✔
645
        if err != nil {
3✔
646
                return currBestBlock, nil, fmt.Errorf("unable to get missed "+
1✔
647
                        "blocks: %v", err)
1✔
648
        }
1✔
649

650
        return currBestBlock, missedBlocks, nil
1✔
651
}
652

653
// getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight)
654
// fetched from the chain.
655
func getMissedBlocks(chainConn ChainConn, startingHeight,
656
        endingHeight int32) ([]BlockEpoch, error) {
3✔
657

3✔
658
        numMissedBlocks := endingHeight - startingHeight
3✔
659
        if numMissedBlocks < 0 {
4✔
660
                return nil, fmt.Errorf("starting height %d is greater than "+
1✔
661
                        "ending height %d", startingHeight, endingHeight)
1✔
662
        }
1✔
663

664
        missedBlocks := make([]BlockEpoch, 0, numMissedBlocks)
3✔
665
        for height := startingHeight; height < endingHeight; height++ {
4✔
666
                hash, err := chainConn.GetBlockHash(int64(height))
1✔
667
                if err != nil {
1✔
668
                        return nil, fmt.Errorf("unable to find blockhash for "+
×
669
                                "height=%d: %v", height, err)
×
670
                }
×
671
                header, err := chainConn.GetBlockHeader(hash)
1✔
672
                if err != nil {
1✔
673
                        return nil, fmt.Errorf("unable to find block header "+
×
674
                                "for height=%d: %v", height, err)
×
675
                }
×
676

677
                missedBlocks = append(
1✔
678
                        missedBlocks,
1✔
679
                        BlockEpoch{
1✔
680
                                Hash:        hash,
1✔
681
                                Height:      height,
1✔
682
                                BlockHeader: header,
1✔
683
                        },
1✔
684
                )
1✔
685
        }
686

687
        return missedBlocks, nil
3✔
688
}
689

690
// TxIndexConn abstracts an RPC backend with txindex enabled.
691
type TxIndexConn interface {
692
        // GetRawTransactionVerbose returns the transaction identified by the
693
        // passed chain hash, and returns additional information such as the
694
        // block that the transaction confirmed.
695
        GetRawTransactionVerbose(*chainhash.Hash) (*btcjson.TxRawResult, error)
696

697
        // GetBlock returns the block identified by the chain hash.
698
        GetBlock(*chainhash.Hash) (*wire.MsgBlock, error)
699
}
700

701
// ConfDetailsFromTxIndex looks up whether a transaction is already included in
702
// a block in the active chain by using the backend node's transaction index.
703
// If the transaction is found its TxConfStatus is returned. If it was found in
704
// the mempool this will be TxFoundMempool, if it is found in a block this will
705
// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found
706
// in a block its confirmation details are also returned.
707
func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
708
        txNotFoundErr string) (*TxConfirmation, TxConfStatus, error) {
2✔
709

2✔
710
        // If the transaction has some or all of its confirmations required,
2✔
711
        // then we may be able to dispatch it immediately.
2✔
712
        rawTxRes, err := chainConn.GetRawTransactionVerbose(&r.TxID)
2✔
713
        if err != nil {
4✔
714
                // If the transaction lookup was successful, but it wasn't found
2✔
715
                // within the index itself, then we can exit early. We'll also
2✔
716
                // need to look at the error message returned as the error code
2✔
717
                // is used for multiple errors.
2✔
718
                jsonErr, ok := err.(*btcjson.RPCError)
2✔
719
                if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo &&
2✔
720
                        strings.Contains(jsonErr.Message, txNotFoundErr) {
4✔
721

2✔
722
                        return nil, TxNotFoundIndex, nil
2✔
723
                }
2✔
724

725
                return nil, TxNotFoundIndex,
×
726
                        fmt.Errorf("unable to query for txid %v: %w",
×
727
                                r.TxID, err)
×
728
        }
729

730
        // Deserialize the hex-encoded transaction to include it in the
731
        // confirmation details.
732
        rawTx, err := hex.DecodeString(rawTxRes.Hex)
2✔
733
        if err != nil {
2✔
734
                return nil, TxNotFoundIndex,
×
735
                        fmt.Errorf("unable to deserialize tx %v: %w",
×
736
                                r.TxID, err)
×
737
        }
×
738
        var tx wire.MsgTx
2✔
739
        if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
2✔
740
                return nil, TxNotFoundIndex,
×
741
                        fmt.Errorf("unable to deserialize tx %v: %w",
×
742
                                r.TxID, err)
×
743
        }
×
744

745
        // Ensure the transaction matches our confirmation request in terms of
746
        // txid and pkscript.
747
        if !r.MatchesTx(&tx) {
2✔
748
                return nil, TxNotFoundIndex,
×
749
                        fmt.Errorf("unable to locate tx %v", r.TxID)
×
750
        }
×
751

752
        // Make sure we actually retrieved a transaction that is included in a
753
        // block. If not, the transaction must be unconfirmed (in the mempool),
754
        // and we'll return TxFoundMempool together with a nil TxConfirmation.
755
        if rawTxRes.BlockHash == "" {
4✔
756
                return nil, TxFoundMempool, nil
2✔
757
        }
2✔
758

759
        // As we need to fully populate the returned TxConfirmation struct,
760
        // grab the block in which the transaction was confirmed so we can
761
        // locate its exact index within the block.
762
        blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
2✔
763
        if err != nil {
2✔
764
                return nil, TxNotFoundIndex,
×
765
                        fmt.Errorf("unable to get block hash %v for "+
×
766
                                "historical dispatch: %w", rawTxRes.BlockHash,
×
767
                                err)
×
768
        }
×
769
        block, err := chainConn.GetBlock(blockHash)
2✔
770
        if err != nil {
2✔
771
                return nil, TxNotFoundIndex,
×
772
                        fmt.Errorf("unable to get block with hash %v for "+
×
773
                                "historical dispatch: %w", blockHash, err)
×
774
        }
×
775

776
        // In the modern chain (the only one we really care about for LN), the
777
        // coinbase transaction of all blocks will include the block height.
778
        // Therefore we can save another query, and just use that height
779
        // directly.
780
        blockHeight, err := blockchain.ExtractCoinbaseHeight(
2✔
781
                btcutil.NewTx(block.Transactions[0]),
2✔
782
        )
2✔
783
        if err != nil {
2✔
784
                return nil, TxNotFoundIndex, fmt.Errorf("unable to extract "+
×
785
                        "coinbase height: %w", err)
×
786
        }
×
787

788
        // If the block was obtained, locate the transaction's index within the
789
        // block so we can give the subscriber full confirmation details.
790
        for txIndex, blockTx := range block.Transactions {
4✔
791
                if blockTx.TxHash() != r.TxID {
4✔
792
                        continue
2✔
793
                }
794

795
                return &TxConfirmation{
2✔
796
                        Tx:          tx.Copy(),
2✔
797
                        BlockHash:   blockHash,
2✔
798
                        BlockHeight: uint32(blockHeight),
2✔
799
                        TxIndex:     uint32(txIndex),
2✔
800
                        Block:       block,
2✔
801
                }, TxFoundIndex, nil
2✔
802
        }
803

804
        // We return an error because we should have found the transaction
805
        // within the block, but didn't.
806
        return nil, TxNotFoundIndex, fmt.Errorf("unable to locate "+
×
807
                "tx %v in block %v", r.TxID, blockHash)
×
808
}
809

810
// SpendHintCache is an interface whose duty is to cache spend hints for
811
// outpoints. A spend hint is defined as the earliest height in the chain at
812
// which an outpoint could have been spent within.
813
type SpendHintCache interface {
814
        // CommitSpendHint commits a spend hint for the outpoints to the cache.
815
        CommitSpendHint(height uint32, spendRequests ...SpendRequest) error
816

817
        // QuerySpendHint returns the latest spend hint for an outpoint.
818
        // ErrSpendHintNotFound is returned if a spend hint does not exist
819
        // within the cache for the outpoint.
820
        QuerySpendHint(spendRequest SpendRequest) (uint32, error)
821

822
        // PurgeSpendHint removes the spend hint for the outpoints from the
823
        // cache.
824
        PurgeSpendHint(spendRequests ...SpendRequest) error
825
}
826

827
// ConfirmHintCache is an interface whose duty is to cache confirm hints for
828
// transactions. A confirm hint is defined as the earliest height in the chain
829
// at which a transaction could have been included in a block.
830
type ConfirmHintCache interface {
831
        // CommitConfirmHint commits a confirm hint for the transactions to the
832
        // cache.
833
        CommitConfirmHint(height uint32, confRequests ...ConfRequest) error
834

835
        // QueryConfirmHint returns the latest confirm hint for a transaction
836
        // hash. ErrConfirmHintNotFound is returned if a confirm hint does not
837
        // exist within the cache for the transaction hash.
838
        QueryConfirmHint(confRequest ConfRequest) (uint32, error)
839

840
        // PurgeConfirmHint removes the confirm hint for the transactions from
841
        // the cache.
842
        PurgeConfirmHint(confRequests ...ConfRequest) error
843
}
844

845
// MempoolWatcher defines an interface that allows the caller to query
846
// information in the mempool.
847
type MempoolWatcher interface {
848
        // SubscribeMempoolSpent allows the caller to register a subscription
849
        // to watch for a spend of an outpoint in the mempool.The event will be
850
        // dispatched once the outpoint is spent in the mempool.
851
        SubscribeMempoolSpent(op wire.OutPoint) (*MempoolSpendEvent, error)
852

853
        // CancelMempoolSpendEvent allows the caller to cancel a subscription to
854
        // watch for a spend of an outpoint in the mempool.
855
        CancelMempoolSpendEvent(sub *MempoolSpendEvent)
856

857
        // LookupInputMempoolSpend looks up the mempool to find a spending tx
858
        // which spends the given outpoint. A fn.None is returned if it's not
859
        // found.
860
        LookupInputMempoolSpend(op wire.OutPoint) fn.Option[wire.MsgTx]
861
}
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