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

lightningnetwork / lnd / 17830307614

18 Sep 2025 01:29PM UTC coverage: 54.617% (-12.0%) from 66.637%
17830307614

Pull #10200

github

web-flow
Merge 181a0a7bc into b34fc964b
Pull Request #10200: github: change to form-based issue template

109249 of 200028 relevant lines covered (54.62%)

21896.43 hits per line

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

67.87
/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 {
225✔
83
        return &NotifierOptions{}
225✔
84
}
225✔
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 caller to specify
91
// that the block that mined a transaction should be included in the response.
92
func WithIncludeBlock() NotifierOption {
53✔
93
        return func(o *NotifierOptions) {
105✔
94
                o.IncludeBlock = true
52✔
95
        }
52✔
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
// TxUpdateInfo contains information about a transaction before it has reached
207
// its required number of confirmations. Transactions are registered for
208
// notification for a specific number of "required" confirmations, this struct
209
// will update the caller incrementally after each new block is found as long as
210
// the transaction is not yet fully regarded as confirmed.
211
type TxUpdateInfo struct {
212
        // BlockHeight is the height of the block that contains the transaction.
213
        BlockHeight uint32
214

215
        // NumConfsLeft is the number of confirmations left for the transaction
216
        // to be regarded as fully confirmed.
217
        NumConfsLeft uint32
218
}
219

220
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
221
// callers can be notified of: the instance the target txid reaches the targeted
222
// number of confirmations, how many confirmations are left for the target txid
223
// to be fully confirmed at every new block height, and also in the event that
224
// the original txid becomes disconnected from the blockchain as a result of a
225
// re-org.
226
//
227
// Once the txid reaches the specified number of confirmations, the 'Confirmed'
228
// channel will be sent upon fulfilling the notification.
229
//
230
// If the event that the original transaction becomes re-org'd out of the main
231
// chain, the 'NegativeConf' will be sent upon with a value representing the
232
// depth of the re-org.
233
//
234
// NOTE: If the caller wishes to cancel their registered spend notification,
235
// the Cancel closure MUST be called.
236
type ConfirmationEvent struct {
237
        // Confirmed is a channel that will be sent upon once the transaction
238
        // has been fully confirmed. The struct sent will contain all the
239
        // details of the channel's confirmation.
240
        //
241
        // NOTE: This channel must be buffered.
242
        Confirmed chan *TxConfirmation
243

244
        // Updates is a channel that will sent upon, at every incremental
245
        // confirmation, how many confirmations are left to declare the
246
        // transaction as fully confirmed, along with the height of the block
247
        // that contains the transaction.
248
        //
249
        // NOTE: This channel must be buffered with the number of required
250
        // confirmations.
251
        Updates chan TxUpdateInfo
252

253
        // NegativeConf is a channel that will be sent upon if the transaction
254
        // confirms, but is later reorged out of the chain. The integer sent
255
        // through the channel represents the reorg depth.
256
        //
257
        // NOTE: This channel must be buffered.
258
        NegativeConf chan int32
259

260
        // Done is a channel that gets sent upon once the confirmation request
261
        // is no longer under the risk of being reorged out of the chain.
262
        //
263
        // NOTE: This channel must be buffered.
264
        Done chan struct{}
265

266
        // Cancel is a closure that should be executed by the caller in the case
267
        // that they wish to prematurely abandon their registered confirmation
268
        // notification.
269
        Cancel func()
270
}
271

272
// NewConfirmationEvent constructs a new ConfirmationEvent with newly opened
273
// channels.
274
func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
221✔
275
        return &ConfirmationEvent{
221✔
276
                // We cannot rely on the subscriber to immediately read from
221✔
277
                // the channel so we need to create a larger buffer to avoid
221✔
278
                // blocking the notifier.
221✔
279
                Confirmed:    make(chan *TxConfirmation, 1),
221✔
280
                Updates:      make(chan TxUpdateInfo, numConfs),
221✔
281
                NegativeConf: make(chan int32, 1),
221✔
282
                Done:         make(chan struct{}, 1),
221✔
283
                Cancel:       cancel,
221✔
284
        }
221✔
285
}
221✔
286

287
// SpendDetail contains details pertaining to a spent output. This struct itself
288
// is the spentness notification. It includes the original outpoint which triggered
289
// the notification, the hash of the transaction spending the output, the
290
// spending transaction itself, and finally the input index which spent the
291
// target output.
292
type SpendDetail struct {
293
        SpentOutPoint     *wire.OutPoint
294
        SpenderTxHash     *chainhash.Hash
295
        SpendingTx        *wire.MsgTx
296
        SpenderInputIndex uint32
297
        SpendingHeight    int32
298
}
299

300
// HasSpenderWitness returns true if the spending transaction has non-empty
301
// witness.
302
func (s *SpendDetail) HasSpenderWitness() bool {
28✔
303
        tx := s.SpendingTx
28✔
304

28✔
305
        // If there are no inputs, then there is no witness.
28✔
306
        if len(tx.TxIn) == 0 {
28✔
307
                return false
×
308
        }
×
309

310
        // If the spender input index is larger than the number of inputs, then
311
        // we don't have a witness and this is an error case so we log it.
312
        if uint32(len(tx.TxIn)) <= s.SpenderInputIndex {
28✔
313
                Log.Errorf("SpenderInputIndex %d is out of range for tx %v",
×
314
                        s.SpenderInputIndex, tx.TxHash())
×
315

×
316
                return false
×
317
        }
×
318

319
        // If the witness is empty, then there is no witness.
320
        if len(tx.TxIn[s.SpenderInputIndex].Witness) == 0 {
29✔
321
                return false
1✔
322
        }
1✔
323

324
        // If the witness is non-empty, then we have a witness.
325
        return true
27✔
326
}
327

328
// String returns a string representation of SpendDetail.
329
func (s *SpendDetail) String() string {
×
330
        return fmt.Sprintf("%v[%d] spending %v at height=%v", s.SpenderTxHash,
×
331
                s.SpenderInputIndex, s.SpentOutPoint, s.SpendingHeight)
×
332
}
×
333

334
// SpendEvent encapsulates a spentness notification. Its only field 'Spend' will
335
// be sent upon once the target output passed into RegisterSpendNtfn has been
336
// spent on the blockchain.
337
//
338
// NOTE: If the caller wishes to cancel their registered spend notification,
339
// the Cancel closure MUST be called.
340
type SpendEvent struct {
341
        // Spend is a receive only channel which will be sent upon once the
342
        // target outpoint has been spent.
343
        //
344
        // NOTE: This channel must be buffered.
345
        Spend chan *SpendDetail
346

347
        // Reorg is a channel that will be sent upon once we detect the spending
348
        // transaction of the outpoint in question has been reorged out of the
349
        // chain.
350
        //
351
        // NOTE: This channel must be buffered.
352
        Reorg chan struct{}
353

354
        // Done is a channel that gets sent upon once the confirmation request
355
        // is no longer under the risk of being reorged out of the chain.
356
        //
357
        // NOTE: This channel must be buffered.
358
        Done chan struct{}
359

360
        // Cancel is a closure that should be executed by the caller in the case
361
        // that they wish to prematurely abandon their registered spend
362
        // notification.
363
        Cancel func()
364
}
365

366
// NewSpendEvent constructs a new SpendEvent with newly opened channels.
367
func NewSpendEvent(cancel func()) *SpendEvent {
125✔
368
        return &SpendEvent{
125✔
369
                Spend:  make(chan *SpendDetail, 1),
125✔
370
                Reorg:  make(chan struct{}, 1),
125✔
371
                Done:   make(chan struct{}, 1),
125✔
372
                Cancel: cancel,
125✔
373
        }
125✔
374
}
125✔
375

376
// BlockEpoch represents metadata concerning each new block connected to the
377
// main chain.
378
type BlockEpoch struct {
379
        // Hash is the block hash of the latest block to be added to the tip of
380
        // the main chain.
381
        Hash *chainhash.Hash
382

383
        // Height is the height of the latest block to be added to the tip of
384
        // the main chain.
385
        Height int32
386

387
        // BlockHeader is the block header of this new height.
388
        BlockHeader *wire.BlockHeader
389
}
390

391
// BlockEpochEvent encapsulates an on-going stream of block epoch
392
// notifications. Its only field 'Epochs' will be sent upon for each new block
393
// connected to the main-chain.
394
//
395
// NOTE: If the caller wishes to cancel their registered block epoch
396
// notification, the Cancel closure MUST be called.
397
type BlockEpochEvent struct {
398
        // Epochs is a receive only channel that will be sent upon each time a
399
        // new block is connected to the end of the main chain.
400
        //
401
        // NOTE: This channel must be buffered.
402
        Epochs <-chan *BlockEpoch
403

404
        // Cancel is a closure that should be executed by the caller in the case
405
        // that they wish to abandon their registered block epochs notification.
406
        Cancel func()
407
}
408

409
// NotifierDriver represents a "driver" for a particular interface. A driver is
410
// identified by a globally unique string identifier along with a 'New()'
411
// method which is responsible for initializing a particular ChainNotifier
412
// concrete implementation.
413
type NotifierDriver struct {
414
        // NotifierType is a string which uniquely identifies the ChainNotifier
415
        // that this driver, drives.
416
        NotifierType string
417

418
        // New creates a new instance of a concrete ChainNotifier
419
        // implementation given a variadic set up arguments. The function takes
420
        // a variadic number of interface parameters in order to provide
421
        // initialization flexibility, thereby accommodating several potential
422
        // ChainNotifier implementations.
423
        New func(args ...interface{}) (ChainNotifier, error)
424
}
425

426
var (
427
        notifiers   = make(map[string]*NotifierDriver)
428
        registerMtx sync.Mutex
429
)
430

431
// RegisteredNotifiers returns a slice of all currently registered notifiers.
432
//
433
// NOTE: This function is safe for concurrent access.
434
func RegisteredNotifiers() []*NotifierDriver {
4✔
435
        registerMtx.Lock()
4✔
436
        defer registerMtx.Unlock()
4✔
437

4✔
438
        drivers := make([]*NotifierDriver, 0, len(notifiers))
4✔
439
        for _, driver := range notifiers {
20✔
440
                drivers = append(drivers, driver)
16✔
441
        }
16✔
442

443
        return drivers
4✔
444
}
445

446
// RegisterNotifier registers a NotifierDriver which is capable of driving a
447
// concrete ChainNotifier interface. In the case that this driver has already
448
// been registered, an error is returned.
449
//
450
// NOTE: This function is safe for concurrent access.
451
func RegisterNotifier(driver *NotifierDriver) error {
34✔
452
        registerMtx.Lock()
34✔
453
        defer registerMtx.Unlock()
34✔
454

34✔
455
        if _, ok := notifiers[driver.NotifierType]; ok {
34✔
456
                return fmt.Errorf("notifier already registered")
×
457
        }
×
458

459
        notifiers[driver.NotifierType] = driver
34✔
460

34✔
461
        return nil
34✔
462
}
463

464
// SupportedNotifiers returns a slice of strings that represent the database
465
// drivers that have been registered and are therefore supported.
466
//
467
// NOTE: This function is safe for concurrent access.
468
func SupportedNotifiers() []string {
×
469
        registerMtx.Lock()
×
470
        defer registerMtx.Unlock()
×
471

×
472
        supportedNotifiers := make([]string, 0, len(notifiers))
×
473
        for driverName := range notifiers {
×
474
                supportedNotifiers = append(supportedNotifiers, driverName)
×
475
        }
×
476

477
        return supportedNotifiers
×
478
}
479

480
// ChainConn enables notifiers to pass in their chain backend to interface
481
// functions that require it.
482
type ChainConn interface {
483
        // GetBlockHeader returns the block header for a hash.
484
        GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error)
485

486
        // GetBlockHeaderVerbose returns the verbose block header for a hash.
487
        GetBlockHeaderVerbose(blockHash *chainhash.Hash) (
488
                *btcjson.GetBlockHeaderVerboseResult, error)
489

490
        // GetBlockHash returns the hash from a block height.
491
        GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
492
}
493

494
// GetCommonBlockAncestorHeight takes in:
495
// (1) the hash of a block that has been reorged out of the main chain
496
// (2) the hash of the block of the same height from the main chain
497
// It returns the height of the nearest common ancestor between the two hashes,
498
// or an error
499
func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash,
500
        chainHash chainhash.Hash) (int32, error) {
21✔
501

21✔
502
        for reorgHash != chainHash {
51✔
503
                reorgHeader, err := chainConn.GetBlockHeader(&reorgHash)
30✔
504
                if err != nil {
30✔
505
                        return 0, fmt.Errorf("unable to get header for "+
×
506
                                "hash=%v: %w", reorgHash, err)
×
507
                }
×
508
                chainHeader, err := chainConn.GetBlockHeader(&chainHash)
30✔
509
                if err != nil {
30✔
510
                        return 0, fmt.Errorf("unable to get header for "+
×
511
                                "hash=%v: %w", chainHash, err)
×
512
                }
×
513
                reorgHash = reorgHeader.PrevBlock
30✔
514
                chainHash = chainHeader.PrevBlock
30✔
515
        }
516

517
        verboseHeader, err := chainConn.GetBlockHeaderVerbose(&chainHash)
21✔
518
        if err != nil {
21✔
519
                return 0, fmt.Errorf("unable to get verbose header for "+
×
520
                        "hash=%v: %w", chainHash, err)
×
521
        }
×
522

523
        return verboseHeader.Height, nil
21✔
524
}
525

526
// GetClientMissedBlocks uses a client's best block to determine what blocks
527
// it missed being notified about, and returns them in a slice. Its
528
// backendStoresReorgs parameter tells it whether or not the notifier's
529
// chainConn stores information about blocks that have been reorged out of the
530
// chain, which allows GetClientMissedBlocks to find out whether the client's
531
// best block has been reorged out of the chain, rewind to the common ancestor
532
// and return blocks starting right after the common ancestor.
533
func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch,
534
        notifierBestHeight int32, backendStoresReorgs bool) ([]BlockEpoch, error) {
20✔
535

20✔
536
        startingHeight := clientBestBlock.Height
20✔
537
        if backendStoresReorgs {
35✔
538
                // If a reorg causes the client's best hash to be incorrect,
15✔
539
                // retrieve the closest common ancestor and dispatch
15✔
540
                // notifications from there.
15✔
541
                hashAtBestHeight, err := chainConn.GetBlockHash(
15✔
542
                        int64(clientBestBlock.Height))
15✔
543
                if err != nil {
15✔
544
                        return nil, fmt.Errorf("unable to find blockhash for "+
×
545
                                "height=%d: %v", clientBestBlock.Height, err)
×
546
                }
×
547

548
                startingHeight, err = GetCommonBlockAncestorHeight(
15✔
549
                        chainConn, *clientBestBlock.Hash, *hashAtBestHeight,
15✔
550
                )
15✔
551
                if err != nil {
15✔
552
                        return nil, fmt.Errorf("unable to find common ancestor: "+
×
553
                                "%v", err)
×
554
                }
×
555
        }
556

557
        // We want to start dispatching historical notifications from the block
558
        // right after the client's best block, to avoid a redundant notification.
559
        missedBlocks, err := getMissedBlocks(
20✔
560
                chainConn, startingHeight+1, notifierBestHeight+1,
20✔
561
        )
20✔
562
        if err != nil {
20✔
563
                return nil, fmt.Errorf("unable to get missed blocks: %w", err)
×
564
        }
×
565

566
        return missedBlocks, nil
20✔
567
}
568

569
// RewindChain handles internal state updates for the notifier's TxNotifier. It
570
// has no effect if given a height greater than or equal to our current best
571
// known height. It returns the new best block for the notifier.
572
func RewindChain(chainConn ChainConn, txNotifier *TxNotifier,
573
        currBestBlock BlockEpoch, targetHeight int32) (BlockEpoch, error) {
63✔
574

63✔
575
        newBestBlock := BlockEpoch{
63✔
576
                Height:      currBestBlock.Height,
63✔
577
                Hash:        currBestBlock.Hash,
63✔
578
                BlockHeader: currBestBlock.BlockHeader,
63✔
579
        }
63✔
580

63✔
581
        for height := currBestBlock.Height; height > targetHeight; height-- {
152✔
582
                hash, err := chainConn.GetBlockHash(int64(height - 1))
89✔
583
                if err != nil {
91✔
584
                        return newBestBlock, fmt.Errorf("unable to "+
2✔
585
                                "find blockhash for disconnected height=%d: %v",
2✔
586
                                height, err)
2✔
587
                }
2✔
588
                header, err := chainConn.GetBlockHeader(hash)
87✔
589
                if err != nil {
87✔
590
                        return newBestBlock, fmt.Errorf("unable to get block "+
×
591
                                "header for height=%v", height-1)
×
592
                }
×
593

594
                Log.Infof("Block disconnected from main chain: "+
87✔
595
                        "height=%v, sha=%v", height, newBestBlock.Hash)
87✔
596

87✔
597
                err = txNotifier.DisconnectTip(uint32(height))
87✔
598
                if err != nil {
87✔
599
                        return newBestBlock, fmt.Errorf("unable to "+
×
600
                                " disconnect tip for height=%d: %v",
×
601
                                height, err)
×
602
                }
×
603
                newBestBlock.Height = height - 1
87✔
604
                newBestBlock.Hash = hash
87✔
605
                newBestBlock.BlockHeader = header
87✔
606
        }
607

608
        return newBestBlock, nil
61✔
609
}
610

611
// HandleMissedBlocks is called when the chain backend for a notifier misses a
612
// series of blocks, handling a reorg if necessary. Its backendStoresReorgs
613
// parameter tells it whether or not the notifier's chainConn stores
614
// information about blocks that have been reorged out of the chain, which allows
615
// HandleMissedBlocks to check whether the notifier's best block has been
616
// reorged out, and rewind the chain accordingly. It returns the best block for
617
// the notifier and a slice of the missed blocks. The new best block needs to be
618
// returned in case a chain rewind occurs and partially completes before
619
// erroring. In the case where there is no rewind, the notifier's
620
// current best block is returned.
621
func HandleMissedBlocks(chainConn ChainConn, txNotifier *TxNotifier,
622
        currBestBlock BlockEpoch, newHeight int32,
623
        backendStoresReorgs bool) (BlockEpoch, []BlockEpoch, error) {
10✔
624

10✔
625
        startingHeight := currBestBlock.Height
10✔
626

10✔
627
        if backendStoresReorgs {
16✔
628
                // If a reorg causes our best hash to be incorrect, rewind the
6✔
629
                // chain so our best block is set to the closest common
6✔
630
                // ancestor, then dispatch notifications from there.
6✔
631
                hashAtBestHeight, err := chainConn.GetBlockHash(
6✔
632
                        int64(currBestBlock.Height),
6✔
633
                )
6✔
634
                if err != nil {
6✔
635
                        return currBestBlock, nil, fmt.Errorf("unable to find "+
×
636
                                "blockhash for height=%d: %v",
×
637
                                currBestBlock.Height, err)
×
638
                }
×
639

640
                startingHeight, err = GetCommonBlockAncestorHeight(
6✔
641
                        chainConn, *currBestBlock.Hash, *hashAtBestHeight,
6✔
642
                )
6✔
643
                if err != nil {
6✔
644
                        return currBestBlock, nil, fmt.Errorf("unable to find "+
×
645
                                "common ancestor: %v", err)
×
646
                }
×
647

648
                currBestBlock, err = RewindChain(
6✔
649
                        chainConn, txNotifier, currBestBlock, startingHeight,
6✔
650
                )
6✔
651
                if err != nil {
6✔
652
                        return currBestBlock, nil, fmt.Errorf("unable to "+
×
653
                                "rewind chain: %v", err)
×
654
                }
×
655
        }
656

657
        // We want to start dispatching historical notifications from the block
658
        // right after our best block, to avoid a redundant notification.
659
        missedBlocks, err := getMissedBlocks(chainConn, startingHeight+1, newHeight)
10✔
660
        if err != nil {
13✔
661
                return currBestBlock, nil, fmt.Errorf("unable to get missed "+
3✔
662
                        "blocks: %v", err)
3✔
663
        }
3✔
664

665
        return currBestBlock, missedBlocks, nil
7✔
666
}
667

668
// getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight)
669
// fetched from the chain.
670
func getMissedBlocks(chainConn ChainConn, startingHeight,
671
        endingHeight int32) ([]BlockEpoch, error) {
30✔
672

30✔
673
        numMissedBlocks := endingHeight - startingHeight
30✔
674
        if numMissedBlocks < 0 {
33✔
675
                return nil, fmt.Errorf("starting height %d is greater than "+
3✔
676
                        "ending height %d", startingHeight, endingHeight)
3✔
677
        }
3✔
678

679
        missedBlocks := make([]BlockEpoch, 0, numMissedBlocks)
27✔
680
        for height := startingHeight; height < endingHeight; height++ {
300✔
681
                hash, err := chainConn.GetBlockHash(int64(height))
273✔
682
                if err != nil {
273✔
683
                        return nil, fmt.Errorf("unable to find blockhash for "+
×
684
                                "height=%d: %v", height, err)
×
685
                }
×
686
                header, err := chainConn.GetBlockHeader(hash)
273✔
687
                if err != nil {
273✔
688
                        return nil, fmt.Errorf("unable to find block header "+
×
689
                                "for height=%d: %v", height, err)
×
690
                }
×
691

692
                missedBlocks = append(
273✔
693
                        missedBlocks,
273✔
694
                        BlockEpoch{
273✔
695
                                Hash:        hash,
273✔
696
                                Height:      height,
273✔
697
                                BlockHeader: header,
273✔
698
                        },
273✔
699
                )
273✔
700
        }
701

702
        return missedBlocks, nil
27✔
703
}
704

705
// TxIndexConn abstracts an RPC backend with txindex enabled.
706
type TxIndexConn interface {
707
        // GetRawTransactionVerbose returns the transaction identified by the
708
        // passed chain hash, and returns additional information such as the
709
        // block that the transaction confirmed.
710
        GetRawTransactionVerbose(*chainhash.Hash) (*btcjson.TxRawResult, error)
711

712
        // GetBlock returns the block identified by the chain hash.
713
        GetBlock(*chainhash.Hash) (*wire.MsgBlock, error)
714
}
715

716
// ConfDetailsFromTxIndex looks up whether a transaction is already included in
717
// a block in the active chain by using the backend node's transaction index.
718
// If the transaction is found its TxConfStatus is returned. If it was found in
719
// the mempool this will be TxFoundMempool, if it is found in a block this will
720
// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found
721
// in a block its confirmation details are also returned.
722
func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
723
        txNotFoundErr string) (*TxConfirmation, TxConfStatus, error) {
68✔
724

68✔
725
        // If the transaction has some or all of its confirmations required,
68✔
726
        // then we may be able to dispatch it immediately.
68✔
727
        rawTxRes, err := chainConn.GetRawTransactionVerbose(&r.TxID)
68✔
728
        if err != nil {
96✔
729
                // If the transaction lookup was successful, but it wasn't found
28✔
730
                // within the index itself, then we can exit early. We'll also
28✔
731
                // need to look at the error message returned as the error code
28✔
732
                // is used for multiple errors.
28✔
733
                jsonErr, ok := err.(*btcjson.RPCError)
28✔
734
                if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo &&
28✔
735
                        strings.Contains(jsonErr.Message, txNotFoundErr) {
43✔
736

15✔
737
                        return nil, TxNotFoundIndex, nil
15✔
738
                }
15✔
739

740
                return nil, TxNotFoundIndex,
13✔
741
                        fmt.Errorf("unable to query for txid %v: %w",
13✔
742
                                r.TxID, err)
13✔
743
        }
744

745
        // Deserialize the hex-encoded transaction to include it in the
746
        // confirmation details.
747
        rawTx, err := hex.DecodeString(rawTxRes.Hex)
40✔
748
        if err != nil {
40✔
749
                return nil, TxNotFoundIndex,
×
750
                        fmt.Errorf("unable to deserialize tx %v: %w",
×
751
                                r.TxID, err)
×
752
        }
×
753
        var tx wire.MsgTx
40✔
754
        if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
40✔
755
                return nil, TxNotFoundIndex,
×
756
                        fmt.Errorf("unable to deserialize tx %v: %w",
×
757
                                r.TxID, err)
×
758
        }
×
759

760
        // Ensure the transaction matches our confirmation request in terms of
761
        // txid and pkscript.
762
        if !r.MatchesTx(&tx) {
44✔
763
                return nil, TxNotFoundIndex,
4✔
764
                        fmt.Errorf("unable to locate tx %v", r.TxID)
4✔
765
        }
4✔
766

767
        // Make sure we actually retrieved a transaction that is included in a
768
        // block. If not, the transaction must be unconfirmed (in the mempool),
769
        // and we'll return TxFoundMempool together with a nil TxConfirmation.
770
        if rawTxRes.BlockHash == "" {
63✔
771
                return nil, TxFoundMempool, nil
27✔
772
        }
27✔
773

774
        // As we need to fully populate the returned TxConfirmation struct,
775
        // grab the block in which the transaction was confirmed so we can
776
        // locate its exact index within the block.
777
        blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
9✔
778
        if err != nil {
9✔
779
                return nil, TxNotFoundIndex,
×
780
                        fmt.Errorf("unable to get block hash %v for "+
×
781
                                "historical dispatch: %w", rawTxRes.BlockHash,
×
782
                                err)
×
783
        }
×
784
        block, err := chainConn.GetBlock(blockHash)
9✔
785
        if err != nil {
9✔
786
                return nil, TxNotFoundIndex,
×
787
                        fmt.Errorf("unable to get block with hash %v for "+
×
788
                                "historical dispatch: %w", blockHash, err)
×
789
        }
×
790

791
        // In the modern chain (the only one we really care about for LN), the
792
        // coinbase transaction of all blocks will include the block height.
793
        // Therefore we can save another query, and just use that height
794
        // directly.
795
        blockHeight, err := blockchain.ExtractCoinbaseHeight(
9✔
796
                btcutil.NewTx(block.Transactions[0]),
9✔
797
        )
9✔
798
        if err != nil {
9✔
799
                return nil, TxNotFoundIndex, fmt.Errorf("unable to extract "+
×
800
                        "coinbase height: %w", err)
×
801
        }
×
802

803
        // If the block was obtained, locate the transaction's index within the
804
        // block so we can give the subscriber full confirmation details.
805
        for txIndex, blockTx := range block.Transactions {
28✔
806
                if blockTx.TxHash() != r.TxID {
29✔
807
                        continue
10✔
808
                }
809

810
                return &TxConfirmation{
9✔
811
                        Tx:          tx.Copy(),
9✔
812
                        BlockHash:   blockHash,
9✔
813
                        BlockHeight: uint32(blockHeight),
9✔
814
                        TxIndex:     uint32(txIndex),
9✔
815
                        Block:       block,
9✔
816
                }, TxFoundIndex, nil
9✔
817
        }
818

819
        // We return an error because we should have found the transaction
820
        // within the block, but didn't.
821
        return nil, TxNotFoundIndex, fmt.Errorf("unable to locate "+
×
822
                "tx %v in block %v", r.TxID, blockHash)
×
823
}
824

825
// SpendHintCache is an interface whose duty is to cache spend hints for
826
// outpoints. A spend hint is defined as the earliest height in the chain at
827
// which an outpoint could have been spent within.
828
type SpendHintCache interface {
829
        // CommitSpendHint commits a spend hint for the outpoints to the cache.
830
        CommitSpendHint(height uint32, spendRequests ...SpendRequest) error
831

832
        // QuerySpendHint returns the latest spend hint for an outpoint.
833
        // ErrSpendHintNotFound is returned if a spend hint does not exist
834
        // within the cache for the outpoint.
835
        QuerySpendHint(spendRequest SpendRequest) (uint32, error)
836

837
        // PurgeSpendHint removes the spend hint for the outpoints from the
838
        // cache.
839
        PurgeSpendHint(spendRequests ...SpendRequest) error
840
}
841

842
// ConfirmHintCache is an interface whose duty is to cache confirm hints for
843
// transactions. A confirm hint is defined as the earliest height in the chain
844
// at which a transaction could have been included in a block.
845
type ConfirmHintCache interface {
846
        // CommitConfirmHint commits a confirm hint for the transactions to the
847
        // cache.
848
        CommitConfirmHint(height uint32, confRequests ...ConfRequest) error
849

850
        // QueryConfirmHint returns the latest confirm hint for a transaction
851
        // hash. ErrConfirmHintNotFound is returned if a confirm hint does not
852
        // exist within the cache for the transaction hash.
853
        QueryConfirmHint(confRequest ConfRequest) (uint32, error)
854

855
        // PurgeConfirmHint removes the confirm hint for the transactions from
856
        // the cache.
857
        PurgeConfirmHint(confRequests ...ConfRequest) error
858
}
859

860
// MempoolWatcher defines an interface that allows the caller to query
861
// information in the mempool.
862
type MempoolWatcher interface {
863
        // SubscribeMempoolSpent allows the caller to register a subscription
864
        // to watch for a spend of an outpoint in the mempool.The event will be
865
        // dispatched once the outpoint is spent in the mempool.
866
        SubscribeMempoolSpent(op wire.OutPoint) (*MempoolSpendEvent, error)
867

868
        // CancelMempoolSpendEvent allows the caller to cancel a subscription to
869
        // watch for a spend of an outpoint in the mempool.
870
        CancelMempoolSpendEvent(sub *MempoolSpendEvent)
871

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