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

lightningnetwork / lnd / 15121633150

19 May 2025 07:34PM UTC coverage: 68.984% (-0.006%) from 68.99%
15121633150

Pull #9677

github

web-flow
Merge a45ae6437 into 3707b1fb7
Pull Request #9677: Expose confirmation count for pending 'channel open' transactions

154 of 186 new or added lines in 5 files covered. (82.8%)

77 existing lines in 20 files now uncovered.

133997 of 194245 relevant lines covered (68.98%)

22068.01 hits per line

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

69.33
/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 {
227✔
83
        return &notifierOptions{}
227✔
84
}
227✔
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 {
55✔
93
        return func(o *notifierOptions) {
110✔
94
                o.includeBlock = true
55✔
95
        }
55✔
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
// String returns a string representation of TxUpdateInfo.
NEW
221
func (t TxUpdateInfo) String() string {
×
NEW
222
        return fmt.Sprintf("TxUpdateInfo{NumConfsLeft: %d, BlockHeight: %d}",
×
NEW
223
                t.NumConfsLeft, t.BlockHeight)
×
NEW
224
}
×
225

226
// Equal returns true if the TxUpdateInfo is equal to other TxUpdateInfo.
227
func (t TxUpdateInfo) Equal(other *TxUpdateInfo) bool {
9✔
228
        return t.NumConfsLeft == other.NumConfsLeft &&
9✔
229
                t.BlockHeight == other.BlockHeight
9✔
230
}
9✔
231

232
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
233
// callers can be notified of: the instance the target txid reaches the targeted
234
// number of confirmations, how many confirmations are left for the target txid
235
// to be fully confirmed at every new block height, and also in the event that
236
// the original txid becomes disconnected from the blockchain as a result of a
237
// re-org.
238
//
239
// Once the txid reaches the specified number of confirmations, the 'Confirmed'
240
// channel will be sent upon fulfilling the notification.
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
        // Updates is a channel that will sent upon, at every incremental
257
        // confirmation, how many confirmations are left to declare the
258
        // transaction as fully confirmed, along with the height of the block
259
        // that contains the transaction.
260
        //
261
        // NOTE: This channel must be buffered with the number of required
262
        // confirmations.
263
        Updates chan TxUpdateInfo
264

265
        // NegativeConf is a channel that will be sent upon if the transaction
266
        // confirms, but is later reorged out of the chain. The integer sent
267
        // through the channel represents the reorg depth.
268
        //
269
        // NOTE: This channel must be buffered.
270
        NegativeConf chan int32
271

272
        // Done is a channel that gets sent upon once the confirmation request
273
        // is no longer under the risk of being reorged out of the chain.
274
        //
275
        // NOTE: This channel must be buffered.
276
        Done chan struct{}
277

278
        // Cancel is a closure that should be executed by the caller in the case
279
        // that they wish to prematurely abandon their registered confirmation
280
        // notification.
281
        Cancel func()
282
}
283

284
// NewConfirmationEvent constructs a new ConfirmationEvent with newly opened
285
// channels.
286
func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
223✔
287
        return &ConfirmationEvent{
223✔
288
                // We cannot rely on the subscriber to immediately read from
223✔
289
                // the channel so we need to create a larger buffer to avoid
223✔
290
                // blocking the notifier.
223✔
291
                Confirmed:    make(chan *TxConfirmation, 1),
223✔
292
                Updates:      make(chan TxUpdateInfo, numConfs),
223✔
293
                NegativeConf: make(chan int32, 1),
223✔
294
                Done:         make(chan struct{}, 1),
223✔
295
                Cancel:       cancel,
223✔
296
        }
223✔
297
}
223✔
298

299
// SpendDetail contains details pertaining to a spent output. This struct itself
300
// is the spentness notification. It includes the original outpoint which triggered
301
// the notification, the hash of the transaction spending the output, the
302
// spending transaction itself, and finally the input index which spent the
303
// target output.
304
type SpendDetail struct {
305
        SpentOutPoint     *wire.OutPoint
306
        SpenderTxHash     *chainhash.Hash
307
        SpendingTx        *wire.MsgTx
308
        SpenderInputIndex uint32
309
        SpendingHeight    int32
310
}
311

312
// HasSpenderWitness returns true if the spending transaction has non-empty
313
// witness.
314
func (s *SpendDetail) HasSpenderWitness() bool {
31✔
315
        tx := s.SpendingTx
31✔
316

31✔
317
        // If there are no inputs, then there is no witness.
31✔
318
        if len(tx.TxIn) == 0 {
31✔
319
                return false
×
320
        }
×
321

322
        // If the spender input index is larger than the number of inputs, then
323
        // we don't have a witness and this is an error case so we log it.
324
        if uint32(len(tx.TxIn)) <= s.SpenderInputIndex {
31✔
325
                Log.Errorf("SpenderInputIndex %d is out of range for tx %v",
×
326
                        s.SpenderInputIndex, tx.TxHash())
×
327

×
328
                return false
×
329
        }
×
330

331
        // If the witness is empty, then there is no witness.
332
        if len(tx.TxIn[s.SpenderInputIndex].Witness) == 0 {
32✔
333
                return false
1✔
334
        }
1✔
335

336
        // If the witness is non-empty, then we have a witness.
337
        return true
30✔
338
}
339

340
// String returns a string representation of SpendDetail.
341
func (s *SpendDetail) String() string {
3✔
342
        return fmt.Sprintf("%v[%d] spending %v at height=%v", s.SpenderTxHash,
3✔
343
                s.SpenderInputIndex, s.SpentOutPoint, s.SpendingHeight)
3✔
344
}
3✔
345

346
// SpendEvent encapsulates a spentness notification. Its only field 'Spend' will
347
// be sent upon once the target output passed into RegisterSpendNtfn has been
348
// spent on the blockchain.
349
//
350
// NOTE: If the caller wishes to cancel their registered spend notification,
351
// the Cancel closure MUST be called.
352
type SpendEvent struct {
353
        // Spend is a receive only channel which will be sent upon once the
354
        // target outpoint has been spent.
355
        //
356
        // NOTE: This channel must be buffered.
357
        Spend chan *SpendDetail
358

359
        // Reorg is a channel that will be sent upon once we detect the spending
360
        // transaction of the outpoint in question has been reorged out of the
361
        // chain.
362
        //
363
        // NOTE: This channel must be buffered.
364
        Reorg chan struct{}
365

366
        // Done is a channel that gets sent upon once the confirmation request
367
        // is no longer under the risk of being reorged out of the chain.
368
        //
369
        // NOTE: This channel must be buffered.
370
        Done chan struct{}
371

372
        // Cancel is a closure that should be executed by the caller in the case
373
        // that they wish to prematurely abandon their registered spend
374
        // notification.
375
        Cancel func()
376
}
377

378
// NewSpendEvent constructs a new SpendEvent with newly opened channels.
379
func NewSpendEvent(cancel func()) *SpendEvent {
128✔
380
        return &SpendEvent{
128✔
381
                Spend:  make(chan *SpendDetail, 1),
128✔
382
                Reorg:  make(chan struct{}, 1),
128✔
383
                Done:   make(chan struct{}, 1),
128✔
384
                Cancel: cancel,
128✔
385
        }
128✔
386
}
128✔
387

388
// BlockEpoch represents metadata concerning each new block connected to the
389
// main chain.
390
type BlockEpoch struct {
391
        // Hash is the block hash of the latest block to be added to the tip of
392
        // the main chain.
393
        Hash *chainhash.Hash
394

395
        // Height is the height of the latest block to be added to the tip of
396
        // the main chain.
397
        Height int32
398

399
        // BlockHeader is the block header of this new height.
400
        BlockHeader *wire.BlockHeader
401
}
402

403
// BlockEpochEvent encapsulates an on-going stream of block epoch
404
// notifications. Its only field 'Epochs' will be sent upon for each new block
405
// connected to the main-chain.
406
//
407
// NOTE: If the caller wishes to cancel their registered block epoch
408
// notification, the Cancel closure MUST be called.
409
type BlockEpochEvent struct {
410
        // Epochs is a receive only channel that will be sent upon each time a
411
        // new block is connected to the end of the main chain.
412
        //
413
        // NOTE: This channel must be buffered.
414
        Epochs <-chan *BlockEpoch
415

416
        // Cancel is a closure that should be executed by the caller in the case
417
        // that they wish to abandon their registered block epochs notification.
418
        Cancel func()
419
}
420

421
// NotifierDriver represents a "driver" for a particular interface. A driver is
422
// identified by a globally unique string identifier along with a 'New()'
423
// method which is responsible for initializing a particular ChainNotifier
424
// concrete implementation.
425
type NotifierDriver struct {
426
        // NotifierType is a string which uniquely identifies the ChainNotifier
427
        // that this driver, drives.
428
        NotifierType string
429

430
        // New creates a new instance of a concrete ChainNotifier
431
        // implementation given a variadic set up arguments. The function takes
432
        // a variadic number of interface parameters in order to provide
433
        // initialization flexibility, thereby accommodating several potential
434
        // ChainNotifier implementations.
435
        New func(args ...interface{}) (ChainNotifier, error)
436
}
437

438
var (
439
        notifiers   = make(map[string]*NotifierDriver)
440
        registerMtx sync.Mutex
441
)
442

443
// RegisteredNotifiers returns a slice of all currently registered notifiers.
444
//
445
// NOTE: This function is safe for concurrent access.
446
func RegisteredNotifiers() []*NotifierDriver {
4✔
447
        registerMtx.Lock()
4✔
448
        defer registerMtx.Unlock()
4✔
449

4✔
450
        drivers := make([]*NotifierDriver, 0, len(notifiers))
4✔
451
        for _, driver := range notifiers {
20✔
452
                drivers = append(drivers, driver)
16✔
453
        }
16✔
454

455
        return drivers
4✔
456
}
457

458
// RegisterNotifier registers a NotifierDriver which is capable of driving a
459
// concrete ChainNotifier interface. In the case that this driver has already
460
// been registered, an error is returned.
461
//
462
// NOTE: This function is safe for concurrent access.
463
func RegisterNotifier(driver *NotifierDriver) error {
37✔
464
        registerMtx.Lock()
37✔
465
        defer registerMtx.Unlock()
37✔
466

37✔
467
        if _, ok := notifiers[driver.NotifierType]; ok {
37✔
468
                return fmt.Errorf("notifier already registered")
×
469
        }
×
470

471
        notifiers[driver.NotifierType] = driver
37✔
472

37✔
473
        return nil
37✔
474
}
475

476
// SupportedNotifiers returns a slice of strings that represent the database
477
// drivers that have been registered and are therefore supported.
478
//
479
// NOTE: This function is safe for concurrent access.
480
func SupportedNotifiers() []string {
×
481
        registerMtx.Lock()
×
482
        defer registerMtx.Unlock()
×
483

×
484
        supportedNotifiers := make([]string, 0, len(notifiers))
×
485
        for driverName := range notifiers {
×
486
                supportedNotifiers = append(supportedNotifiers, driverName)
×
487
        }
×
488

489
        return supportedNotifiers
×
490
}
491

492
// ChainConn enables notifiers to pass in their chain backend to interface
493
// functions that require it.
494
type ChainConn interface {
495
        // GetBlockHeader returns the block header for a hash.
496
        GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error)
497

498
        // GetBlockHeaderVerbose returns the verbose block header for a hash.
499
        GetBlockHeaderVerbose(blockHash *chainhash.Hash) (
500
                *btcjson.GetBlockHeaderVerboseResult, error)
501

502
        // GetBlockHash returns the hash from a block height.
503
        GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
504
}
505

506
// GetCommonBlockAncestorHeight takes in:
507
// (1) the hash of a block that has been reorged out of the main chain
508
// (2) the hash of the block of the same height from the main chain
509
// It returns the height of the nearest common ancestor between the two hashes,
510
// or an error
511
func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash,
512
        chainHash chainhash.Hash) (int32, error) {
23✔
513

23✔
514
        for reorgHash != chainHash {
53✔
515
                reorgHeader, err := chainConn.GetBlockHeader(&reorgHash)
30✔
516
                if err != nil {
30✔
517
                        return 0, fmt.Errorf("unable to get header for "+
×
518
                                "hash=%v: %w", reorgHash, err)
×
519
                }
×
520
                chainHeader, err := chainConn.GetBlockHeader(&chainHash)
30✔
521
                if err != nil {
30✔
522
                        return 0, fmt.Errorf("unable to get header for "+
×
523
                                "hash=%v: %w", chainHash, err)
×
524
                }
×
525
                reorgHash = reorgHeader.PrevBlock
30✔
526
                chainHash = chainHeader.PrevBlock
30✔
527
        }
528

529
        verboseHeader, err := chainConn.GetBlockHeaderVerbose(&chainHash)
23✔
530
        if err != nil {
23✔
531
                return 0, fmt.Errorf("unable to get verbose header for "+
×
532
                        "hash=%v: %w", chainHash, err)
×
533
        }
×
534

535
        return verboseHeader.Height, nil
23✔
536
}
537

538
// GetClientMissedBlocks uses a client's best block to determine what blocks
539
// it missed being notified about, and returns them in a slice. Its
540
// backendStoresReorgs parameter tells it whether or not the notifier's
541
// chainConn stores information about blocks that have been reorged out of the
542
// chain, which allows GetClientMissedBlocks to find out whether the client's
543
// best block has been reorged out of the chain, rewind to the common ancestor
544
// and return blocks starting right after the common ancestor.
545
func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch,
546
        notifierBestHeight int32, backendStoresReorgs bool) ([]BlockEpoch, error) {
23✔
547

23✔
548
        startingHeight := clientBestBlock.Height
23✔
549
        if backendStoresReorgs {
40✔
550
                // If a reorg causes the client's best hash to be incorrect,
17✔
551
                // retrieve the closest common ancestor and dispatch
17✔
552
                // notifications from there.
17✔
553
                hashAtBestHeight, err := chainConn.GetBlockHash(
17✔
554
                        int64(clientBestBlock.Height))
17✔
555
                if err != nil {
17✔
556
                        return nil, fmt.Errorf("unable to find blockhash for "+
×
557
                                "height=%d: %v", clientBestBlock.Height, err)
×
558
                }
×
559

560
                startingHeight, err = GetCommonBlockAncestorHeight(
17✔
561
                        chainConn, *clientBestBlock.Hash, *hashAtBestHeight,
17✔
562
                )
17✔
563
                if err != nil {
17✔
564
                        return nil, fmt.Errorf("unable to find common ancestor: "+
×
565
                                "%v", err)
×
566
                }
×
567
        }
568

569
        // We want to start dispatching historical notifications from the block
570
        // right after the client's best block, to avoid a redundant notification.
571
        missedBlocks, err := getMissedBlocks(
23✔
572
                chainConn, startingHeight+1, notifierBestHeight+1,
23✔
573
        )
23✔
574
        if err != nil {
24✔
575
                return nil, fmt.Errorf("unable to get missed blocks: %w", err)
1✔
576
        }
1✔
577

578
        return missedBlocks, nil
23✔
579
}
580

581
// RewindChain handles internal state updates for the notifier's TxNotifier. It
582
// has no effect if given a height greater than or equal to our current best
583
// known height. It returns the new best block for the notifier.
584
func RewindChain(chainConn ChainConn, txNotifier *TxNotifier,
585
        currBestBlock BlockEpoch, targetHeight int32) (BlockEpoch, error) {
65✔
586

65✔
587
        newBestBlock := BlockEpoch{
65✔
588
                Height:      currBestBlock.Height,
65✔
589
                Hash:        currBestBlock.Hash,
65✔
590
                BlockHeader: currBestBlock.BlockHeader,
65✔
591
        }
65✔
592

65✔
593
        for height := currBestBlock.Height; height > targetHeight; height-- {
154✔
594
                hash, err := chainConn.GetBlockHash(int64(height - 1))
89✔
595
                if err != nil {
90✔
596
                        return newBestBlock, fmt.Errorf("unable to "+
1✔
597
                                "find blockhash for disconnected height=%d: %v",
1✔
598
                                height, err)
1✔
599
                }
1✔
600
                header, err := chainConn.GetBlockHeader(hash)
89✔
601
                if err != nil {
89✔
602
                        return newBestBlock, fmt.Errorf("unable to get block "+
×
603
                                "header for height=%v", height-1)
×
604
                }
×
605

606
                Log.Infof("Block disconnected from main chain: "+
89✔
607
                        "height=%v, sha=%v", height, newBestBlock.Hash)
89✔
608

89✔
609
                err = txNotifier.DisconnectTip(uint32(height))
89✔
610
                if err != nil {
89✔
611
                        return newBestBlock, fmt.Errorf("unable to "+
×
612
                                " disconnect tip for height=%d: %v",
×
613
                                height, err)
×
614
                }
×
615
                newBestBlock.Height = height - 1
89✔
616
                newBestBlock.Hash = hash
89✔
617
                newBestBlock.BlockHeader = header
89✔
618
        }
619

620
        return newBestBlock, nil
65✔
621
}
622

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

11✔
637
        startingHeight := currBestBlock.Height
11✔
638

11✔
639
        if backendStoresReorgs {
17✔
640
                // If a reorg causes our best hash to be incorrect, rewind the
6✔
641
                // chain so our best block is set to the closest common
6✔
642
                // ancestor, then dispatch notifications from there.
6✔
643
                hashAtBestHeight, err := chainConn.GetBlockHash(
6✔
644
                        int64(currBestBlock.Height),
6✔
645
                )
6✔
646
                if err != nil {
6✔
647
                        return currBestBlock, nil, fmt.Errorf("unable to find "+
×
648
                                "blockhash for height=%d: %v",
×
649
                                currBestBlock.Height, err)
×
650
                }
×
651

652
                startingHeight, err = GetCommonBlockAncestorHeight(
6✔
653
                        chainConn, *currBestBlock.Hash, *hashAtBestHeight,
6✔
654
                )
6✔
655
                if err != nil {
6✔
656
                        return currBestBlock, nil, fmt.Errorf("unable to find "+
×
657
                                "common ancestor: %v", err)
×
658
                }
×
659

660
                currBestBlock, err = RewindChain(
6✔
661
                        chainConn, txNotifier, currBestBlock, startingHeight,
6✔
662
                )
6✔
663
                if err != nil {
6✔
664
                        return currBestBlock, nil, fmt.Errorf("unable to "+
×
665
                                "rewind chain: %v", err)
×
666
                }
×
667
        }
668

669
        // We want to start dispatching historical notifications from the block
670
        // right after our best block, to avoid a redundant notification.
671
        missedBlocks, err := getMissedBlocks(chainConn, startingHeight+1, newHeight)
11✔
672
        if err != nil {
15✔
673
                return currBestBlock, nil, fmt.Errorf("unable to get missed "+
4✔
674
                        "blocks: %v", err)
4✔
675
        }
4✔
676

677
        return currBestBlock, missedBlocks, nil
7✔
678
}
679

680
// getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight)
681
// fetched from the chain.
682
func getMissedBlocks(chainConn ChainConn, startingHeight,
683
        endingHeight int32) ([]BlockEpoch, error) {
33✔
684

33✔
685
        numMissedBlocks := endingHeight - startingHeight
33✔
686
        if numMissedBlocks < 0 {
38✔
687
                return nil, fmt.Errorf("starting height %d is greater than "+
5✔
688
                        "ending height %d", startingHeight, endingHeight)
5✔
689
        }
5✔
690

691
        missedBlocks := make([]BlockEpoch, 0, numMissedBlocks)
30✔
692
        for height := startingHeight; height < endingHeight; height++ {
304✔
693
                hash, err := chainConn.GetBlockHash(int64(height))
274✔
694
                if err != nil {
274✔
695
                        return nil, fmt.Errorf("unable to find blockhash for "+
×
696
                                "height=%d: %v", height, err)
×
697
                }
×
698
                header, err := chainConn.GetBlockHeader(hash)
274✔
699
                if err != nil {
274✔
700
                        return nil, fmt.Errorf("unable to find block header "+
×
701
                                "for height=%d: %v", height, err)
×
702
                }
×
703

704
                missedBlocks = append(
274✔
705
                        missedBlocks,
274✔
706
                        BlockEpoch{
274✔
707
                                Hash:        hash,
274✔
708
                                Height:      height,
274✔
709
                                BlockHeader: header,
274✔
710
                        },
274✔
711
                )
274✔
712
        }
713

714
        return missedBlocks, nil
30✔
715
}
716

717
// TxIndexConn abstracts an RPC backend with txindex enabled.
718
type TxIndexConn interface {
719
        // GetRawTransactionVerbose returns the transaction identified by the
720
        // passed chain hash, and returns additional information such as the
721
        // block that the transaction confirmed.
722
        GetRawTransactionVerbose(*chainhash.Hash) (*btcjson.TxRawResult, error)
723

724
        // GetBlock returns the block identified by the chain hash.
725
        GetBlock(*chainhash.Hash) (*wire.MsgBlock, error)
726
}
727

728
// ConfDetailsFromTxIndex looks up whether a transaction is already included in
729
// a block in the active chain by using the backend node's transaction index.
730
// If the transaction is found its TxConfStatus is returned. If it was found in
731
// the mempool this will be TxFoundMempool, if it is found in a block this will
732
// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found
733
// in a block its confirmation details are also returned.
734
func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
735
        txNotFoundErr string) (*TxConfirmation, TxConfStatus, error) {
70✔
736

70✔
737
        // If the transaction has some or all of its confirmations required,
70✔
738
        // then we may be able to dispatch it immediately.
70✔
739
        rawTxRes, err := chainConn.GetRawTransactionVerbose(&r.TxID)
70✔
740
        if err != nil {
99✔
741
                // If the transaction lookup was successful, but it wasn't found
29✔
742
                // within the index itself, then we can exit early. We'll also
29✔
743
                // need to look at the error message returned as the error code
29✔
744
                // is used for multiple errors.
29✔
745
                jsonErr, ok := err.(*btcjson.RPCError)
29✔
746
                if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo &&
29✔
747
                        strings.Contains(jsonErr.Message, txNotFoundErr) {
46✔
748

17✔
749
                        return nil, TxNotFoundIndex, nil
17✔
750
                }
17✔
751

752
                return nil, TxNotFoundIndex,
12✔
753
                        fmt.Errorf("unable to query for txid %v: %w",
12✔
754
                                r.TxID, err)
12✔
755
        }
756

757
        // Deserialize the hex-encoded transaction to include it in the
758
        // confirmation details.
759
        rawTx, err := hex.DecodeString(rawTxRes.Hex)
43✔
760
        if err != nil {
43✔
761
                return nil, TxNotFoundIndex,
×
762
                        fmt.Errorf("unable to deserialize tx %v: %w",
×
763
                                r.TxID, err)
×
764
        }
×
765
        var tx wire.MsgTx
43✔
766
        if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
43✔
767
                return nil, TxNotFoundIndex,
×
768
                        fmt.Errorf("unable to deserialize tx %v: %w",
×
769
                                r.TxID, err)
×
770
        }
×
771

772
        // Ensure the transaction matches our confirmation request in terms of
773
        // txid and pkscript.
774
        if !r.MatchesTx(&tx) {
47✔
775
                return nil, TxNotFoundIndex,
4✔
776
                        fmt.Errorf("unable to locate tx %v", r.TxID)
4✔
777
        }
4✔
778

779
        // Make sure we actually retrieved a transaction that is included in a
780
        // block. If not, the transaction must be unconfirmed (in the mempool),
781
        // and we'll return TxFoundMempool together with a nil TxConfirmation.
782
        if rawTxRes.BlockHash == "" {
70✔
783
                return nil, TxFoundMempool, nil
31✔
784
        }
31✔
785

786
        // As we need to fully populate the returned TxConfirmation struct,
787
        // grab the block in which the transaction was confirmed so we can
788
        // locate its exact index within the block.
789
        blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
10✔
790
        if err != nil {
10✔
791
                return nil, TxNotFoundIndex,
×
792
                        fmt.Errorf("unable to get block hash %v for "+
×
793
                                "historical dispatch: %w", rawTxRes.BlockHash,
×
794
                                err)
×
795
        }
×
796
        block, err := chainConn.GetBlock(blockHash)
10✔
797
        if err != nil {
10✔
798
                return nil, TxNotFoundIndex,
×
799
                        fmt.Errorf("unable to get block with hash %v for "+
×
800
                                "historical dispatch: %w", blockHash, err)
×
801
        }
×
802

803
        // In the modern chain (the only one we really care about for LN), the
804
        // coinbase transaction of all blocks will include the block height.
805
        // Therefore we can save another query, and just use that height
806
        // directly.
807
        blockHeight, err := blockchain.ExtractCoinbaseHeight(
10✔
808
                btcutil.NewTx(block.Transactions[0]),
10✔
809
        )
10✔
810
        if err != nil {
10✔
811
                return nil, TxNotFoundIndex, fmt.Errorf("unable to extract "+
×
812
                        "coinbase height: %w", err)
×
813
        }
×
814

815
        // If the block was obtained, locate the transaction's index within the
816
        // block so we can give the subscriber full confirmation details.
817
        for txIndex, blockTx := range block.Transactions {
30✔
818
                if blockTx.TxHash() != r.TxID {
32✔
819
                        continue
12✔
820
                }
821

822
                return &TxConfirmation{
10✔
823
                        Tx:          tx.Copy(),
10✔
824
                        BlockHash:   blockHash,
10✔
825
                        BlockHeight: uint32(blockHeight),
10✔
826
                        TxIndex:     uint32(txIndex),
10✔
827
                        Block:       block,
10✔
828
                }, TxFoundIndex, nil
10✔
829
        }
830

831
        // We return an error because we should have found the transaction
832
        // within the block, but didn't.
833
        return nil, TxNotFoundIndex, fmt.Errorf("unable to locate "+
×
834
                "tx %v in block %v", r.TxID, blockHash)
×
835
}
836

837
// SpendHintCache is an interface whose duty is to cache spend hints for
838
// outpoints. A spend hint is defined as the earliest height in the chain at
839
// which an outpoint could have been spent within.
840
type SpendHintCache interface {
841
        // CommitSpendHint commits a spend hint for the outpoints to the cache.
842
        CommitSpendHint(height uint32, spendRequests ...SpendRequest) error
843

844
        // QuerySpendHint returns the latest spend hint for an outpoint.
845
        // ErrSpendHintNotFound is returned if a spend hint does not exist
846
        // within the cache for the outpoint.
847
        QuerySpendHint(spendRequest SpendRequest) (uint32, error)
848

849
        // PurgeSpendHint removes the spend hint for the outpoints from the
850
        // cache.
851
        PurgeSpendHint(spendRequests ...SpendRequest) error
852
}
853

854
// ConfirmHintCache is an interface whose duty is to cache confirm hints for
855
// transactions. A confirm hint is defined as the earliest height in the chain
856
// at which a transaction could have been included in a block.
857
type ConfirmHintCache interface {
858
        // CommitConfirmHint commits a confirm hint for the transactions to the
859
        // cache.
860
        CommitConfirmHint(height uint32, confRequests ...ConfRequest) error
861

862
        // QueryConfirmHint returns the latest confirm hint for a transaction
863
        // hash. ErrConfirmHintNotFound is returned if a confirm hint does not
864
        // exist within the cache for the transaction hash.
865
        QueryConfirmHint(confRequest ConfRequest) (uint32, error)
866

867
        // PurgeConfirmHint removes the confirm hint for the transactions from
868
        // the cache.
869
        PurgeConfirmHint(confRequests ...ConfRequest) error
870
}
871

872
// MempoolWatcher defines an interface that allows the caller to query
873
// information in the mempool.
874
type MempoolWatcher interface {
875
        // SubscribeMempoolSpent allows the caller to register a subscription
876
        // to watch for a spend of an outpoint in the mempool.The event will be
877
        // dispatched once the outpoint is spent in the mempool.
878
        SubscribeMempoolSpent(op wire.OutPoint) (*MempoolSpendEvent, error)
879

880
        // CancelMempoolSpendEvent allows the caller to cancel a subscription to
881
        // watch for a spend of an outpoint in the mempool.
882
        CancelMempoolSpendEvent(sub *MempoolSpendEvent)
883

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