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

lightningnetwork / lnd / 15450589128

04 Jun 2025 07:05PM UTC coverage: 58.31% (-0.001%) from 58.311%
15450589128

Pull #9878

github

web-flow
Merge 2c95f81cb into aec16eee9
Pull Request #9878: chainntfns: add option to send all confirmations

14 of 22 new or added lines in 2 files covered. (63.64%)

45 existing lines in 9 files now uncovered.

97498 of 167205 relevant lines covered (58.31%)

1.81 hits per line

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

64.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
        // allConfirmations notifies the caller of every confirmation received
81
        // for the target transaction. If false, the caller will only be
82
        // notified once the transaction reaches the targeted number of
83
        // confirmations.
84
        allConfirmations bool
85
}
86

87
// defaultNotifierOptions returns the set of default options for the notifier.
88
func defaultNotifierOptions() *notifierOptions {
3✔
89
        return &notifierOptions{}
3✔
90
}
3✔
91

92
// NotifierOption is a functional option that allows a caller to modify the
93
// events received from the notifier.
94
type NotifierOption func(*notifierOptions)
95

96
// WithIncludeBlock is an optional argument that allows the caller to specify
97
// that the block that mined a transaction should be included in the response.
98
func WithIncludeBlock() NotifierOption {
3✔
99
        return func(o *notifierOptions) {
6✔
100
                o.includeBlock = true
3✔
101
        }
3✔
102
}
103

104
// WithAllConfirmations is an optional argument that allows the caller to
105
// specify that they wish to receive a notification for every confirmation of
106
// the target transaction, rather than only when it reaches the targeted number
107
// of confirmations.
NEW
108
func WithAllConfirmations() NotifierOption {
×
NEW
109
        return func(o *notifierOptions) {
×
NEW
110
                o.allConfirmations = true
×
NEW
111
        }
×
112
}
113

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

147
        // RegisterSpendNtfn registers an intent to be notified once the target
148
        // outpoint is successfully spent within a transaction. The script that
149
        // the outpoint creates must also be specified. This allows this
150
        // interface to be implemented by BIP 158-like filtering. If a nil
151
        // outpoint is passed in, then not only should we match on the script,
152
        // but we should also dispatch once a transaction spends the output
153
        // containing said script. This can be useful in instances where we only
154
        // know the script in advance, but not the outpoint itself.
155
        //
156
        // The returned SpendEvent will receive a send on the 'Spend'
157
        // transaction once a transaction spending the input is detected on the
158
        // blockchain. The heightHint parameter is provided as a convenience to
159
        // light clients. It denotes the earliest height in the blockchain in
160
        // which the target output could have been spent.
161
        //
162
        // NOTE: The notification should only be triggered when the spending
163
        // transaction receives a single confirmation.
164
        //
165
        // NOTE: Dispatching notifications to multiple clients subscribed to a
166
        // spend of the same outpoint MUST be supported.
167
        RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte,
168
                heightHint uint32) (*SpendEvent, error)
169

170
        // RegisterBlockEpochNtfn registers an intent to be notified of each
171
        // new block connected to the tip of the main chain. The returned
172
        // BlockEpochEvent struct contains a channel which will be sent upon
173
        // for each new block discovered.
174
        //
175
        // Clients have the option of passing in their best known block.
176
        // If they specify a block, the ChainNotifier checks whether the client
177
        // is behind on blocks. If they are, the ChainNotifier sends a backlog
178
        // of block notifications for the missed blocks. If they do not provide
179
        // one, then a notification will be dispatched immediately for the
180
        // current tip of the chain upon a successful registration.
181
        RegisterBlockEpochNtfn(*BlockEpoch) (*BlockEpochEvent, error)
182

183
        // Start the ChainNotifier. Once started, the implementation should be
184
        // ready, and able to receive notification registrations from clients.
185
        Start() error
186

187
        // Started returns true if this instance has been started, and false otherwise.
188
        Started() bool
189

190
        // Stops the concrete ChainNotifier. Once stopped, the ChainNotifier
191
        // should disallow any future requests from potential clients.
192
        // Additionally, all pending client notifications will be canceled
193
        // by closing the related channels on the *Event's.
194
        Stop() error
195
}
196

197
// TxConfirmation carries some additional block-level details of the exact
198
// block that specified transactions was confirmed within.
199
type TxConfirmation struct {
200
        // BlockHash is the hash of the block that confirmed the original
201
        // transition.
202
        BlockHash *chainhash.Hash
203

204
        // BlockHeight is the height of the block in which the transaction was
205
        // confirmed within.
206
        BlockHeight uint32
207

208
        // TxIndex is the index within the block of the ultimate confirmed
209
        // transaction.
210
        TxIndex uint32
211

212
        // Tx is the transaction for which the notification was requested for.
213
        Tx *wire.MsgTx
214

215
        // Block is the block that contains the transaction referenced above.
216
        //
217
        // NOTE: This is only specified if the confirmation request opts to
218
        // have the response include the block itself.
219
        Block *wire.MsgBlock
220

221
        // NumConfsLeft is the number of confirmations left for the transaction
222
        // to be regarded as fully confirmed.
223
        //
224
        // NOTE: This is only specified if the confirmation request opts to have
225
        // the response include all confirmations, rather than just the targeted
226
        // number of confirmations.
227
        NumConfsLeft uint32
228
}
229

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

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

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

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

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

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

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

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

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

×
318
                return false
×
319
        }
×
320

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

326
        // If the witness is non-empty, then we have a witness.
327
        return true
3✔
328
}
329

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
440
        drivers := make([]*NotifierDriver, 0, len(notifiers))
×
441
        for _, driver := range notifiers {
×
442
                drivers = append(drivers, driver)
×
443
        }
×
444

445
        return drivers
×
446
}
447

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

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

461
        notifiers[driver.NotifierType] = driver
3✔
462

3✔
463
        return nil
3✔
464
}
465

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

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

479
        return supportedNotifiers
×
480
}
481

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

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

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

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

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

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

525
        return verboseHeader.Height, nil
2✔
526
}
527

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

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

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

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

568
        return missedBlocks, nil
3✔
569
}
570

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

2✔
577
        newBestBlock := BlockEpoch{
2✔
578
                Height:      currBestBlock.Height,
2✔
579
                Hash:        currBestBlock.Hash,
2✔
580
                BlockHeader: currBestBlock.BlockHeader,
2✔
581
        }
2✔
582

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

596
                Log.Infof("Block disconnected from main chain: "+
2✔
597
                        "height=%v, sha=%v", height, newBestBlock.Hash)
2✔
598

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

610
        return newBestBlock, nil
2✔
611
}
612

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

2✔
627
        startingHeight := currBestBlock.Height
2✔
628

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

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

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

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

667
        return currBestBlock, missedBlocks, nil
1✔
668
}
669

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

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

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

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

704
        return missedBlocks, nil
3✔
705
}
706

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

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

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

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

2✔
739
                        return nil, TxNotFoundIndex, nil
2✔
740
                }
2✔
741

742
                return nil, TxNotFoundIndex,
×
743
                        fmt.Errorf("unable to query for txid %v: %w",
×
744
                                r.TxID, err)
×
745
        }
746

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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