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

lightningnetwork / lnd / 12349698563

16 Dec 2024 09:29AM UTC coverage: 58.55% (-0.09%) from 58.636%
12349698563

Pull #9357

github

GeorgeTsagk
contractcourt: include custom records on replayed htlc

When notifying the invoice registry for an exit hop htlc we also want to
include its custom records. The channelLink, the other caller of this
method, already populates this field. So we make sure the contest
resolver does so too.
Pull Request #9357: contractcourt: include custom records on replayed htlc

2 of 2 new or added lines in 1 file covered. (100.0%)

262 existing lines in 24 files now uncovered.

134243 of 229278 relevant lines covered (58.55%)

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

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

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

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

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

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

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

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

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

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

×
301
                return false
×
302
        }
×
303

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

428
        return drivers
4✔
429
}
430

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

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

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

37✔
446
        return nil
37✔
447
}
448

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

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

462
        return supportedNotifiers
×
463
}
464

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

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

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

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

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

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

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

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

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

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

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

551
        return missedBlocks, nil
23✔
552
}
553

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

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

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

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

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

593
        return newBestBlock, nil
64✔
594
}
595

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

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

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

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

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

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

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

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

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

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

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

687
        return missedBlocks, nil
30✔
688
}
689

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

857
        // LookupInputMempoolSpend looks up the mempool to find a spending tx
858
        // which spends the given outpoint. A fn.None is returned if it's not
859
        // found.
860
        LookupInputMempoolSpend(op wire.OutPoint) fn.Option[wire.MsgTx]
861
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc