mirror of
https://github.com/muun/recovery.git
synced 2025-11-10 22:10:14 -05:00
Release v0.3.0
This commit is contained in:
30
vendor/github.com/lightningnetwork/lnd/chainntnfs/README.md
generated
vendored
Normal file
30
vendor/github.com/lightningnetwork/lnd/chainntnfs/README.md
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
chainntnfs
|
||||
==========
|
||||
|
||||
[](https://travis-ci.org/lightningnetwork/lnd)
|
||||
[](https://github.com/lightningnetwork/lnd/blob/master/LICENSE)
|
||||
[](http://godoc.org/github.com/lightningnetwork/lnd/chainntnfs)
|
||||
|
||||
The chainntnfs package implements a set of interfaces which allow callers to
|
||||
receive notifications in response to specific on-chain events. The set of
|
||||
notifications available include:
|
||||
|
||||
* Notifications for each new block connected to the current best chain.
|
||||
* Notifications once a `txid` has reached a specified number of
|
||||
confirmations.
|
||||
* Notifications once a target outpoint (`txid:index`) has been spent.
|
||||
|
||||
These notifications are used within `lnd` in order to properly handle the
|
||||
workflows for: channel funding, cooperative channel closures, forced channel
|
||||
closures, channel contract breaches, sweeping time-locked outputs, and finally
|
||||
pruning the channel graph.
|
||||
|
||||
This package is intentionally general enough to be applicable outside the
|
||||
specific use cases within `lnd` outlined above. The current sole concrete
|
||||
implementation of the `ChainNotifier` interface depends on `btcd`.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/lightningnetwork/lnd/chainntnfs
|
||||
```
|
||||
296
vendor/github.com/lightningnetwork/lnd/chainntnfs/height_hint_cache.go
generated
vendored
Normal file
296
vendor/github.com/lightningnetwork/lnd/chainntnfs/height_hint_cache.go
generated
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
package chainntnfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/kvdb"
|
||||
)
|
||||
|
||||
var (
|
||||
// spendHintBucket is the name of the bucket which houses the height
|
||||
// hint for outpoints. Each height hint represents the earliest height
|
||||
// at which its corresponding outpoint could have been spent within.
|
||||
spendHintBucket = []byte("spend-hints")
|
||||
|
||||
// confirmHintBucket is the name of the bucket which houses the height
|
||||
// hints for transactions. Each height hint represents the earliest
|
||||
// height at which its corresponding transaction could have been
|
||||
// confirmed within.
|
||||
confirmHintBucket = []byte("confirm-hints")
|
||||
|
||||
// ErrCorruptedHeightHintCache indicates that the on-disk bucketing
|
||||
// structure has altered since the height hint cache instance was
|
||||
// initialized.
|
||||
ErrCorruptedHeightHintCache = errors.New("height hint cache has been " +
|
||||
"corrupted")
|
||||
|
||||
// ErrSpendHintNotFound is an error returned when a spend hint for an
|
||||
// outpoint was not found.
|
||||
ErrSpendHintNotFound = errors.New("spend hint not found")
|
||||
|
||||
// ErrConfirmHintNotFound is an error returned when a confirm hint for a
|
||||
// transaction was not found.
|
||||
ErrConfirmHintNotFound = errors.New("confirm hint not found")
|
||||
)
|
||||
|
||||
// SpendHintCache is an interface whose duty is to cache spend hints for
|
||||
// outpoints. A spend hint is defined as the earliest height in the chain at
|
||||
// which an outpoint could have been spent within.
|
||||
type SpendHintCache interface {
|
||||
// CommitSpendHint commits a spend hint for the outpoints to the cache.
|
||||
CommitSpendHint(height uint32, spendRequests ...SpendRequest) error
|
||||
|
||||
// QuerySpendHint returns the latest spend hint for an outpoint.
|
||||
// ErrSpendHintNotFound is returned if a spend hint does not exist
|
||||
// within the cache for the outpoint.
|
||||
QuerySpendHint(spendRequest SpendRequest) (uint32, error)
|
||||
|
||||
// PurgeSpendHint removes the spend hint for the outpoints from the
|
||||
// cache.
|
||||
PurgeSpendHint(spendRequests ...SpendRequest) error
|
||||
}
|
||||
|
||||
// ConfirmHintCache is an interface whose duty is to cache confirm hints for
|
||||
// transactions. A confirm hint is defined as the earliest height in the chain
|
||||
// at which a transaction could have been included in a block.
|
||||
type ConfirmHintCache interface {
|
||||
// CommitConfirmHint commits a confirm hint for the transactions to the
|
||||
// cache.
|
||||
CommitConfirmHint(height uint32, confRequests ...ConfRequest) error
|
||||
|
||||
// QueryConfirmHint returns the latest confirm hint for a transaction
|
||||
// hash. ErrConfirmHintNotFound is returned if a confirm hint does not
|
||||
// exist within the cache for the transaction hash.
|
||||
QueryConfirmHint(confRequest ConfRequest) (uint32, error)
|
||||
|
||||
// PurgeConfirmHint removes the confirm hint for the transactions from
|
||||
// the cache.
|
||||
PurgeConfirmHint(confRequests ...ConfRequest) error
|
||||
}
|
||||
|
||||
// HeightHintCache is an implementation of the SpendHintCache and
|
||||
// ConfirmHintCache interfaces backed by a channeldb DB instance where the hints
|
||||
// will be stored.
|
||||
type HeightHintCache struct {
|
||||
db *channeldb.DB
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure HeightHintCache satisfies the SpendHintCache
|
||||
// and ConfirmHintCache interfaces.
|
||||
var _ SpendHintCache = (*HeightHintCache)(nil)
|
||||
var _ ConfirmHintCache = (*HeightHintCache)(nil)
|
||||
|
||||
// NewHeightHintCache returns a new height hint cache backed by a database.
|
||||
func NewHeightHintCache(db *channeldb.DB) (*HeightHintCache, error) {
|
||||
cache := &HeightHintCache{db}
|
||||
if err := cache.initBuckets(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
// initBuckets ensures that the primary buckets used by the circuit are
|
||||
// initialized so that we can assume their existence after startup.
|
||||
func (c *HeightHintCache) initBuckets() error {
|
||||
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
|
||||
_, err := tx.CreateTopLevelBucket(spendHintBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.CreateTopLevelBucket(confirmHintBucket)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// CommitSpendHint commits a spend hint for the outpoints to the cache.
|
||||
func (c *HeightHintCache) CommitSpendHint(height uint32,
|
||||
spendRequests ...SpendRequest) error {
|
||||
|
||||
if len(spendRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Tracef("Updating spend hint to height %d for %v", height,
|
||||
spendRequests)
|
||||
|
||||
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
|
||||
spendHints := tx.ReadWriteBucket(spendHintBucket)
|
||||
if spendHints == nil {
|
||||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
var hint bytes.Buffer
|
||||
if err := channeldb.WriteElement(&hint, height); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, spendRequest := range spendRequests {
|
||||
spendHintKey, err := spendRequest.SpendHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = spendHints.Put(spendHintKey, hint.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// QuerySpendHint returns the latest spend hint for an outpoint.
|
||||
// ErrSpendHintNotFound is returned if a spend hint does not exist within the
|
||||
// cache for the outpoint.
|
||||
func (c *HeightHintCache) QuerySpendHint(spendRequest SpendRequest) (uint32, error) {
|
||||
var hint uint32
|
||||
err := kvdb.View(c.db, func(tx kvdb.ReadTx) error {
|
||||
spendHints := tx.ReadBucket(spendHintBucket)
|
||||
if spendHints == nil {
|
||||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
spendHintKey, err := spendRequest.SpendHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spendHint := spendHints.Get(spendHintKey)
|
||||
if spendHint == nil {
|
||||
return ErrSpendHintNotFound
|
||||
}
|
||||
|
||||
return channeldb.ReadElement(bytes.NewReader(spendHint), &hint)
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return hint, nil
|
||||
}
|
||||
|
||||
// PurgeSpendHint removes the spend hint for the outpoints from the cache.
|
||||
func (c *HeightHintCache) PurgeSpendHint(spendRequests ...SpendRequest) error {
|
||||
if len(spendRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Tracef("Removing spend hints for %v", spendRequests)
|
||||
|
||||
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
|
||||
spendHints := tx.ReadWriteBucket(spendHintBucket)
|
||||
if spendHints == nil {
|
||||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
for _, spendRequest := range spendRequests {
|
||||
spendHintKey, err := spendRequest.SpendHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := spendHints.Delete(spendHintKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// CommitConfirmHint commits a confirm hint for the transactions to the cache.
|
||||
func (c *HeightHintCache) CommitConfirmHint(height uint32,
|
||||
confRequests ...ConfRequest) error {
|
||||
|
||||
if len(confRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Tracef("Updating confirm hints to height %d for %v", height,
|
||||
confRequests)
|
||||
|
||||
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
|
||||
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
|
||||
if confirmHints == nil {
|
||||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
var hint bytes.Buffer
|
||||
if err := channeldb.WriteElement(&hint, height); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, confRequest := range confRequests {
|
||||
confHintKey, err := confRequest.ConfHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = confirmHints.Put(confHintKey, hint.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// QueryConfirmHint returns the latest confirm hint for a transaction hash.
|
||||
// ErrConfirmHintNotFound is returned if a confirm hint does not exist within
|
||||
// the cache for the transaction hash.
|
||||
func (c *HeightHintCache) QueryConfirmHint(confRequest ConfRequest) (uint32, error) {
|
||||
var hint uint32
|
||||
err := kvdb.View(c.db, func(tx kvdb.ReadTx) error {
|
||||
confirmHints := tx.ReadBucket(confirmHintBucket)
|
||||
if confirmHints == nil {
|
||||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
confHintKey, err := confRequest.ConfHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confirmHint := confirmHints.Get(confHintKey)
|
||||
if confirmHint == nil {
|
||||
return ErrConfirmHintNotFound
|
||||
}
|
||||
|
||||
return channeldb.ReadElement(bytes.NewReader(confirmHint), &hint)
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return hint, nil
|
||||
}
|
||||
|
||||
// PurgeConfirmHint removes the confirm hint for the transactions from the
|
||||
// cache.
|
||||
func (c *HeightHintCache) PurgeConfirmHint(confRequests ...ConfRequest) error {
|
||||
if len(confRequests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Log.Tracef("Removing confirm hints for %v", confRequests)
|
||||
|
||||
return kvdb.Batch(c.db.Backend, func(tx kvdb.RwTx) error {
|
||||
confirmHints := tx.ReadWriteBucket(confirmHintBucket)
|
||||
if confirmHints == nil {
|
||||
return ErrCorruptedHeightHintCache
|
||||
}
|
||||
|
||||
for _, confRequest := range confRequests {
|
||||
confHintKey, err := confRequest.ConfHintKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := confirmHints.Delete(confHintKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
705
vendor/github.com/lightningnetwork/lnd/chainntnfs/interface.go
generated
vendored
Normal file
705
vendor/github.com/lightningnetwork/lnd/chainntnfs/interface.go
generated
vendored
Normal file
@@ -0,0 +1,705 @@
|
||||
package chainntnfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrChainNotifierShuttingDown is used when we are trying to
|
||||
// measure a spend notification when notifier is already stopped.
|
||||
ErrChainNotifierShuttingDown = errors.New("chain notifier shutting down")
|
||||
)
|
||||
|
||||
// TxConfStatus denotes the status of a transaction's lookup.
|
||||
type TxConfStatus uint8
|
||||
|
||||
const (
|
||||
// TxFoundMempool denotes that the transaction was found within the
|
||||
// backend node's mempool.
|
||||
TxFoundMempool TxConfStatus = iota
|
||||
|
||||
// TxFoundIndex denotes that the transaction was found within the
|
||||
// backend node's txindex.
|
||||
TxFoundIndex
|
||||
|
||||
// TxNotFoundIndex denotes that the transaction was not found within the
|
||||
// backend node's txindex.
|
||||
TxNotFoundIndex
|
||||
|
||||
// TxFoundManually denotes that the transaction was found within the
|
||||
// chain by scanning for it manually.
|
||||
TxFoundManually
|
||||
|
||||
// TxNotFoundManually denotes that the transaction was not found within
|
||||
// the chain by scanning for it manually.
|
||||
TxNotFoundManually
|
||||
)
|
||||
|
||||
// String returns the string representation of the TxConfStatus.
|
||||
func (t TxConfStatus) String() string {
|
||||
switch t {
|
||||
case TxFoundMempool:
|
||||
return "TxFoundMempool"
|
||||
|
||||
case TxFoundIndex:
|
||||
return "TxFoundIndex"
|
||||
|
||||
case TxNotFoundIndex:
|
||||
return "TxNotFoundIndex"
|
||||
|
||||
case TxFoundManually:
|
||||
return "TxFoundManually"
|
||||
|
||||
case TxNotFoundManually:
|
||||
return "TxNotFoundManually"
|
||||
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// ChainNotifier represents a trusted source to receive notifications concerning
|
||||
// targeted events on the Bitcoin blockchain. The interface specification is
|
||||
// intentionally general in order to support a wide array of chain notification
|
||||
// implementations such as: btcd's websockets notifications, Bitcoin Core's
|
||||
// ZeroMQ notifications, various Bitcoin API services, Electrum servers, etc.
|
||||
//
|
||||
// Concrete implementations of ChainNotifier should be able to support multiple
|
||||
// concurrent client requests, as well as multiple concurrent notification events.
|
||||
type ChainNotifier interface {
|
||||
// RegisterConfirmationsNtfn registers an intent to be notified once
|
||||
// txid reaches numConfs confirmations. We also pass in the pkScript as
|
||||
// the default light client instead needs to match on scripts created in
|
||||
// the block. If a nil txid is passed in, then not only should we match
|
||||
// on the script, but we should also dispatch once the transaction
|
||||
// containing the script reaches numConfs confirmations. This can be
|
||||
// useful in instances where we only know the script in advance, but not
|
||||
// the transaction containing it.
|
||||
//
|
||||
// The returned ConfirmationEvent should properly notify the client once
|
||||
// the specified number of confirmations has been reached for the txid,
|
||||
// as well as if the original tx gets re-org'd out of the mainchain. The
|
||||
// heightHint parameter is provided as a convenience to light clients.
|
||||
// It heightHint denotes the earliest height in the blockchain in which
|
||||
// the target txid _could_ have been included in the chain. This can be
|
||||
// used to bound the search space when checking to see if a notification
|
||||
// can immediately be dispatched due to historical data.
|
||||
//
|
||||
// NOTE: Dispatching notifications to multiple clients subscribed to
|
||||
// the same (txid, numConfs) tuple MUST be supported.
|
||||
RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte,
|
||||
numConfs, heightHint uint32) (*ConfirmationEvent, error)
|
||||
|
||||
// RegisterSpendNtfn registers an intent to be notified once the target
|
||||
// outpoint is successfully spent within a transaction. The script that
|
||||
// the outpoint creates must also be specified. This allows this
|
||||
// interface to be implemented by BIP 158-like filtering. If a nil
|
||||
// outpoint is passed in, then not only should we match on the script,
|
||||
// but we should also dispatch once a transaction spends the output
|
||||
// containing said script. This can be useful in instances where we only
|
||||
// know the script in advance, but not the outpoint itself.
|
||||
//
|
||||
// The returned SpendEvent will receive a send on the 'Spend'
|
||||
// transaction once a transaction spending the input is detected on the
|
||||
// blockchain. The heightHint parameter is provided as a convenience to
|
||||
// light clients. It denotes the earliest height in the blockchain in
|
||||
// which the target output could have been spent.
|
||||
//
|
||||
// NOTE: The notification should only be triggered when the spending
|
||||
// transaction receives a single confirmation.
|
||||
//
|
||||
// NOTE: Dispatching notifications to multiple clients subscribed to a
|
||||
// spend of the same outpoint MUST be supported.
|
||||
RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte,
|
||||
heightHint uint32) (*SpendEvent, error)
|
||||
|
||||
// RegisterBlockEpochNtfn registers an intent to be notified of each
|
||||
// new block connected to the tip of the main chain. The returned
|
||||
// BlockEpochEvent struct contains a channel which will be sent upon
|
||||
// for each new block discovered.
|
||||
//
|
||||
// Clients have the option of passing in their best known block.
|
||||
// If they specify a block, the ChainNotifier checks whether the client
|
||||
// is behind on blocks. If they are, the ChainNotifier sends a backlog
|
||||
// of block notifications for the missed blocks. If they do not provide
|
||||
// one, then a notification will be dispatched immediately for the
|
||||
// current tip of the chain upon a successful registration.
|
||||
RegisterBlockEpochNtfn(*BlockEpoch) (*BlockEpochEvent, error)
|
||||
|
||||
// Start the ChainNotifier. Once started, the implementation should be
|
||||
// ready, and able to receive notification registrations from clients.
|
||||
Start() error
|
||||
|
||||
// Started returns true if this instance has been started, and false otherwise.
|
||||
Started() bool
|
||||
|
||||
// Stops the concrete ChainNotifier. Once stopped, the ChainNotifier
|
||||
// should disallow any future requests from potential clients.
|
||||
// Additionally, all pending client notifications will be canceled
|
||||
// by closing the related channels on the *Event's.
|
||||
Stop() error
|
||||
}
|
||||
|
||||
// TxConfirmation carries some additional block-level details of the exact
|
||||
// block that specified transactions was confirmed within.
|
||||
type TxConfirmation struct {
|
||||
// BlockHash is the hash of the block that confirmed the original
|
||||
// transition.
|
||||
BlockHash *chainhash.Hash
|
||||
|
||||
// BlockHeight is the height of the block in which the transaction was
|
||||
// confirmed within.
|
||||
BlockHeight uint32
|
||||
|
||||
// TxIndex is the index within the block of the ultimate confirmed
|
||||
// transaction.
|
||||
TxIndex uint32
|
||||
|
||||
// Tx is the transaction for which the notification was requested for.
|
||||
Tx *wire.MsgTx
|
||||
}
|
||||
|
||||
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
|
||||
// callers can be notified of: the instance the target txid reaches the targeted
|
||||
// number of confirmations, how many confirmations are left for the target txid
|
||||
// to be fully confirmed at every new block height, and also in the event that
|
||||
// the original txid becomes disconnected from the blockchain as a result of a
|
||||
// re-org.
|
||||
//
|
||||
// Once the txid reaches the specified number of confirmations, the 'Confirmed'
|
||||
// channel will be sent upon fulfilling the notification.
|
||||
//
|
||||
// If the event that the original transaction becomes re-org'd out of the main
|
||||
// chain, the 'NegativeConf' will be sent upon with a value representing the
|
||||
// depth of the re-org.
|
||||
//
|
||||
// NOTE: If the caller wishes to cancel their registered spend notification,
|
||||
// the Cancel closure MUST be called.
|
||||
type ConfirmationEvent struct {
|
||||
// Confirmed is a channel that will be sent upon once the transaction
|
||||
// has been fully confirmed. The struct sent will contain all the
|
||||
// details of the channel's confirmation.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
Confirmed chan *TxConfirmation
|
||||
|
||||
// Updates is a channel that will sent upon, at every incremental
|
||||
// confirmation, how many confirmations are left to declare the
|
||||
// transaction as fully confirmed.
|
||||
//
|
||||
// NOTE: This channel must be buffered with the number of required
|
||||
// confirmations.
|
||||
Updates chan uint32
|
||||
|
||||
// NegativeConf is a channel that will be sent upon if the transaction
|
||||
// confirms, but is later reorged out of the chain. The integer sent
|
||||
// through the channel represents the reorg depth.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
NegativeConf chan int32
|
||||
|
||||
// Done is a channel that gets sent upon once the confirmation request
|
||||
// is no longer under the risk of being reorged out of the chain.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
Done chan struct{}
|
||||
|
||||
// Cancel is a closure that should be executed by the caller in the case
|
||||
// that they wish to prematurely abandon their registered confirmation
|
||||
// notification.
|
||||
Cancel func()
|
||||
}
|
||||
|
||||
// NewConfirmationEvent constructs a new ConfirmationEvent with newly opened
|
||||
// channels.
|
||||
func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
|
||||
return &ConfirmationEvent{
|
||||
Confirmed: make(chan *TxConfirmation, 1),
|
||||
Updates: make(chan uint32, numConfs),
|
||||
NegativeConf: make(chan int32, 1),
|
||||
Done: make(chan struct{}, 1),
|
||||
Cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
// SpendDetail contains details pertaining to a spent output. This struct itself
|
||||
// is the spentness notification. It includes the original outpoint which triggered
|
||||
// the notification, the hash of the transaction spending the output, the
|
||||
// spending transaction itself, and finally the input index which spent the
|
||||
// target output.
|
||||
type SpendDetail struct {
|
||||
SpentOutPoint *wire.OutPoint
|
||||
SpenderTxHash *chainhash.Hash
|
||||
SpendingTx *wire.MsgTx
|
||||
SpenderInputIndex uint32
|
||||
SpendingHeight int32
|
||||
}
|
||||
|
||||
// String returns a string representation of SpendDetail.
|
||||
func (s *SpendDetail) String() string {
|
||||
return fmt.Sprintf("%v[%d] spending %v at height=%v", s.SpenderTxHash,
|
||||
s.SpenderInputIndex, s.SpentOutPoint, s.SpendingHeight)
|
||||
}
|
||||
|
||||
// SpendEvent encapsulates a spentness notification. Its only field 'Spend' will
|
||||
// be sent upon once the target output passed into RegisterSpendNtfn has been
|
||||
// spent on the blockchain.
|
||||
//
|
||||
// NOTE: If the caller wishes to cancel their registered spend notification,
|
||||
// the Cancel closure MUST be called.
|
||||
type SpendEvent struct {
|
||||
// Spend is a receive only channel which will be sent upon once the
|
||||
// target outpoint has been spent.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
Spend chan *SpendDetail
|
||||
|
||||
// Reorg is a channel that will be sent upon once we detect the spending
|
||||
// transaction of the outpoint in question has been reorged out of the
|
||||
// chain.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
Reorg chan struct{}
|
||||
|
||||
// Done is a channel that gets sent upon once the confirmation request
|
||||
// is no longer under the risk of being reorged out of the chain.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
Done chan struct{}
|
||||
|
||||
// Cancel is a closure that should be executed by the caller in the case
|
||||
// that they wish to prematurely abandon their registered spend
|
||||
// notification.
|
||||
Cancel func()
|
||||
}
|
||||
|
||||
// NewSpendEvent constructs a new SpendEvent with newly opened channels.
|
||||
func NewSpendEvent(cancel func()) *SpendEvent {
|
||||
return &SpendEvent{
|
||||
Spend: make(chan *SpendDetail, 1),
|
||||
Reorg: make(chan struct{}, 1),
|
||||
Done: make(chan struct{}, 1),
|
||||
Cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockEpoch represents metadata concerning each new block connected to the
|
||||
// main chain.
|
||||
type BlockEpoch struct {
|
||||
// Hash is the block hash of the latest block to be added to the tip of
|
||||
// the main chain.
|
||||
Hash *chainhash.Hash
|
||||
|
||||
// Height is the height of the latest block to be added to the tip of
|
||||
// the main chain.
|
||||
Height int32
|
||||
}
|
||||
|
||||
// BlockEpochEvent encapsulates an on-going stream of block epoch
|
||||
// notifications. Its only field 'Epochs' will be sent upon for each new block
|
||||
// connected to the main-chain.
|
||||
//
|
||||
// NOTE: If the caller wishes to cancel their registered block epoch
|
||||
// notification, the Cancel closure MUST be called.
|
||||
type BlockEpochEvent struct {
|
||||
// Epochs is a receive only channel that will be sent upon each time a
|
||||
// new block is connected to the end of the main chain.
|
||||
//
|
||||
// NOTE: This channel must be buffered.
|
||||
Epochs <-chan *BlockEpoch
|
||||
|
||||
// Cancel is a closure that should be executed by the caller in the case
|
||||
// that they wish to abandon their registered block epochs notification.
|
||||
Cancel func()
|
||||
}
|
||||
|
||||
// NotifierDriver represents a "driver" for a particular interface. A driver is
|
||||
// identified by a globally unique string identifier along with a 'New()'
|
||||
// method which is responsible for initializing a particular ChainNotifier
|
||||
// concrete implementation.
|
||||
type NotifierDriver struct {
|
||||
// NotifierType is a string which uniquely identifies the ChainNotifier
|
||||
// that this driver, drives.
|
||||
NotifierType string
|
||||
|
||||
// New creates a new instance of a concrete ChainNotifier
|
||||
// implementation given a variadic set up arguments. The function takes
|
||||
// a variadic number of interface parameters in order to provide
|
||||
// initialization flexibility, thereby accommodating several potential
|
||||
// ChainNotifier implementations.
|
||||
New func(args ...interface{}) (ChainNotifier, error)
|
||||
}
|
||||
|
||||
var (
|
||||
notifiers = make(map[string]*NotifierDriver)
|
||||
registerMtx sync.Mutex
|
||||
)
|
||||
|
||||
// RegisteredNotifiers returns a slice of all currently registered notifiers.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func RegisteredNotifiers() []*NotifierDriver {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
drivers := make([]*NotifierDriver, 0, len(notifiers))
|
||||
for _, driver := range notifiers {
|
||||
drivers = append(drivers, driver)
|
||||
}
|
||||
|
||||
return drivers
|
||||
}
|
||||
|
||||
// RegisterNotifier registers a NotifierDriver which is capable of driving a
|
||||
// concrete ChainNotifier interface. In the case that this driver has already
|
||||
// been registered, an error is returned.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func RegisterNotifier(driver *NotifierDriver) error {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
if _, ok := notifiers[driver.NotifierType]; ok {
|
||||
return fmt.Errorf("notifier already registered")
|
||||
}
|
||||
|
||||
notifiers[driver.NotifierType] = driver
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportedNotifiers returns a slice of strings that represent the database
|
||||
// drivers that have been registered and are therefore supported.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func SupportedNotifiers() []string {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
supportedNotifiers := make([]string, 0, len(notifiers))
|
||||
for driverName := range notifiers {
|
||||
supportedNotifiers = append(supportedNotifiers, driverName)
|
||||
}
|
||||
|
||||
return supportedNotifiers
|
||||
}
|
||||
|
||||
// ChainConn enables notifiers to pass in their chain backend to interface
|
||||
// functions that require it.
|
||||
type ChainConn interface {
|
||||
// GetBlockHeader returns the block header for a hash.
|
||||
GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error)
|
||||
|
||||
// GetBlockHeaderVerbose returns the verbose block header for a hash.
|
||||
GetBlockHeaderVerbose(blockHash *chainhash.Hash) (
|
||||
*btcjson.GetBlockHeaderVerboseResult, error)
|
||||
|
||||
// GetBlockHash returns the hash from a block height.
|
||||
GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
|
||||
}
|
||||
|
||||
// GetCommonBlockAncestorHeight takes in:
|
||||
// (1) the hash of a block that has been reorged out of the main chain
|
||||
// (2) the hash of the block of the same height from the main chain
|
||||
// It returns the height of the nearest common ancestor between the two hashes,
|
||||
// or an error
|
||||
func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash,
|
||||
chainHash chainhash.Hash) (int32, error) {
|
||||
|
||||
for reorgHash != chainHash {
|
||||
reorgHeader, err := chainConn.GetBlockHeader(&reorgHash)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to get header for hash=%v: %v",
|
||||
reorgHash, err)
|
||||
}
|
||||
chainHeader, err := chainConn.GetBlockHeader(&chainHash)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to get header for hash=%v: %v",
|
||||
chainHash, err)
|
||||
}
|
||||
reorgHash = reorgHeader.PrevBlock
|
||||
chainHash = chainHeader.PrevBlock
|
||||
}
|
||||
|
||||
verboseHeader, err := chainConn.GetBlockHeaderVerbose(&chainHash)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to get verbose header for hash=%v: %v",
|
||||
chainHash, err)
|
||||
}
|
||||
|
||||
return verboseHeader.Height, nil
|
||||
}
|
||||
|
||||
// GetClientMissedBlocks uses a client's best block to determine what blocks
|
||||
// it missed being notified about, and returns them in a slice. Its
|
||||
// backendStoresReorgs parameter tells it whether or not the notifier's
|
||||
// chainConn stores information about blocks that have been reorged out of the
|
||||
// chain, which allows GetClientMissedBlocks to find out whether the client's
|
||||
// best block has been reorged out of the chain, rewind to the common ancestor
|
||||
// and return blocks starting right after the common ancestor.
|
||||
func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch,
|
||||
notifierBestHeight int32, backendStoresReorgs bool) ([]BlockEpoch, error) {
|
||||
|
||||
startingHeight := clientBestBlock.Height
|
||||
if backendStoresReorgs {
|
||||
// If a reorg causes the client's best hash to be incorrect,
|
||||
// retrieve the closest common ancestor and dispatch
|
||||
// notifications from there.
|
||||
hashAtBestHeight, err := chainConn.GetBlockHash(
|
||||
int64(clientBestBlock.Height))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find blockhash for "+
|
||||
"height=%d: %v", clientBestBlock.Height, err)
|
||||
}
|
||||
|
||||
startingHeight, err = GetCommonBlockAncestorHeight(
|
||||
chainConn, *clientBestBlock.Hash, *hashAtBestHeight,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find common ancestor: "+
|
||||
"%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We want to start dispatching historical notifications from the block
|
||||
// right after the client's best block, to avoid a redundant notification.
|
||||
missedBlocks, err := getMissedBlocks(
|
||||
chainConn, startingHeight+1, notifierBestHeight+1,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get missed blocks: %v", err)
|
||||
}
|
||||
|
||||
return missedBlocks, nil
|
||||
}
|
||||
|
||||
// RewindChain handles internal state updates for the notifier's TxNotifier It
|
||||
// has no effect if given a height greater than or equal to our current best
|
||||
// known height. It returns the new best block for the notifier.
|
||||
func RewindChain(chainConn ChainConn, txNotifier *TxNotifier,
|
||||
currBestBlock BlockEpoch, targetHeight int32) (BlockEpoch, error) {
|
||||
|
||||
newBestBlock := BlockEpoch{
|
||||
Height: currBestBlock.Height,
|
||||
Hash: currBestBlock.Hash,
|
||||
}
|
||||
|
||||
for height := currBestBlock.Height; height > targetHeight; height-- {
|
||||
hash, err := chainConn.GetBlockHash(int64(height - 1))
|
||||
if err != nil {
|
||||
return newBestBlock, fmt.Errorf("unable to "+
|
||||
"find blockhash for disconnected height=%d: %v",
|
||||
height, err)
|
||||
}
|
||||
|
||||
Log.Infof("Block disconnected from main chain: "+
|
||||
"height=%v, sha=%v", height, newBestBlock.Hash)
|
||||
|
||||
err = txNotifier.DisconnectTip(uint32(height))
|
||||
if err != nil {
|
||||
return newBestBlock, fmt.Errorf("unable to "+
|
||||
" disconnect tip for height=%d: %v",
|
||||
height, err)
|
||||
}
|
||||
newBestBlock.Height = height - 1
|
||||
newBestBlock.Hash = hash
|
||||
}
|
||||
return newBestBlock, nil
|
||||
}
|
||||
|
||||
// HandleMissedBlocks is called when the chain backend for a notifier misses a
|
||||
// series of blocks, handling a reorg if necessary. Its backendStoresReorgs
|
||||
// parameter tells it whether or not the notifier's chainConn stores
|
||||
// information about blocks that have been reorged out of the chain, which allows
|
||||
// HandleMissedBlocks to check whether the notifier's best block has been
|
||||
// reorged out, and rewind the chain accordingly. It returns the best block for
|
||||
// the notifier and a slice of the missed blocks. The new best block needs to be
|
||||
// returned in case a chain rewind occurs and partially completes before
|
||||
// erroring. In the case where there is no rewind, the notifier's
|
||||
// current best block is returned.
|
||||
func HandleMissedBlocks(chainConn ChainConn, txNotifier *TxNotifier,
|
||||
currBestBlock BlockEpoch, newHeight int32,
|
||||
backendStoresReorgs bool) (BlockEpoch, []BlockEpoch, error) {
|
||||
|
||||
startingHeight := currBestBlock.Height
|
||||
|
||||
if backendStoresReorgs {
|
||||
// If a reorg causes our best hash to be incorrect, rewind the
|
||||
// chain so our best block is set to the closest common
|
||||
// ancestor, then dispatch notifications from there.
|
||||
hashAtBestHeight, err :=
|
||||
chainConn.GetBlockHash(int64(currBestBlock.Height))
|
||||
if err != nil {
|
||||
return currBestBlock, nil, fmt.Errorf("unable to find "+
|
||||
"blockhash for height=%d: %v",
|
||||
currBestBlock.Height, err)
|
||||
}
|
||||
|
||||
startingHeight, err = GetCommonBlockAncestorHeight(
|
||||
chainConn, *currBestBlock.Hash, *hashAtBestHeight,
|
||||
)
|
||||
if err != nil {
|
||||
return currBestBlock, nil, fmt.Errorf("unable to find "+
|
||||
"common ancestor: %v", err)
|
||||
}
|
||||
|
||||
currBestBlock, err = RewindChain(chainConn, txNotifier,
|
||||
currBestBlock, startingHeight)
|
||||
if err != nil {
|
||||
return currBestBlock, nil, fmt.Errorf("unable to "+
|
||||
"rewind chain: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// We want to start dispatching historical notifications from the block
|
||||
// right after our best block, to avoid a redundant notification.
|
||||
missedBlocks, err := getMissedBlocks(chainConn, startingHeight+1, newHeight)
|
||||
if err != nil {
|
||||
return currBestBlock, nil, fmt.Errorf("unable to get missed "+
|
||||
"blocks: %v", err)
|
||||
}
|
||||
|
||||
return currBestBlock, missedBlocks, nil
|
||||
}
|
||||
|
||||
// getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight)
|
||||
// fetched from the chain.
|
||||
func getMissedBlocks(chainConn ChainConn, startingHeight,
|
||||
endingHeight int32) ([]BlockEpoch, error) {
|
||||
|
||||
numMissedBlocks := endingHeight - startingHeight
|
||||
if numMissedBlocks < 0 {
|
||||
return nil, fmt.Errorf("starting height %d is greater than "+
|
||||
"ending height %d", startingHeight, endingHeight)
|
||||
}
|
||||
|
||||
missedBlocks := make([]BlockEpoch, 0, numMissedBlocks)
|
||||
for height := startingHeight; height < endingHeight; height++ {
|
||||
hash, err := chainConn.GetBlockHash(int64(height))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find blockhash for "+
|
||||
"height=%d: %v", height, err)
|
||||
}
|
||||
missedBlocks = append(missedBlocks,
|
||||
BlockEpoch{Hash: hash, Height: height})
|
||||
}
|
||||
|
||||
return missedBlocks, nil
|
||||
}
|
||||
|
||||
// TxIndexConn abstracts an RPC backend with txindex enabled.
|
||||
type TxIndexConn interface {
|
||||
// GetRawTransactionVerbose returns the transaction identified by the
|
||||
// passed chain hash, and returns additional information such as the
|
||||
// block that the transaction confirmed.
|
||||
GetRawTransactionVerbose(*chainhash.Hash) (*btcjson.TxRawResult, error)
|
||||
|
||||
// GetBlockVerbose returns the block identified by the chain hash along
|
||||
// with additional information such as the block's height in the chain.
|
||||
GetBlockVerbose(*chainhash.Hash) (*btcjson.GetBlockVerboseResult, error)
|
||||
}
|
||||
|
||||
// ConfDetailsFromTxIndex looks up whether a transaction is already included in
|
||||
// a block in the active chain by using the backend node's transaction index.
|
||||
// If the transaction is found its TxConfStatus is returned. If it was found in
|
||||
// the mempool this will be TxFoundMempool, if it is found in a block this will
|
||||
// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found
|
||||
// in a block its confirmation details are also returned.
|
||||
func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
|
||||
txNotFoundErr string) (*TxConfirmation, TxConfStatus, error) {
|
||||
|
||||
// If the transaction has some or all of its confirmations required,
|
||||
// then we may be able to dispatch it immediately.
|
||||
rawTxRes, err := chainConn.GetRawTransactionVerbose(&r.TxID)
|
||||
if err != nil {
|
||||
// If the transaction lookup was successful, but it wasn't found
|
||||
// within the index itself, then we can exit early. We'll also
|
||||
// need to look at the error message returned as the error code
|
||||
// is used for multiple errors.
|
||||
jsonErr, ok := err.(*btcjson.RPCError)
|
||||
if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo &&
|
||||
strings.Contains(jsonErr.Message, txNotFoundErr) {
|
||||
|
||||
return nil, TxNotFoundIndex, nil
|
||||
}
|
||||
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to query for txid %v: %v",
|
||||
r.TxID, err)
|
||||
}
|
||||
|
||||
// Deserialize the hex-encoded transaction to include it in the
|
||||
// confirmation details.
|
||||
rawTx, err := hex.DecodeString(rawTxRes.Hex)
|
||||
if err != nil {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to deserialize tx %v: %v",
|
||||
r.TxID, err)
|
||||
}
|
||||
var tx wire.MsgTx
|
||||
if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to deserialize tx %v: %v",
|
||||
r.TxID, err)
|
||||
}
|
||||
|
||||
// Ensure the transaction matches our confirmation request in terms of
|
||||
// txid and pkscript.
|
||||
if !r.MatchesTx(&tx) {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to locate tx %v", r.TxID)
|
||||
}
|
||||
|
||||
// Make sure we actually retrieved a transaction that is included in a
|
||||
// block. If not, the transaction must be unconfirmed (in the mempool),
|
||||
// and we'll return TxFoundMempool together with a nil TxConfirmation.
|
||||
if rawTxRes.BlockHash == "" {
|
||||
return nil, TxFoundMempool, nil
|
||||
}
|
||||
|
||||
// As we need to fully populate the returned TxConfirmation struct,
|
||||
// grab the block in which the transaction was confirmed so we can
|
||||
// locate its exact index within the block.
|
||||
blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
|
||||
if err != nil {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to get block hash %v for "+
|
||||
"historical dispatch: %v", rawTxRes.BlockHash, err)
|
||||
}
|
||||
block, err := chainConn.GetBlockVerbose(blockHash)
|
||||
if err != nil {
|
||||
return nil, TxNotFoundIndex,
|
||||
fmt.Errorf("unable to get block with hash %v for "+
|
||||
"historical dispatch: %v", blockHash, err)
|
||||
}
|
||||
|
||||
// If the block was obtained, locate the transaction's index within the
|
||||
// block so we can give the subscriber full confirmation details.
|
||||
txidStr := r.TxID.String()
|
||||
for txIndex, txHash := range block.Tx {
|
||||
if txHash != txidStr {
|
||||
continue
|
||||
}
|
||||
|
||||
return &TxConfirmation{
|
||||
Tx: &tx,
|
||||
BlockHash: blockHash,
|
||||
BlockHeight: uint32(block.Height),
|
||||
TxIndex: uint32(txIndex),
|
||||
}, TxFoundIndex, nil
|
||||
}
|
||||
|
||||
// We return an error because we should have found the transaction
|
||||
// within the block, but didn't.
|
||||
return nil, TxNotFoundIndex, fmt.Errorf("unable to locate "+
|
||||
"tx %v in block %v", r.TxID, blockHash)
|
||||
}
|
||||
15
vendor/github.com/lightningnetwork/lnd/chainntnfs/interface_dev.go
generated
vendored
Normal file
15
vendor/github.com/lightningnetwork/lnd/chainntnfs/interface_dev.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build dev
|
||||
|
||||
package chainntnfs
|
||||
|
||||
import "github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
||||
// TestChainNotifier enables the use of methods that are only present during
|
||||
// testing for ChainNotifiers.
|
||||
type TestChainNotifier interface {
|
||||
ChainNotifier
|
||||
|
||||
// UnsafeStart enables notifiers to start up with a specific best block.
|
||||
// Used for testing.
|
||||
UnsafeStart(int32, *chainhash.Hash, int32, func() error) error
|
||||
}
|
||||
29
vendor/github.com/lightningnetwork/lnd/chainntnfs/log.go
generated
vendored
Normal file
29
vendor/github.com/lightningnetwork/lnd/chainntnfs/log.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package chainntnfs
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// Log is a logger that is initialized with no output filters. This
|
||||
// means the package will not perform any logging by default until the caller
|
||||
// requests it.
|
||||
var Log btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger("NTFN", nil))
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until UseLogger is called.
|
||||
func DisableLog() {
|
||||
UseLogger(btclog.Disabled)
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info.
|
||||
// This should be used in preference to SetLogWriter if the caller is also
|
||||
// using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
Log = logger
|
||||
}
|
||||
304
vendor/github.com/lightningnetwork/lnd/chainntnfs/test_utils.go
generated
vendored
Normal file
304
vendor/github.com/lightningnetwork/lnd/chainntnfs/test_utils.go
generated
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
// +build dev
|
||||
|
||||
package chainntnfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/integration/rpctest"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/chain"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/lightninglabs/neutrino"
|
||||
)
|
||||
|
||||
var (
|
||||
// TrickleInterval is the interval at which the miner should trickle
|
||||
// transactions to its peers. We'll set it small to ensure the miner
|
||||
// propagates transactions quickly in the tests.
|
||||
TrickleInterval = 10 * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
NetParams = &chaincfg.RegressionNetParams
|
||||
)
|
||||
|
||||
// randPubKeyHashScript generates a P2PKH script that pays to the public key of
|
||||
// a randomly-generated private key.
|
||||
func randPubKeyHashScript() ([]byte, *btcec.PrivateKey, error) {
|
||||
privKey, err := btcec.NewPrivateKey(btcec.S256())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pubKeyHash := btcutil.Hash160(privKey.PubKey().SerializeCompressed())
|
||||
addrScript, err := btcutil.NewAddressPubKeyHash(pubKeyHash, NetParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkScript, err := txscript.PayToAddrScript(addrScript)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return pkScript, privKey, nil
|
||||
}
|
||||
|
||||
// GetTestTxidAndScript generate a new test transaction and returns its txid and
|
||||
// the script of the output being generated.
|
||||
func GetTestTxidAndScript(h *rpctest.Harness) (*chainhash.Hash, []byte, error) {
|
||||
pkScript, _, err := randPubKeyHashScript()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to generate pkScript: %v", err)
|
||||
}
|
||||
output := &wire.TxOut{Value: 2e8, PkScript: pkScript}
|
||||
txid, err := h.SendOutputs([]*wire.TxOut{output}, 10)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return txid, pkScript, nil
|
||||
}
|
||||
|
||||
// WaitForMempoolTx waits for the txid to be seen in the miner's mempool.
|
||||
func WaitForMempoolTx(miner *rpctest.Harness, txid *chainhash.Hash) error {
|
||||
timeout := time.After(10 * time.Second)
|
||||
trickle := time.After(2 * TrickleInterval)
|
||||
for {
|
||||
// Check for the harness' knowledge of the txid.
|
||||
tx, err := miner.Node.GetRawTransaction(txid)
|
||||
if err != nil {
|
||||
jsonErr, ok := err.(*btcjson.RPCError)
|
||||
if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if tx != nil && tx.Hash().IsEqual(txid) {
|
||||
break
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-timeout:
|
||||
return errors.New("timed out waiting for tx")
|
||||
}
|
||||
}
|
||||
|
||||
// To ensure any transactions propagate from the miner to the peers
|
||||
// before returning, ensure we have waited for at least
|
||||
// 2*trickleInterval before returning.
|
||||
select {
|
||||
case <-trickle:
|
||||
case <-timeout:
|
||||
return errors.New("timeout waiting for trickle interval. " +
|
||||
"Trickle interval to large?")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSpendableOutput creates and returns an output that can be spent later
|
||||
// on.
|
||||
func CreateSpendableOutput(t *testing.T,
|
||||
miner *rpctest.Harness) (*wire.OutPoint, *wire.TxOut, *btcec.PrivateKey) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
// Create a transaction that only has one output, the one destined for
|
||||
// the recipient.
|
||||
pkScript, privKey, err := randPubKeyHashScript()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate pkScript: %v", err)
|
||||
}
|
||||
output := &wire.TxOut{Value: 2e8, PkScript: pkScript}
|
||||
txid, err := miner.SendOutputsWithoutChange([]*wire.TxOut{output}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create tx: %v", err)
|
||||
}
|
||||
|
||||
// Mine the transaction to mark the output as spendable.
|
||||
if err := WaitForMempoolTx(miner, txid); err != nil {
|
||||
t.Fatalf("tx not relayed to miner: %v", err)
|
||||
}
|
||||
if _, err := miner.Node.Generate(1); err != nil {
|
||||
t.Fatalf("unable to generate single block: %v", err)
|
||||
}
|
||||
|
||||
return wire.NewOutPoint(txid, 0), output, privKey
|
||||
}
|
||||
|
||||
// CreateSpendTx creates a transaction spending the specified output.
|
||||
func CreateSpendTx(t *testing.T, prevOutPoint *wire.OutPoint,
|
||||
prevOutput *wire.TxOut, privKey *btcec.PrivateKey) *wire.MsgTx {
|
||||
|
||||
t.Helper()
|
||||
|
||||
spendingTx := wire.NewMsgTx(1)
|
||||
spendingTx.AddTxIn(&wire.TxIn{PreviousOutPoint: *prevOutPoint})
|
||||
spendingTx.AddTxOut(&wire.TxOut{Value: 1e8, PkScript: prevOutput.PkScript})
|
||||
|
||||
sigScript, err := txscript.SignatureScript(
|
||||
spendingTx, 0, prevOutput.PkScript, txscript.SigHashAll,
|
||||
privKey, true,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sign tx: %v", err)
|
||||
}
|
||||
spendingTx.TxIn[0].SignatureScript = sigScript
|
||||
|
||||
return spendingTx
|
||||
}
|
||||
|
||||
// NewMiner spawns testing harness backed by a btcd node that can serve as a
|
||||
// miner.
|
||||
func NewMiner(t *testing.T, extraArgs []string, createChain bool,
|
||||
spendableOutputs uint32) (*rpctest.Harness, func()) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
// Add the trickle interval argument to the extra args.
|
||||
trickle := fmt.Sprintf("--trickleinterval=%v", TrickleInterval)
|
||||
extraArgs = append(extraArgs, trickle)
|
||||
|
||||
node, err := rpctest.New(NetParams, nil, extraArgs)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create backend node: %v", err)
|
||||
}
|
||||
if err := node.SetUp(createChain, spendableOutputs); err != nil {
|
||||
node.TearDown()
|
||||
t.Fatalf("unable to set up backend node: %v", err)
|
||||
}
|
||||
|
||||
return node, func() { node.TearDown() }
|
||||
}
|
||||
|
||||
// NewBitcoindBackend spawns a new bitcoind node that connects to a miner at the
|
||||
// specified address. The txindex boolean can be set to determine whether the
|
||||
// backend node should maintain a transaction index. A connection to the newly
|
||||
// spawned bitcoind node is returned.
|
||||
func NewBitcoindBackend(t *testing.T, minerAddr string,
|
||||
txindex bool) (*chain.BitcoindConn, func()) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
tempBitcoindDir, err := ioutil.TempDir("", "bitcoind")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
rpcPort := rand.Intn(65536-1024) + 1024
|
||||
zmqBlockHost := "ipc:///" + tempBitcoindDir + "/blocks.socket"
|
||||
zmqTxHost := "ipc:///" + tempBitcoindDir + "/tx.socket"
|
||||
|
||||
args := []string{
|
||||
"-connect=" + minerAddr,
|
||||
"-datadir=" + tempBitcoindDir,
|
||||
"-regtest",
|
||||
"-rpcauth=weks:469e9bb14ab2360f8e226efed5ca6fd$507c670e800a952" +
|
||||
"84294edb5773b05544b220110063096c221be9933c82d38e1",
|
||||
fmt.Sprintf("-rpcport=%d", rpcPort),
|
||||
"-disablewallet",
|
||||
"-zmqpubrawblock=" + zmqBlockHost,
|
||||
"-zmqpubrawtx=" + zmqTxHost,
|
||||
}
|
||||
if txindex {
|
||||
args = append(args, "-txindex")
|
||||
}
|
||||
|
||||
bitcoind := exec.Command("bitcoind", args...)
|
||||
if err := bitcoind.Start(); err != nil {
|
||||
os.RemoveAll(tempBitcoindDir)
|
||||
t.Fatalf("unable to start bitcoind: %v", err)
|
||||
}
|
||||
|
||||
// Wait for the bitcoind instance to start up.
|
||||
time.Sleep(time.Second)
|
||||
|
||||
host := fmt.Sprintf("127.0.0.1:%d", rpcPort)
|
||||
conn, err := chain.NewBitcoindConn(
|
||||
NetParams, host, "weks", "weks", zmqBlockHost, zmqTxHost,
|
||||
100*time.Millisecond,
|
||||
)
|
||||
if err != nil {
|
||||
bitcoind.Process.Kill()
|
||||
bitcoind.Wait()
|
||||
os.RemoveAll(tempBitcoindDir)
|
||||
t.Fatalf("unable to establish connection to bitcoind: %v", err)
|
||||
}
|
||||
if err := conn.Start(); err != nil {
|
||||
bitcoind.Process.Kill()
|
||||
bitcoind.Wait()
|
||||
os.RemoveAll(tempBitcoindDir)
|
||||
t.Fatalf("unable to establish connection to bitcoind: %v", err)
|
||||
}
|
||||
|
||||
return conn, func() {
|
||||
conn.Stop()
|
||||
bitcoind.Process.Kill()
|
||||
bitcoind.Wait()
|
||||
os.RemoveAll(tempBitcoindDir)
|
||||
}
|
||||
}
|
||||
|
||||
// NewNeutrinoBackend spawns a new neutrino node that connects to a miner at
|
||||
// the specified address.
|
||||
func NewNeutrinoBackend(t *testing.T, minerAddr string) (*neutrino.ChainService, func()) {
|
||||
t.Helper()
|
||||
|
||||
spvDir, err := ioutil.TempDir("", "neutrino")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
|
||||
dbName := filepath.Join(spvDir, "neutrino.db")
|
||||
spvDatabase, err := walletdb.Create("bdb", dbName, true)
|
||||
if err != nil {
|
||||
os.RemoveAll(spvDir)
|
||||
t.Fatalf("unable to create walletdb: %v", err)
|
||||
}
|
||||
|
||||
// Create an instance of neutrino connected to the running btcd
|
||||
// instance.
|
||||
spvConfig := neutrino.Config{
|
||||
DataDir: spvDir,
|
||||
Database: spvDatabase,
|
||||
ChainParams: *NetParams,
|
||||
ConnectPeers: []string{minerAddr},
|
||||
}
|
||||
spvNode, err := neutrino.NewChainService(spvConfig)
|
||||
if err != nil {
|
||||
os.RemoveAll(spvDir)
|
||||
spvDatabase.Close()
|
||||
t.Fatalf("unable to create neutrino: %v", err)
|
||||
}
|
||||
|
||||
// We'll also wait for the instance to sync up fully to the chain
|
||||
// generated by the btcd instance.
|
||||
spvNode.Start()
|
||||
for !spvNode.IsCurrent() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
return spvNode, func() {
|
||||
spvNode.Stop()
|
||||
spvDatabase.Close()
|
||||
os.RemoveAll(spvDir)
|
||||
}
|
||||
}
|
||||
1963
vendor/github.com/lightningnetwork/lnd/chainntnfs/txnotifier.go
generated
vendored
Normal file
1963
vendor/github.com/lightningnetwork/lnd/chainntnfs/txnotifier.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user