mirror of
https://github.com/muun/recovery.git
synced 2025-11-11 06:20:16 -05:00
Release v0.1.0
This commit is contained in:
16
vendor/github.com/btcsuite/btcwallet/LICENSE
generated
vendored
Normal file
16
vendor/github.com/btcsuite/btcwallet/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2013-2017 The btcsuite developers
|
||||
Copyright (c) 2015-2016 The Decred developers
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
1341
vendor/github.com/btcsuite/btcwallet/chain/bitcoind_client.go
generated
vendored
Normal file
1341
vendor/github.com/btcsuite/btcwallet/chain/bitcoind_client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
397
vendor/github.com/btcsuite/btcwallet/chain/bitcoind_conn.go
generated
vendored
Normal file
397
vendor/github.com/btcsuite/btcwallet/chain/bitcoind_conn.go
generated
vendored
Normal file
@@ -0,0 +1,397 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightninglabs/gozmq"
|
||||
)
|
||||
|
||||
// BitcoindConn represents a persistent client connection to a bitcoind node
|
||||
// that listens for events read from a ZMQ connection.
|
||||
type BitcoindConn struct {
|
||||
started int32 // To be used atomically.
|
||||
stopped int32 // To be used atomically.
|
||||
|
||||
// rescanClientCounter is an atomic counter that assigns a unique ID to
|
||||
// each new bitcoind rescan client using the current bitcoind
|
||||
// connection.
|
||||
rescanClientCounter uint64
|
||||
|
||||
// chainParams identifies the current network the bitcoind node is
|
||||
// running on.
|
||||
chainParams *chaincfg.Params
|
||||
|
||||
// client is the RPC client to the bitcoind node.
|
||||
client *rpcclient.Client
|
||||
|
||||
// zmqBlockConn is the ZMQ connection we'll use to read raw block
|
||||
// events.
|
||||
zmqBlockConn *gozmq.Conn
|
||||
|
||||
// zmqTxConn is the ZMQ connection we'll use to read raw transaction
|
||||
// events.
|
||||
zmqTxConn *gozmq.Conn
|
||||
|
||||
// rescanClients is the set of active bitcoind rescan clients to which
|
||||
// ZMQ event notfications will be sent to.
|
||||
rescanClientsMtx sync.Mutex
|
||||
rescanClients map[uint64]*BitcoindClient
|
||||
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewBitcoindConn creates a client connection to the node described by the host
|
||||
// string. The ZMQ connections are established immediately to ensure liveness.
|
||||
// If the remote node does not operate on the same bitcoin network as described
|
||||
// by the passed chain parameters, the connection will be disconnected.
|
||||
func NewBitcoindConn(chainParams *chaincfg.Params,
|
||||
host, user, pass, zmqBlockHost, zmqTxHost string,
|
||||
zmqPollInterval time.Duration) (*BitcoindConn, error) {
|
||||
|
||||
clientCfg := &rpcclient.ConnConfig{
|
||||
Host: host,
|
||||
User: user,
|
||||
Pass: pass,
|
||||
DisableAutoReconnect: false,
|
||||
DisableConnectOnNew: true,
|
||||
DisableTLS: true,
|
||||
HTTPPostMode: true,
|
||||
}
|
||||
|
||||
client, err := rpcclient.New(clientCfg, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Establish two different ZMQ connections to bitcoind to retrieve block
|
||||
// and transaction event notifications. We'll use two as a separation of
|
||||
// concern to ensure one type of event isn't dropped from the connection
|
||||
// queue due to another type of event filling it up.
|
||||
zmqBlockConn, err := gozmq.Subscribe(
|
||||
zmqBlockHost, []string{"rawblock"}, zmqPollInterval,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to subscribe for zmq block "+
|
||||
"events: %v", err)
|
||||
}
|
||||
|
||||
zmqTxConn, err := gozmq.Subscribe(
|
||||
zmqTxHost, []string{"rawtx"}, zmqPollInterval,
|
||||
)
|
||||
if err != nil {
|
||||
zmqBlockConn.Close()
|
||||
return nil, fmt.Errorf("unable to subscribe for zmq tx "+
|
||||
"events: %v", err)
|
||||
}
|
||||
|
||||
conn := &BitcoindConn{
|
||||
chainParams: chainParams,
|
||||
client: client,
|
||||
zmqBlockConn: zmqBlockConn,
|
||||
zmqTxConn: zmqTxConn,
|
||||
rescanClients: make(map[uint64]*BitcoindClient),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Start attempts to establish a RPC and ZMQ connection to a bitcoind node. If
|
||||
// successful, a goroutine is spawned to read events from the ZMQ connection.
|
||||
// It's possible for this function to fail due to a limited number of connection
|
||||
// attempts. This is done to prevent waiting forever on the connection to be
|
||||
// established in the case that the node is down.
|
||||
func (c *BitcoindConn) Start() error {
|
||||
if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify that the node is running on the expected network.
|
||||
net, err := c.getCurrentNet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if net != c.chainParams.Net {
|
||||
return fmt.Errorf("expected network %v, got %v",
|
||||
c.chainParams.Net, net)
|
||||
}
|
||||
|
||||
c.wg.Add(2)
|
||||
go c.blockEventHandler()
|
||||
go c.txEventHandler()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop terminates the RPC and ZMQ connection to a bitcoind node and removes any
|
||||
// active rescan clients.
|
||||
func (c *BitcoindConn) Stop() {
|
||||
if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, client := range c.rescanClients {
|
||||
client.Stop()
|
||||
}
|
||||
|
||||
close(c.quit)
|
||||
c.client.Shutdown()
|
||||
c.zmqBlockConn.Close()
|
||||
c.zmqTxConn.Close()
|
||||
|
||||
c.client.WaitForShutdown()
|
||||
c.wg.Wait()
|
||||
}
|
||||
|
||||
// blockEventHandler reads raw blocks events from the ZMQ block socket and
|
||||
// forwards them along to the current rescan clients.
|
||||
//
|
||||
// NOTE: This must be run as a goroutine.
|
||||
func (c *BitcoindConn) blockEventHandler() {
|
||||
defer c.wg.Done()
|
||||
|
||||
log.Info("Started listening for bitcoind block notifications via ZMQ "+
|
||||
"on", c.zmqBlockConn.RemoteAddr())
|
||||
|
||||
for {
|
||||
// Before attempting to read from the ZMQ socket, we'll make
|
||||
// sure to check if we've been requested to shut down.
|
||||
select {
|
||||
case <-c.quit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// Poll an event from the ZMQ socket.
|
||||
msgBytes, err := c.zmqBlockConn.Receive()
|
||||
if err != nil {
|
||||
// EOF should only be returned if the connection was
|
||||
// explicitly closed, so we can exit at this point.
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
|
||||
// It's possible that the connection to the socket
|
||||
// continuously times out, so we'll prevent logging this
|
||||
// error to prevent spamming the logs.
|
||||
netErr, ok := err.(net.Error)
|
||||
if ok && netErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Errorf("Unable to receive ZMQ rawblock message: %v",
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We have an event! We'll now ensure it is a block event,
|
||||
// deserialize it, and report it to the different rescan
|
||||
// clients.
|
||||
eventType := string(msgBytes[0])
|
||||
switch eventType {
|
||||
case "rawblock":
|
||||
block := &wire.MsgBlock{}
|
||||
r := bytes.NewReader(msgBytes[1])
|
||||
if err := block.Deserialize(r); err != nil {
|
||||
log.Errorf("Unable to deserialize block: %v",
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
c.rescanClientsMtx.Lock()
|
||||
for _, client := range c.rescanClients {
|
||||
select {
|
||||
case client.zmqBlockNtfns <- block:
|
||||
case <-client.quit:
|
||||
case <-c.quit:
|
||||
c.rescanClientsMtx.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.rescanClientsMtx.Unlock()
|
||||
default:
|
||||
// It's possible that the message wasn't fully read if
|
||||
// bitcoind shuts down, which will produce an unreadable
|
||||
// event type. To prevent from logging it, we'll make
|
||||
// sure it conforms to the ASCII standard.
|
||||
if eventType == "" || !isASCII(eventType) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Warnf("Received unexpected event type from "+
|
||||
"rawblock subscription: %v", eventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// txEventHandler reads raw blocks events from the ZMQ block socket and forwards
|
||||
// them along to the current rescan clients.
|
||||
//
|
||||
// NOTE: This must be run as a goroutine.
|
||||
func (c *BitcoindConn) txEventHandler() {
|
||||
defer c.wg.Done()
|
||||
|
||||
log.Info("Started listening for bitcoind transaction notifications "+
|
||||
"via ZMQ on", c.zmqTxConn.RemoteAddr())
|
||||
|
||||
for {
|
||||
// Before attempting to read from the ZMQ socket, we'll make
|
||||
// sure to check if we've been requested to shut down.
|
||||
select {
|
||||
case <-c.quit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// Poll an event from the ZMQ socket.
|
||||
msgBytes, err := c.zmqTxConn.Receive()
|
||||
if err != nil {
|
||||
// EOF should only be returned if the connection was
|
||||
// explicitly closed, so we can exit at this point.
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
|
||||
// It's possible that the connection to the socket
|
||||
// continuously times out, so we'll prevent logging this
|
||||
// error to prevent spamming the logs.
|
||||
netErr, ok := err.(net.Error)
|
||||
if ok && netErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Errorf("Unable to receive ZMQ rawtx message: %v",
|
||||
err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We have an event! We'll now ensure it is a transaction event,
|
||||
// deserialize it, and report it to the different rescan
|
||||
// clients.
|
||||
eventType := string(msgBytes[0])
|
||||
switch eventType {
|
||||
case "rawtx":
|
||||
tx := &wire.MsgTx{}
|
||||
r := bytes.NewReader(msgBytes[1])
|
||||
if err := tx.Deserialize(r); err != nil {
|
||||
log.Errorf("Unable to deserialize "+
|
||||
"transaction: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
c.rescanClientsMtx.Lock()
|
||||
for _, client := range c.rescanClients {
|
||||
select {
|
||||
case client.zmqTxNtfns <- tx:
|
||||
case <-client.quit:
|
||||
case <-c.quit:
|
||||
c.rescanClientsMtx.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.rescanClientsMtx.Unlock()
|
||||
default:
|
||||
// It's possible that the message wasn't fully read if
|
||||
// bitcoind shuts down, which will produce an unreadable
|
||||
// event type. To prevent from logging it, we'll make
|
||||
// sure it conforms to the ASCII standard.
|
||||
if eventType == "" || !isASCII(eventType) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Warnf("Received unexpected event type from rawtx "+
|
||||
"subscription: %v", eventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getCurrentNet returns the network on which the bitcoind node is running.
|
||||
func (c *BitcoindConn) getCurrentNet() (wire.BitcoinNet, error) {
|
||||
hash, err := c.client.GetBlockHash(0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch *hash {
|
||||
case *chaincfg.TestNet3Params.GenesisHash:
|
||||
return chaincfg.TestNet3Params.Net, nil
|
||||
case *chaincfg.RegressionNetParams.GenesisHash:
|
||||
return chaincfg.RegressionNetParams.Net, nil
|
||||
case *chaincfg.MainNetParams.GenesisHash:
|
||||
return chaincfg.MainNetParams.Net, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown network with genesis hash %v", hash)
|
||||
}
|
||||
}
|
||||
|
||||
// NewBitcoindClient returns a bitcoind client using the current bitcoind
|
||||
// connection. This allows us to share the same connection using multiple
|
||||
// clients.
|
||||
func (c *BitcoindConn) NewBitcoindClient() *BitcoindClient {
|
||||
return &BitcoindClient{
|
||||
quit: make(chan struct{}),
|
||||
|
||||
id: atomic.AddUint64(&c.rescanClientCounter, 1),
|
||||
|
||||
chainParams: c.chainParams,
|
||||
chainConn: c,
|
||||
|
||||
rescanUpdate: make(chan interface{}),
|
||||
watchedAddresses: make(map[string]struct{}),
|
||||
watchedOutPoints: make(map[wire.OutPoint]struct{}),
|
||||
watchedTxs: make(map[chainhash.Hash]struct{}),
|
||||
|
||||
notificationQueue: NewConcurrentQueue(20),
|
||||
zmqTxNtfns: make(chan *wire.MsgTx),
|
||||
zmqBlockNtfns: make(chan *wire.MsgBlock),
|
||||
|
||||
mempool: make(map[chainhash.Hash]struct{}),
|
||||
expiredMempool: make(map[int32]map[chainhash.Hash]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// AddClient adds a client to the set of active rescan clients of the current
|
||||
// chain connection. This allows the connection to include the specified client
|
||||
// in its notification delivery.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func (c *BitcoindConn) AddClient(client *BitcoindClient) {
|
||||
c.rescanClientsMtx.Lock()
|
||||
defer c.rescanClientsMtx.Unlock()
|
||||
|
||||
c.rescanClients[client.id] = client
|
||||
}
|
||||
|
||||
// RemoveClient removes the client with the given ID from the set of active
|
||||
// rescan clients. Once removed, the client will no longer receive block and
|
||||
// transaction notifications from the chain connection.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func (c *BitcoindConn) RemoveClient(id uint64) {
|
||||
c.rescanClientsMtx.Lock()
|
||||
defer c.rescanClientsMtx.Unlock()
|
||||
|
||||
delete(c.rescanClients, id)
|
||||
}
|
||||
|
||||
// isASCII is a helper method that checks whether all bytes in `data` would be
|
||||
// printable ASCII characters if interpreted as a string.
|
||||
func isASCII(s string) bool {
|
||||
for _, c := range s {
|
||||
if c < 32 || c > 126 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
217
vendor/github.com/btcsuite/btcwallet/chain/block_filterer.go
generated
vendored
Normal file
217
vendor/github.com/btcsuite/btcwallet/chain/block_filterer.go
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
)
|
||||
|
||||
// BlockFilterer is used to iteratively scan blocks for a set of addresses of
|
||||
// interest. This is done by constructing reverse indexes mapping the
|
||||
// addresses to a ScopedIndex, which permits the reconstruction of the exact
|
||||
// child deriviation paths that reported matches.
|
||||
//
|
||||
// Once initialized, a BlockFilterer can be used to scan any number of blocks
|
||||
// until a invocation of `FilterBlock` returns true. This allows the reverse
|
||||
// indexes to be resused in the event that the set of addresses does not need to
|
||||
// be altered. After a match is reported, a new BlockFilterer should be
|
||||
// initialized with the updated set of addresses that include any new keys that
|
||||
// are now within our look-ahead.
|
||||
//
|
||||
// We track internal and external addresses separately in order to conserve the
|
||||
// amount of space occupied in memory. Specifically, the account and branch
|
||||
// combined contribute only 1-bit of information when using the default scopes
|
||||
// used by the wallet. Thus we can avoid storing an additional 64-bits per
|
||||
// address of interest by not storing the full derivation paths, and instead
|
||||
// opting to allow the caller to contextually infer the account (DefaultAccount)
|
||||
// and branch (Internal or External).
|
||||
type BlockFilterer struct {
|
||||
// Params specifies the chain params of the current network.
|
||||
Params *chaincfg.Params
|
||||
|
||||
// ExReverseFilter holds a reverse index mapping an external address to
|
||||
// the scoped index from which it was derived.
|
||||
ExReverseFilter map[string]waddrmgr.ScopedIndex
|
||||
|
||||
// InReverseFilter holds a reverse index mapping an internal address to
|
||||
// the scoped index from which it was derived.
|
||||
InReverseFilter map[string]waddrmgr.ScopedIndex
|
||||
|
||||
// WathcedOutPoints is a global set of outpoints being tracked by the
|
||||
// wallet. This allows the block filterer to check for spends from an
|
||||
// outpoint we own.
|
||||
WatchedOutPoints map[wire.OutPoint]btcutil.Address
|
||||
|
||||
// FoundExternal is a two-layer map recording the scope and index of
|
||||
// external addresses found in a single block.
|
||||
FoundExternal map[waddrmgr.KeyScope]map[uint32]struct{}
|
||||
|
||||
// FoundInternal is a two-layer map recording the scope and index of
|
||||
// internal addresses found in a single block.
|
||||
FoundInternal map[waddrmgr.KeyScope]map[uint32]struct{}
|
||||
|
||||
// FoundOutPoints is a set of outpoints found in a single block whose
|
||||
// address belongs to the wallet.
|
||||
FoundOutPoints map[wire.OutPoint]btcutil.Address
|
||||
|
||||
// RelevantTxns records the transactions found in a particular block
|
||||
// that contained matches from an address in either ExReverseFilter or
|
||||
// InReverseFilter.
|
||||
RelevantTxns []*wire.MsgTx
|
||||
}
|
||||
|
||||
// NewBlockFilterer constructs the reverse indexes for the current set of
|
||||
// external and internal addresses that we are searching for, and is used to
|
||||
// scan successive blocks for addresses of interest. A particular block filter
|
||||
// can be reused until the first call from `FitlerBlock` returns true.
|
||||
func NewBlockFilterer(params *chaincfg.Params,
|
||||
req *FilterBlocksRequest) *BlockFilterer {
|
||||
|
||||
// Construct a reverse index by address string for the requested
|
||||
// external addresses.
|
||||
nExAddrs := len(req.ExternalAddrs)
|
||||
exReverseFilter := make(map[string]waddrmgr.ScopedIndex, nExAddrs)
|
||||
for scopedIndex, addr := range req.ExternalAddrs {
|
||||
exReverseFilter[addr.EncodeAddress()] = scopedIndex
|
||||
}
|
||||
|
||||
// Construct a reverse index by address string for the requested
|
||||
// internal addresses.
|
||||
nInAddrs := len(req.InternalAddrs)
|
||||
inReverseFilter := make(map[string]waddrmgr.ScopedIndex, nInAddrs)
|
||||
for scopedIndex, addr := range req.InternalAddrs {
|
||||
inReverseFilter[addr.EncodeAddress()] = scopedIndex
|
||||
}
|
||||
|
||||
foundExternal := make(map[waddrmgr.KeyScope]map[uint32]struct{})
|
||||
foundInternal := make(map[waddrmgr.KeyScope]map[uint32]struct{})
|
||||
foundOutPoints := make(map[wire.OutPoint]btcutil.Address)
|
||||
|
||||
return &BlockFilterer{
|
||||
Params: params,
|
||||
ExReverseFilter: exReverseFilter,
|
||||
InReverseFilter: inReverseFilter,
|
||||
WatchedOutPoints: req.WatchedOutPoints,
|
||||
FoundExternal: foundExternal,
|
||||
FoundInternal: foundInternal,
|
||||
FoundOutPoints: foundOutPoints,
|
||||
}
|
||||
}
|
||||
|
||||
// FilterBlock parses all txns in the provided block, searching for any that
|
||||
// contain addresses of interest in either the external or internal reverse
|
||||
// filters. This method return true iff the block contains a non-zero number of
|
||||
// addresses of interest, or a transaction in the block spends from outpoints
|
||||
// controlled by the wallet.
|
||||
func (bf *BlockFilterer) FilterBlock(block *wire.MsgBlock) bool {
|
||||
var hasRelevantTxns bool
|
||||
for _, tx := range block.Transactions {
|
||||
if bf.FilterTx(tx) {
|
||||
bf.RelevantTxns = append(bf.RelevantTxns, tx)
|
||||
hasRelevantTxns = true
|
||||
}
|
||||
}
|
||||
|
||||
return hasRelevantTxns
|
||||
}
|
||||
|
||||
// FilterTx scans all txouts in the provided txn, testing to see if any found
|
||||
// addresses match those contained within the external or internal reverse
|
||||
// indexes. This method returns true iff the txn contains a non-zero number of
|
||||
// addresses of interest, or the transaction spends from an outpoint that
|
||||
// belongs to the wallet.
|
||||
func (bf *BlockFilterer) FilterTx(tx *wire.MsgTx) bool {
|
||||
var isRelevant bool
|
||||
|
||||
// First, check the inputs to this transaction to see if they spend any
|
||||
// inputs belonging to the wallet. In addition to checking
|
||||
// WatchedOutPoints, we also check FoundOutPoints, in case a txn spends
|
||||
// from an outpoint created in the same block.
|
||||
for _, in := range tx.TxIn {
|
||||
if _, ok := bf.WatchedOutPoints[in.PreviousOutPoint]; ok {
|
||||
isRelevant = true
|
||||
}
|
||||
if _, ok := bf.FoundOutPoints[in.PreviousOutPoint]; ok {
|
||||
isRelevant = true
|
||||
}
|
||||
}
|
||||
|
||||
// Now, parse all of the outputs created by this transactions, and see
|
||||
// if they contain any addresses known the wallet using our reverse
|
||||
// indexes for both external and internal addresses. If a new output is
|
||||
// found, we will add the outpoint to our set of FoundOutPoints.
|
||||
for i, out := range tx.TxOut {
|
||||
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
|
||||
out.PkScript, bf.Params,
|
||||
)
|
||||
if err != nil {
|
||||
log.Warnf("Could not parse output script in %s:%d: %v",
|
||||
tx.TxHash(), i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bf.FilterOutputAddrs(addrs) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we've reached this point, then the output contains an
|
||||
// address of interest.
|
||||
isRelevant = true
|
||||
|
||||
// Record the outpoint that containing the address in our set of
|
||||
// found outpoints, so that the caller can update its global
|
||||
// set of watched outpoints.
|
||||
outPoint := wire.OutPoint{
|
||||
Hash: *btcutil.NewTx(tx).Hash(),
|
||||
Index: uint32(i),
|
||||
}
|
||||
|
||||
bf.FoundOutPoints[outPoint] = addrs[0]
|
||||
}
|
||||
|
||||
return isRelevant
|
||||
}
|
||||
|
||||
// FilterOutputAddrs tests the set of addresses against the block filterer's
|
||||
// external and internal reverse address indexes. If any are found, they are
|
||||
// added to set of external and internal found addresses, respectively. This
|
||||
// method returns true iff a non-zero number of the provided addresses are of
|
||||
// interest.
|
||||
func (bf *BlockFilterer) FilterOutputAddrs(addrs []btcutil.Address) bool {
|
||||
var isRelevant bool
|
||||
for _, addr := range addrs {
|
||||
addrStr := addr.EncodeAddress()
|
||||
if scopedIndex, ok := bf.ExReverseFilter[addrStr]; ok {
|
||||
bf.foundExternal(scopedIndex)
|
||||
isRelevant = true
|
||||
}
|
||||
if scopedIndex, ok := bf.InReverseFilter[addrStr]; ok {
|
||||
bf.foundInternal(scopedIndex)
|
||||
isRelevant = true
|
||||
}
|
||||
}
|
||||
|
||||
return isRelevant
|
||||
}
|
||||
|
||||
// foundExternal marks the scoped index as found within the block filterer's
|
||||
// FoundExternal map. If this the first index found for a particular scope, the
|
||||
// scope's second layer map will be initialized before marking the index.
|
||||
func (bf *BlockFilterer) foundExternal(scopedIndex waddrmgr.ScopedIndex) {
|
||||
if _, ok := bf.FoundExternal[scopedIndex.Scope]; !ok {
|
||||
bf.FoundExternal[scopedIndex.Scope] = make(map[uint32]struct{})
|
||||
}
|
||||
bf.FoundExternal[scopedIndex.Scope][scopedIndex.Index] = struct{}{}
|
||||
}
|
||||
|
||||
// foundInternal marks the scoped index as found within the block filterer's
|
||||
// FoundInternal map. If this the first index found for a particular scope, the
|
||||
// scope's second layer map will be initialized before marking the index.
|
||||
func (bf *BlockFilterer) foundInternal(scopedIndex waddrmgr.ScopedIndex) {
|
||||
if _, ok := bf.FoundInternal[scopedIndex.Scope]; !ok {
|
||||
bf.FoundInternal[scopedIndex.Scope] = make(map[uint32]struct{})
|
||||
}
|
||||
bf.FoundInternal[scopedIndex.Scope][scopedIndex.Index] = struct{}{}
|
||||
}
|
||||
124
vendor/github.com/btcsuite/btcwallet/chain/interface.go
generated
vendored
Normal file
124
vendor/github.com/btcsuite/btcwallet/chain/interface.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
// isCurrentDelta is the delta duration we'll use from the present time to
|
||||
// determine if a backend is considered "current", i.e. synced to the tip of
|
||||
// the chain.
|
||||
const isCurrentDelta = 2 * time.Hour
|
||||
|
||||
// BackEnds returns a list of the available back ends.
|
||||
// TODO: Refactor each into a driver and use dynamic registration.
|
||||
func BackEnds() []string {
|
||||
return []string{
|
||||
"bitcoind",
|
||||
"btcd",
|
||||
"neutrino",
|
||||
}
|
||||
}
|
||||
|
||||
// Interface allows more than one backing blockchain source, such as a
|
||||
// btcd RPC chain server, or an SPV library, as long as we write a driver for
|
||||
// it.
|
||||
type Interface interface {
|
||||
Start() error
|
||||
Stop()
|
||||
WaitForShutdown()
|
||||
GetBestBlock() (*chainhash.Hash, int32, error)
|
||||
GetBlock(*chainhash.Hash) (*wire.MsgBlock, error)
|
||||
GetBlockHash(int64) (*chainhash.Hash, error)
|
||||
GetBlockHeader(*chainhash.Hash) (*wire.BlockHeader, error)
|
||||
IsCurrent() bool
|
||||
FilterBlocks(*FilterBlocksRequest) (*FilterBlocksResponse, error)
|
||||
BlockStamp() (*waddrmgr.BlockStamp, error)
|
||||
SendRawTransaction(*wire.MsgTx, bool) (*chainhash.Hash, error)
|
||||
Rescan(*chainhash.Hash, []btcutil.Address, map[wire.OutPoint]btcutil.Address) error
|
||||
NotifyReceived([]btcutil.Address) error
|
||||
NotifyBlocks() error
|
||||
Notifications() <-chan interface{}
|
||||
BackEnd() string
|
||||
}
|
||||
|
||||
// Notification types. These are defined here and processed from from reading
|
||||
// a notificationChan to avoid handling these notifications directly in
|
||||
// rpcclient callbacks, which isn't very Go-like and doesn't allow
|
||||
// blocking client calls.
|
||||
type (
|
||||
// ClientConnected is a notification for when a client connection is
|
||||
// opened or reestablished to the chain server.
|
||||
ClientConnected struct{}
|
||||
|
||||
// BlockConnected is a notification for a newly-attached block to the
|
||||
// best chain.
|
||||
BlockConnected wtxmgr.BlockMeta
|
||||
|
||||
// FilteredBlockConnected is an alternate notification that contains
|
||||
// both block and relevant transaction information in one struct, which
|
||||
// allows atomic updates.
|
||||
FilteredBlockConnected struct {
|
||||
Block *wtxmgr.BlockMeta
|
||||
RelevantTxs []*wtxmgr.TxRecord
|
||||
}
|
||||
|
||||
// FilterBlocksRequest specifies a range of blocks and the set of
|
||||
// internal and external addresses of interest, indexed by corresponding
|
||||
// scoped-index of the child address. A global set of watched outpoints
|
||||
// is also included to monitor for spends.
|
||||
FilterBlocksRequest struct {
|
||||
Blocks []wtxmgr.BlockMeta
|
||||
ExternalAddrs map[waddrmgr.ScopedIndex]btcutil.Address
|
||||
InternalAddrs map[waddrmgr.ScopedIndex]btcutil.Address
|
||||
WatchedOutPoints map[wire.OutPoint]btcutil.Address
|
||||
}
|
||||
|
||||
// FilterBlocksResponse reports the set of all internal and external
|
||||
// addresses found in response to a FilterBlockRequest, any outpoints
|
||||
// found that correspond to those addresses, as well as the relevant
|
||||
// transactions that can modify the wallet's balance. The index of the
|
||||
// block within the FilterBlocksRequest is returned, such that the
|
||||
// caller can reinitiate a request for the subsequent block after
|
||||
// updating the addresses of interest.
|
||||
FilterBlocksResponse struct {
|
||||
BatchIndex uint32
|
||||
BlockMeta wtxmgr.BlockMeta
|
||||
FoundExternalAddrs map[waddrmgr.KeyScope]map[uint32]struct{}
|
||||
FoundInternalAddrs map[waddrmgr.KeyScope]map[uint32]struct{}
|
||||
FoundOutPoints map[wire.OutPoint]btcutil.Address
|
||||
RelevantTxns []*wire.MsgTx
|
||||
}
|
||||
|
||||
// BlockDisconnected is a notifcation that the block described by the
|
||||
// BlockStamp was reorganized out of the best chain.
|
||||
BlockDisconnected wtxmgr.BlockMeta
|
||||
|
||||
// RelevantTx is a notification for a transaction which spends wallet
|
||||
// inputs or pays to a watched address.
|
||||
RelevantTx struct {
|
||||
TxRecord *wtxmgr.TxRecord
|
||||
Block *wtxmgr.BlockMeta // nil if unmined
|
||||
}
|
||||
|
||||
// RescanProgress is a notification describing the current status
|
||||
// of an in-progress rescan.
|
||||
RescanProgress struct {
|
||||
Hash *chainhash.Hash
|
||||
Height int32
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// RescanFinished is a notification that a previous rescan request
|
||||
// has finished.
|
||||
RescanFinished struct {
|
||||
Hash *chainhash.Hash
|
||||
Height int32
|
||||
Time time.Time
|
||||
}
|
||||
)
|
||||
56
vendor/github.com/btcsuite/btcwallet/chain/log.go
generated
vendored
Normal file
56
vendor/github.com/btcsuite/btcwallet/chain/log.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2013-2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package chain
|
||||
|
||||
import "github.com/btcsuite/btclog"
|
||||
|
||||
// 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() {
|
||||
DisableLog()
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until either UseLogger or SetLogWriter are called.
|
||||
func DisableLog() {
|
||||
log = 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
|
||||
}
|
||||
|
||||
// LogClosure is a closure that can be printed with %v to be used to
|
||||
// generate expensive-to-create data for a detailed log level and avoid doing
|
||||
// the work if the data isn't printed.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the log closure and returns the results string.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over the passed function which allows
|
||||
// it to be used as a parameter in a logging function that is only invoked when
|
||||
// the logging level is such that the message will actually be logged.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
||||
|
||||
// pickNoun returns the singular or plural form of a noun depending
|
||||
// on the count n.
|
||||
func pickNoun(n int, singular, plural string) string {
|
||||
if n == 1 {
|
||||
return singular
|
||||
}
|
||||
return plural
|
||||
}
|
||||
747
vendor/github.com/btcsuite/btcwallet/chain/neutrino.go
generated
vendored
Normal file
747
vendor/github.com/btcsuite/btcwallet/chain/neutrino.go
generated
vendored
Normal file
@@ -0,0 +1,747 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/gcs"
|
||||
"github.com/btcsuite/btcutil/gcs/builder"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
"github.com/lightninglabs/neutrino"
|
||||
"github.com/lightninglabs/neutrino/headerfs"
|
||||
)
|
||||
|
||||
// NeutrinoClient is an implementation of the btcwalet chain.Interface interface.
|
||||
type NeutrinoClient struct {
|
||||
CS *neutrino.ChainService
|
||||
|
||||
chainParams *chaincfg.Params
|
||||
|
||||
// We currently support one rescan/notifiction goroutine per client
|
||||
rescan *neutrino.Rescan
|
||||
|
||||
enqueueNotification chan interface{}
|
||||
dequeueNotification chan interface{}
|
||||
startTime time.Time
|
||||
lastProgressSent bool
|
||||
lastFilteredBlockHeader *wire.BlockHeader
|
||||
currentBlock chan *waddrmgr.BlockStamp
|
||||
|
||||
quit chan struct{}
|
||||
rescanQuit chan struct{}
|
||||
rescanErr <-chan error
|
||||
wg sync.WaitGroup
|
||||
started bool
|
||||
scanning bool
|
||||
finished bool
|
||||
isRescan bool
|
||||
|
||||
clientMtx sync.Mutex
|
||||
}
|
||||
|
||||
// NewNeutrinoClient creates a new NeutrinoClient struct with a backing
|
||||
// ChainService.
|
||||
func NewNeutrinoClient(chainParams *chaincfg.Params,
|
||||
chainService *neutrino.ChainService) *NeutrinoClient {
|
||||
|
||||
return &NeutrinoClient{
|
||||
CS: chainService,
|
||||
chainParams: chainParams,
|
||||
}
|
||||
}
|
||||
|
||||
// BackEnd returns the name of the driver.
|
||||
func (s *NeutrinoClient) BackEnd() string {
|
||||
return "neutrino"
|
||||
}
|
||||
|
||||
// Start replicates the RPC client's Start method.
|
||||
func (s *NeutrinoClient) Start() error {
|
||||
s.CS.Start()
|
||||
s.clientMtx.Lock()
|
||||
defer s.clientMtx.Unlock()
|
||||
if !s.started {
|
||||
s.enqueueNotification = make(chan interface{})
|
||||
s.dequeueNotification = make(chan interface{})
|
||||
s.currentBlock = make(chan *waddrmgr.BlockStamp)
|
||||
s.quit = make(chan struct{})
|
||||
s.started = true
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
select {
|
||||
case s.enqueueNotification <- ClientConnected{}:
|
||||
case <-s.quit:
|
||||
}
|
||||
}()
|
||||
go s.notificationHandler()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop replicates the RPC client's Stop method.
|
||||
func (s *NeutrinoClient) Stop() {
|
||||
s.clientMtx.Lock()
|
||||
defer s.clientMtx.Unlock()
|
||||
if !s.started {
|
||||
return
|
||||
}
|
||||
close(s.quit)
|
||||
s.started = false
|
||||
}
|
||||
|
||||
// WaitForShutdown replicates the RPC client's WaitForShutdown method.
|
||||
func (s *NeutrinoClient) WaitForShutdown() {
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
// GetBlock replicates the RPC client's GetBlock command.
|
||||
func (s *NeutrinoClient) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) {
|
||||
// TODO(roasbeef): add a block cache?
|
||||
// * which evication strategy? depends on use case
|
||||
// Should the block cache be INSIDE neutrino instead of in btcwallet?
|
||||
block, err := s.CS.GetBlock(*hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return block.MsgBlock(), nil
|
||||
}
|
||||
|
||||
// GetBlockHeight gets the height of a block by its hash. It serves as a
|
||||
// replacement for the use of GetBlockVerboseTxAsync for the wallet package
|
||||
// since we can't actually return a FutureGetBlockVerboseResult because the
|
||||
// underlying type is private to rpcclient.
|
||||
func (s *NeutrinoClient) GetBlockHeight(hash *chainhash.Hash) (int32, error) {
|
||||
return s.CS.GetBlockHeight(hash)
|
||||
}
|
||||
|
||||
// GetBestBlock replicates the RPC client's GetBestBlock command.
|
||||
func (s *NeutrinoClient) GetBestBlock() (*chainhash.Hash, int32, error) {
|
||||
chainTip, err := s.CS.BestBlock()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return &chainTip.Hash, chainTip.Height, nil
|
||||
}
|
||||
|
||||
// BlockStamp returns the latest block notified by the client, or an error
|
||||
// if the client has been shut down.
|
||||
func (s *NeutrinoClient) BlockStamp() (*waddrmgr.BlockStamp, error) {
|
||||
select {
|
||||
case bs := <-s.currentBlock:
|
||||
return bs, nil
|
||||
case <-s.quit:
|
||||
return nil, errors.New("disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlockHash returns the block hash for the given height, or an error if the
|
||||
// client has been shut down or the hash at the block height doesn't exist or
|
||||
// is unknown.
|
||||
func (s *NeutrinoClient) GetBlockHash(height int64) (*chainhash.Hash, error) {
|
||||
return s.CS.GetBlockHash(height)
|
||||
}
|
||||
|
||||
// GetBlockHeader returns the block header for the given block hash, or an error
|
||||
// if the client has been shut down or the hash doesn't exist or is unknown.
|
||||
func (s *NeutrinoClient) GetBlockHeader(
|
||||
blockHash *chainhash.Hash) (*wire.BlockHeader, error) {
|
||||
return s.CS.GetBlockHeader(blockHash)
|
||||
}
|
||||
|
||||
// IsCurrent returns whether the chain backend considers its view of the network
|
||||
// as "current".
|
||||
func (s *NeutrinoClient) IsCurrent() bool {
|
||||
return s.CS.IsCurrent()
|
||||
}
|
||||
|
||||
// SendRawTransaction replicates the RPC client's SendRawTransaction command.
|
||||
func (s *NeutrinoClient) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (
|
||||
*chainhash.Hash, error) {
|
||||
err := s.CS.SendTransaction(tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash := tx.TxHash()
|
||||
return &hash, nil
|
||||
}
|
||||
|
||||
// FilterBlocks scans the blocks contained in the FilterBlocksRequest for any
|
||||
// addresses of interest. For each requested block, the corresponding compact
|
||||
// filter will first be checked for matches, skipping those that do not report
|
||||
// anything. If the filter returns a postive match, the full block will be
|
||||
// fetched and filtered. This method returns a FilterBlocksReponse for the first
|
||||
// block containing a matching address. If no matches are found in the range of
|
||||
// blocks requested, the returned response will be nil.
|
||||
func (s *NeutrinoClient) FilterBlocks(
|
||||
req *FilterBlocksRequest) (*FilterBlocksResponse, error) {
|
||||
|
||||
blockFilterer := NewBlockFilterer(s.chainParams, req)
|
||||
|
||||
// Construct the watchlist using the addresses and outpoints contained
|
||||
// in the filter blocks request.
|
||||
watchList, err := buildFilterBlocksWatchList(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Iterate over the requested blocks, fetching the compact filter for
|
||||
// each one, and matching it against the watchlist generated above. If
|
||||
// the filter returns a positive match, the full block is then requested
|
||||
// and scanned for addresses using the block filterer.
|
||||
for i, blk := range req.Blocks {
|
||||
filter, err := s.pollCFilter(&blk.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Skip any empty filters.
|
||||
if filter == nil || filter.N() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := builder.DeriveKey(&blk.Hash)
|
||||
matched, err := filter.MatchAny(key, watchList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Fetching block height=%d hash=%v",
|
||||
blk.Height, blk.Hash)
|
||||
|
||||
// TODO(conner): can optimize bandwidth by only fetching
|
||||
// stripped blocks
|
||||
rawBlock, err := s.GetBlock(&blk.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !blockFilterer.FilterBlock(rawBlock) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If any external or internal addresses were detected in this
|
||||
// block, we return them to the caller so that the rescan
|
||||
// windows can widened with subsequent addresses. The
|
||||
// `BatchIndex` is returned so that the caller can compute the
|
||||
// *next* block from which to begin again.
|
||||
resp := &FilterBlocksResponse{
|
||||
BatchIndex: uint32(i),
|
||||
BlockMeta: blk,
|
||||
FoundExternalAddrs: blockFilterer.FoundExternal,
|
||||
FoundInternalAddrs: blockFilterer.FoundInternal,
|
||||
FoundOutPoints: blockFilterer.FoundOutPoints,
|
||||
RelevantTxns: blockFilterer.RelevantTxns,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// No addresses were found for this range.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// buildFilterBlocksWatchList constructs a watchlist used for matching against a
|
||||
// cfilter from a FilterBlocksRequest. The watchlist will be populated with all
|
||||
// external addresses, internal addresses, and outpoints contained in the
|
||||
// request.
|
||||
func buildFilterBlocksWatchList(req *FilterBlocksRequest) ([][]byte, error) {
|
||||
// Construct a watch list containing the script addresses of all
|
||||
// internal and external addresses that were requested, in addition to
|
||||
// the set of outpoints currently being watched.
|
||||
watchListSize := len(req.ExternalAddrs) +
|
||||
len(req.InternalAddrs) +
|
||||
len(req.WatchedOutPoints)
|
||||
|
||||
watchList := make([][]byte, 0, watchListSize)
|
||||
|
||||
for _, addr := range req.ExternalAddrs {
|
||||
p2shAddr, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watchList = append(watchList, p2shAddr)
|
||||
}
|
||||
|
||||
for _, addr := range req.InternalAddrs {
|
||||
p2shAddr, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watchList = append(watchList, p2shAddr)
|
||||
}
|
||||
|
||||
for _, addr := range req.WatchedOutPoints {
|
||||
addr, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watchList = append(watchList, addr)
|
||||
}
|
||||
|
||||
return watchList, nil
|
||||
}
|
||||
|
||||
// pollCFilter attempts to fetch a CFilter from the neutrino client. This is
|
||||
// used to get around the fact that the filter headers may lag behind the
|
||||
// highest known block header.
|
||||
func (s *NeutrinoClient) pollCFilter(hash *chainhash.Hash) (*gcs.Filter, error) {
|
||||
var (
|
||||
filter *gcs.Filter
|
||||
err error
|
||||
count int
|
||||
)
|
||||
|
||||
const maxFilterRetries = 50
|
||||
for count < maxFilterRetries {
|
||||
if count > 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
filter, err = s.CS.GetCFilter(*hash, wire.GCSFilterRegular)
|
||||
if err != nil {
|
||||
count++
|
||||
continue
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rescan replicates the RPC client's Rescan command.
|
||||
func (s *NeutrinoClient) Rescan(startHash *chainhash.Hash, addrs []btcutil.Address,
|
||||
outPoints map[wire.OutPoint]btcutil.Address) error {
|
||||
|
||||
s.clientMtx.Lock()
|
||||
if !s.started {
|
||||
s.clientMtx.Unlock()
|
||||
return fmt.Errorf("can't do a rescan when the chain client " +
|
||||
"is not started")
|
||||
}
|
||||
if s.scanning {
|
||||
// Restart the rescan by killing the existing rescan.
|
||||
close(s.rescanQuit)
|
||||
rescan := s.rescan
|
||||
s.clientMtx.Unlock()
|
||||
rescan.WaitForShutdown()
|
||||
s.clientMtx.Lock()
|
||||
s.rescan = nil
|
||||
s.rescanErr = nil
|
||||
}
|
||||
s.rescanQuit = make(chan struct{})
|
||||
s.scanning = true
|
||||
s.finished = false
|
||||
s.lastProgressSent = false
|
||||
s.lastFilteredBlockHeader = nil
|
||||
s.isRescan = true
|
||||
s.clientMtx.Unlock()
|
||||
|
||||
bestBlock, err := s.CS.BestBlock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't get chain service's best block: %s", err)
|
||||
}
|
||||
header, err := s.CS.GetBlockHeader(&bestBlock.Hash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't get block header for hash %v: %s",
|
||||
bestBlock.Hash, err)
|
||||
}
|
||||
|
||||
// If the wallet is already fully caught up, or the rescan has started
|
||||
// with state that indicates a "fresh" wallet, we'll send a
|
||||
// notification indicating the rescan has "finished".
|
||||
if header.BlockHash() == *startHash {
|
||||
s.clientMtx.Lock()
|
||||
s.finished = true
|
||||
rescanQuit := s.rescanQuit
|
||||
s.clientMtx.Unlock()
|
||||
|
||||
// Release the lock while dispatching the notification since
|
||||
// it's possible for the notificationHandler to be waiting to
|
||||
// acquire it before receiving the notification.
|
||||
select {
|
||||
case s.enqueueNotification <- &RescanFinished{
|
||||
Hash: startHash,
|
||||
Height: int32(bestBlock.Height),
|
||||
Time: header.Timestamp,
|
||||
}:
|
||||
case <-s.quit:
|
||||
return nil
|
||||
case <-rescanQuit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var inputsToWatch []neutrino.InputWithScript
|
||||
for op, addr := range outPoints {
|
||||
addrScript, err := txscript.PayToAddrScript(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inputsToWatch = append(inputsToWatch, neutrino.InputWithScript{
|
||||
OutPoint: op,
|
||||
PkScript: addrScript,
|
||||
})
|
||||
}
|
||||
|
||||
s.clientMtx.Lock()
|
||||
newRescan := neutrino.NewRescan(
|
||||
&neutrino.RescanChainSource{
|
||||
ChainService: s.CS,
|
||||
},
|
||||
neutrino.NotificationHandlers(rpcclient.NotificationHandlers{
|
||||
OnBlockConnected: s.onBlockConnected,
|
||||
OnFilteredBlockConnected: s.onFilteredBlockConnected,
|
||||
OnBlockDisconnected: s.onBlockDisconnected,
|
||||
}),
|
||||
neutrino.StartBlock(&headerfs.BlockStamp{Hash: *startHash}),
|
||||
neutrino.StartTime(s.startTime),
|
||||
neutrino.QuitChan(s.rescanQuit),
|
||||
neutrino.WatchAddrs(addrs...),
|
||||
neutrino.WatchInputs(inputsToWatch...),
|
||||
)
|
||||
s.rescan = newRescan
|
||||
s.rescanErr = s.rescan.Start()
|
||||
s.clientMtx.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotifyBlocks replicates the RPC client's NotifyBlocks command.
|
||||
func (s *NeutrinoClient) NotifyBlocks() error {
|
||||
s.clientMtx.Lock()
|
||||
// If we're scanning, we're already notifying on blocks. Otherwise,
|
||||
// start a rescan without watching any addresses.
|
||||
if !s.scanning {
|
||||
s.clientMtx.Unlock()
|
||||
return s.NotifyReceived([]btcutil.Address{})
|
||||
}
|
||||
s.clientMtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotifyReceived replicates the RPC client's NotifyReceived command.
|
||||
func (s *NeutrinoClient) NotifyReceived(addrs []btcutil.Address) error {
|
||||
s.clientMtx.Lock()
|
||||
|
||||
// If we have a rescan running, we just need to add the appropriate
|
||||
// addresses to the watch list.
|
||||
if s.scanning {
|
||||
s.clientMtx.Unlock()
|
||||
return s.rescan.Update(neutrino.AddAddrs(addrs...))
|
||||
}
|
||||
|
||||
s.rescanQuit = make(chan struct{})
|
||||
s.scanning = true
|
||||
|
||||
// Don't need RescanFinished or RescanProgress notifications.
|
||||
s.finished = true
|
||||
s.lastProgressSent = true
|
||||
s.lastFilteredBlockHeader = nil
|
||||
|
||||
// Rescan with just the specified addresses.
|
||||
newRescan := neutrino.NewRescan(
|
||||
&neutrino.RescanChainSource{
|
||||
ChainService: s.CS,
|
||||
},
|
||||
neutrino.NotificationHandlers(rpcclient.NotificationHandlers{
|
||||
OnBlockConnected: s.onBlockConnected,
|
||||
OnFilteredBlockConnected: s.onFilteredBlockConnected,
|
||||
OnBlockDisconnected: s.onBlockDisconnected,
|
||||
}),
|
||||
neutrino.StartTime(s.startTime),
|
||||
neutrino.QuitChan(s.rescanQuit),
|
||||
neutrino.WatchAddrs(addrs...),
|
||||
)
|
||||
s.rescan = newRescan
|
||||
s.rescanErr = s.rescan.Start()
|
||||
s.clientMtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Notifications replicates the RPC client's Notifications method.
|
||||
func (s *NeutrinoClient) Notifications() <-chan interface{} {
|
||||
return s.dequeueNotification
|
||||
}
|
||||
|
||||
// SetStartTime is a non-interface method to set the birthday of the wallet
|
||||
// using this object. Since only a single rescan at a time is currently
|
||||
// supported, only one birthday needs to be set. This does not fully restart a
|
||||
// running rescan, so should not be used to update a rescan while it is running.
|
||||
// TODO: When factoring out to multiple rescans per Neutrino client, add a
|
||||
// birthday per client.
|
||||
func (s *NeutrinoClient) SetStartTime(startTime time.Time) {
|
||||
s.clientMtx.Lock()
|
||||
defer s.clientMtx.Unlock()
|
||||
|
||||
s.startTime = startTime
|
||||
}
|
||||
|
||||
// onFilteredBlockConnected sends appropriate notifications to the notification
|
||||
// channel.
|
||||
func (s *NeutrinoClient) onFilteredBlockConnected(height int32,
|
||||
header *wire.BlockHeader, relevantTxs []*btcutil.Tx) {
|
||||
ntfn := FilteredBlockConnected{
|
||||
Block: &wtxmgr.BlockMeta{
|
||||
Block: wtxmgr.Block{
|
||||
Hash: header.BlockHash(),
|
||||
Height: height,
|
||||
},
|
||||
Time: header.Timestamp,
|
||||
},
|
||||
}
|
||||
for _, tx := range relevantTxs {
|
||||
rec, err := wtxmgr.NewTxRecordFromMsgTx(tx.MsgTx(),
|
||||
header.Timestamp)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot create transaction record for "+
|
||||
"relevant tx: %s", err)
|
||||
// TODO(aakselrod): Return?
|
||||
continue
|
||||
}
|
||||
ntfn.RelevantTxs = append(ntfn.RelevantTxs, rec)
|
||||
}
|
||||
|
||||
select {
|
||||
case s.enqueueNotification <- ntfn:
|
||||
case <-s.quit:
|
||||
return
|
||||
case <-s.rescanQuit:
|
||||
return
|
||||
}
|
||||
|
||||
s.clientMtx.Lock()
|
||||
s.lastFilteredBlockHeader = header
|
||||
s.clientMtx.Unlock()
|
||||
|
||||
// Handle RescanFinished notification if required.
|
||||
s.dispatchRescanFinished()
|
||||
}
|
||||
|
||||
// onBlockDisconnected sends appropriate notifications to the notification
|
||||
// channel.
|
||||
func (s *NeutrinoClient) onBlockDisconnected(hash *chainhash.Hash, height int32,
|
||||
t time.Time) {
|
||||
select {
|
||||
case s.enqueueNotification <- BlockDisconnected{
|
||||
Block: wtxmgr.Block{
|
||||
Hash: *hash,
|
||||
Height: height,
|
||||
},
|
||||
Time: t,
|
||||
}:
|
||||
case <-s.quit:
|
||||
case <-s.rescanQuit:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NeutrinoClient) onBlockConnected(hash *chainhash.Hash, height int32,
|
||||
time time.Time) {
|
||||
// TODO: Move this closure out and parameterize it? Is it useful
|
||||
// outside here?
|
||||
sendRescanProgress := func() {
|
||||
select {
|
||||
case s.enqueueNotification <- &RescanProgress{
|
||||
Hash: hash,
|
||||
Height: height,
|
||||
Time: time,
|
||||
}:
|
||||
case <-s.quit:
|
||||
case <-s.rescanQuit:
|
||||
}
|
||||
}
|
||||
// Only send BlockConnected notification if we're processing blocks
|
||||
// before the birthday. Otherwise, we can just update using
|
||||
// RescanProgress notifications.
|
||||
if time.Before(s.startTime) {
|
||||
// Send a RescanProgress notification every 10K blocks.
|
||||
if height%10000 == 0 {
|
||||
s.clientMtx.Lock()
|
||||
shouldSend := s.isRescan && !s.finished
|
||||
s.clientMtx.Unlock()
|
||||
if shouldSend {
|
||||
sendRescanProgress()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Send a RescanProgress notification if we're just going over
|
||||
// the boundary between pre-birthday and post-birthday blocks,
|
||||
// and note that we've sent it.
|
||||
s.clientMtx.Lock()
|
||||
if !s.lastProgressSent {
|
||||
shouldSend := s.isRescan && !s.finished
|
||||
if shouldSend {
|
||||
s.clientMtx.Unlock()
|
||||
sendRescanProgress()
|
||||
s.clientMtx.Lock()
|
||||
s.lastProgressSent = true
|
||||
}
|
||||
}
|
||||
s.clientMtx.Unlock()
|
||||
select {
|
||||
case s.enqueueNotification <- BlockConnected{
|
||||
Block: wtxmgr.Block{
|
||||
Hash: *hash,
|
||||
Height: height,
|
||||
},
|
||||
Time: time,
|
||||
}:
|
||||
case <-s.quit:
|
||||
case <-s.rescanQuit:
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're able to dispatch our final RescanFinished notification
|
||||
// after processing this block.
|
||||
s.dispatchRescanFinished()
|
||||
}
|
||||
|
||||
// dispatchRescanFinished determines whether we're able to dispatch our final
|
||||
// RescanFinished notification in order to mark the wallet as synced with the
|
||||
// chain. If the notification has already been dispatched, then it won't be done
|
||||
// again.
|
||||
func (s *NeutrinoClient) dispatchRescanFinished() {
|
||||
bs, err := s.CS.BestBlock()
|
||||
if err != nil {
|
||||
log.Errorf("Can't get chain service's best block: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
s.clientMtx.Lock()
|
||||
// Only send the RescanFinished notification once.
|
||||
if s.lastFilteredBlockHeader == nil || s.finished {
|
||||
s.clientMtx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Only send the RescanFinished notification once the underlying chain
|
||||
// service sees itself as current.
|
||||
if bs.Hash != s.lastFilteredBlockHeader.BlockHash() {
|
||||
s.clientMtx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
s.finished = s.CS.IsCurrent() && s.lastProgressSent
|
||||
if !s.finished {
|
||||
s.clientMtx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
header := s.lastFilteredBlockHeader
|
||||
s.clientMtx.Unlock()
|
||||
|
||||
select {
|
||||
case s.enqueueNotification <- &RescanFinished{
|
||||
Hash: &bs.Hash,
|
||||
Height: bs.Height,
|
||||
Time: header.Timestamp,
|
||||
}:
|
||||
case <-s.quit:
|
||||
return
|
||||
case <-s.rescanQuit:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// notificationHandler queues and dequeues notifications. There are currently
|
||||
// no bounds on the queue, so the dequeue channel should be read continually to
|
||||
// avoid running out of memory.
|
||||
func (s *NeutrinoClient) notificationHandler() {
|
||||
hash, height, err := s.GetBestBlock()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get best block from chain service: %s",
|
||||
err)
|
||||
s.Stop()
|
||||
s.wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
bs := &waddrmgr.BlockStamp{Hash: *hash, Height: height}
|
||||
|
||||
// TODO: Rather than leaving this as an unbounded queue for all types of
|
||||
// notifications, try dropping ones where a later enqueued notification
|
||||
// can fully invalidate one waiting to be processed. For example,
|
||||
// blockconnected notifications for greater block heights can remove the
|
||||
// need to process earlier blockconnected notifications still waiting
|
||||
// here.
|
||||
|
||||
var notifications []interface{}
|
||||
enqueue := s.enqueueNotification
|
||||
var dequeue chan interface{}
|
||||
var next interface{}
|
||||
out:
|
||||
for {
|
||||
s.clientMtx.Lock()
|
||||
rescanErr := s.rescanErr
|
||||
s.clientMtx.Unlock()
|
||||
select {
|
||||
case n, ok := <-enqueue:
|
||||
if !ok {
|
||||
// If no notifications are queued for handling,
|
||||
// the queue is finished.
|
||||
if len(notifications) == 0 {
|
||||
break out
|
||||
}
|
||||
// nil channel so no more reads can occur.
|
||||
enqueue = nil
|
||||
continue
|
||||
}
|
||||
if len(notifications) == 0 {
|
||||
next = n
|
||||
dequeue = s.dequeueNotification
|
||||
}
|
||||
notifications = append(notifications, n)
|
||||
|
||||
case dequeue <- next:
|
||||
if n, ok := next.(BlockConnected); ok {
|
||||
bs = &waddrmgr.BlockStamp{
|
||||
Height: n.Height,
|
||||
Hash: n.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
notifications[0] = nil
|
||||
notifications = notifications[1:]
|
||||
if len(notifications) != 0 {
|
||||
next = notifications[0]
|
||||
} else {
|
||||
// If no more notifications can be enqueued, the
|
||||
// queue is finished.
|
||||
if enqueue == nil {
|
||||
break out
|
||||
}
|
||||
dequeue = nil
|
||||
}
|
||||
|
||||
case err := <-rescanErr:
|
||||
if err != nil {
|
||||
log.Errorf("Neutrino rescan ended with error: %s", err)
|
||||
}
|
||||
|
||||
case s.currentBlock <- bs:
|
||||
|
||||
case <-s.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
s.Stop()
|
||||
close(s.dequeueNotification)
|
||||
s.wg.Done()
|
||||
}
|
||||
88
vendor/github.com/btcsuite/btcwallet/chain/queue.go
generated
vendored
Normal file
88
vendor/github.com/btcsuite/btcwallet/chain/queue.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
// ConcurrentQueue is a concurrent-safe FIFO queue with unbounded capacity.
|
||||
// Clients interact with the queue by pushing items into the in channel and
|
||||
// popping items from the out channel. There is a goroutine that manages moving
|
||||
// items from the in channel to the out channel in the correct order that must
|
||||
// be started by calling Start().
|
||||
type ConcurrentQueue struct {
|
||||
chanIn chan interface{}
|
||||
chanOut chan interface{}
|
||||
quit chan struct{}
|
||||
overflow *list.List
|
||||
}
|
||||
|
||||
// NewConcurrentQueue constructs a ConcurrentQueue. The bufferSize parameter is
|
||||
// the capacity of the output channel. When the size of the queue is below this
|
||||
// threshold, pushes do not incur the overhead of the less efficient overflow
|
||||
// structure.
|
||||
func NewConcurrentQueue(bufferSize int) *ConcurrentQueue {
|
||||
return &ConcurrentQueue{
|
||||
chanIn: make(chan interface{}),
|
||||
chanOut: make(chan interface{}, bufferSize),
|
||||
quit: make(chan struct{}),
|
||||
overflow: list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// ChanIn returns a channel that can be used to push new items into the queue.
|
||||
func (cq *ConcurrentQueue) ChanIn() chan<- interface{} {
|
||||
return cq.chanIn
|
||||
}
|
||||
|
||||
// ChanOut returns a channel that can be used to pop items from the queue.
|
||||
func (cq *ConcurrentQueue) ChanOut() <-chan interface{} {
|
||||
return cq.chanOut
|
||||
}
|
||||
|
||||
// Start begins a goroutine that manages moving items from the in channel to
|
||||
// the out channel. The queue tries to move items directly to the out channel
|
||||
// minimize overhead, but if the out channel is full it pushes items to an
|
||||
// overflow queue. This must be called before using the queue.
|
||||
func (cq *ConcurrentQueue) Start() {
|
||||
go func() {
|
||||
for {
|
||||
nextElement := cq.overflow.Front()
|
||||
if nextElement == nil {
|
||||
// The overflow queue is empty, so incoming
|
||||
// items can be pushed directly to the output
|
||||
// channel. However, if output channel is full,
|
||||
// we'll push to the overflow list instead.
|
||||
select {
|
||||
case item := <-cq.chanIn:
|
||||
select {
|
||||
case cq.chanOut <- item:
|
||||
case <-cq.quit:
|
||||
return
|
||||
default:
|
||||
cq.overflow.PushBack(item)
|
||||
}
|
||||
case <-cq.quit:
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// The overflow queue is not empty, so any new
|
||||
// items get pushed to the back to preserve
|
||||
// order.
|
||||
select {
|
||||
case item := <-cq.chanIn:
|
||||
cq.overflow.PushBack(item)
|
||||
case cq.chanOut <- nextElement.Value:
|
||||
cq.overflow.Remove(nextElement)
|
||||
case <-cq.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop ends the goroutine that moves items from the in channel to the out
|
||||
// channel.
|
||||
func (cq *ConcurrentQueue) Stop() {
|
||||
close(cq.quit)
|
||||
}
|
||||
461
vendor/github.com/btcsuite/btcwallet/chain/rpc.go
generated
vendored
Normal file
461
vendor/github.com/btcsuite/btcwallet/chain/rpc.go
generated
vendored
Normal file
@@ -0,0 +1,461 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package chain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/gcs"
|
||||
"github.com/btcsuite/btcutil/gcs/builder"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||
)
|
||||
|
||||
// RPCClient represents a persistent client connection to a bitcoin RPC server
|
||||
// for information regarding the current best block chain.
|
||||
type RPCClient struct {
|
||||
*rpcclient.Client
|
||||
connConfig *rpcclient.ConnConfig // Work around unexported field
|
||||
chainParams *chaincfg.Params
|
||||
reconnectAttempts int
|
||||
|
||||
enqueueNotification chan interface{}
|
||||
dequeueNotification chan interface{}
|
||||
currentBlock chan *waddrmgr.BlockStamp
|
||||
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
started bool
|
||||
quitMtx sync.Mutex
|
||||
}
|
||||
|
||||
// NewRPCClient creates a client connection to the server described by the
|
||||
// connect string. If disableTLS is false, the remote RPC certificate must be
|
||||
// provided in the certs slice. The connection is not established immediately,
|
||||
// but must be done using the Start method. If the remote server does not
|
||||
// operate on the same bitcoin network as described by the passed chain
|
||||
// parameters, the connection will be disconnected.
|
||||
func NewRPCClient(chainParams *chaincfg.Params, connect, user, pass string, certs []byte,
|
||||
disableTLS bool, reconnectAttempts int) (*RPCClient, error) {
|
||||
|
||||
if reconnectAttempts < 0 {
|
||||
return nil, errors.New("reconnectAttempts must be positive")
|
||||
}
|
||||
|
||||
client := &RPCClient{
|
||||
connConfig: &rpcclient.ConnConfig{
|
||||
Host: connect,
|
||||
Endpoint: "ws",
|
||||
User: user,
|
||||
Pass: pass,
|
||||
Certificates: certs,
|
||||
DisableAutoReconnect: false,
|
||||
DisableConnectOnNew: true,
|
||||
DisableTLS: disableTLS,
|
||||
},
|
||||
chainParams: chainParams,
|
||||
reconnectAttempts: reconnectAttempts,
|
||||
enqueueNotification: make(chan interface{}),
|
||||
dequeueNotification: make(chan interface{}),
|
||||
currentBlock: make(chan *waddrmgr.BlockStamp),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
ntfnCallbacks := &rpcclient.NotificationHandlers{
|
||||
OnClientConnected: client.onClientConnect,
|
||||
OnBlockConnected: client.onBlockConnected,
|
||||
OnBlockDisconnected: client.onBlockDisconnected,
|
||||
OnRecvTx: client.onRecvTx,
|
||||
OnRedeemingTx: client.onRedeemingTx,
|
||||
OnRescanFinished: client.onRescanFinished,
|
||||
OnRescanProgress: client.onRescanProgress,
|
||||
}
|
||||
rpcClient, err := rpcclient.New(client.connConfig, ntfnCallbacks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.Client = rpcClient
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// BackEnd returns the name of the driver.
|
||||
func (c *RPCClient) BackEnd() string {
|
||||
return "btcd"
|
||||
}
|
||||
|
||||
// Start attempts to establish a client connection with the remote server.
|
||||
// If successful, handler goroutines are started to process notifications
|
||||
// sent by the server. After a limited number of connection attempts, this
|
||||
// function gives up, and therefore will not block forever waiting for the
|
||||
// connection to be established to a server that may not exist.
|
||||
func (c *RPCClient) Start() error {
|
||||
err := c.Connect(c.reconnectAttempts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify that the server is running on the expected network.
|
||||
net, err := c.GetCurrentNet()
|
||||
if err != nil {
|
||||
c.Disconnect()
|
||||
return err
|
||||
}
|
||||
if net != c.chainParams.Net {
|
||||
c.Disconnect()
|
||||
return errors.New("mismatched networks")
|
||||
}
|
||||
|
||||
c.quitMtx.Lock()
|
||||
c.started = true
|
||||
c.quitMtx.Unlock()
|
||||
|
||||
c.wg.Add(1)
|
||||
go c.handler()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop disconnects the client and signals the shutdown of all goroutines
|
||||
// started by Start.
|
||||
func (c *RPCClient) Stop() {
|
||||
c.quitMtx.Lock()
|
||||
select {
|
||||
case <-c.quit:
|
||||
default:
|
||||
close(c.quit)
|
||||
c.Client.Shutdown()
|
||||
|
||||
if !c.started {
|
||||
close(c.dequeueNotification)
|
||||
}
|
||||
}
|
||||
c.quitMtx.Unlock()
|
||||
}
|
||||
|
||||
// IsCurrent returns whether the chain backend considers its view of the network
|
||||
// as "current".
|
||||
func (c *RPCClient) IsCurrent() bool {
|
||||
bestHash, _, err := c.GetBestBlock()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bestHeader, err := c.GetBlockHeader(bestHash)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return bestHeader.Timestamp.After(time.Now().Add(-isCurrentDelta))
|
||||
}
|
||||
|
||||
// Rescan wraps the normal Rescan command with an additional paramter that
|
||||
// allows us to map an oupoint to the address in the chain that it pays to.
|
||||
// This is useful when using BIP 158 filters as they include the prev pkScript
|
||||
// rather than the full outpoint.
|
||||
func (c *RPCClient) Rescan(startHash *chainhash.Hash, addrs []btcutil.Address,
|
||||
outPoints map[wire.OutPoint]btcutil.Address) error {
|
||||
|
||||
flatOutpoints := make([]*wire.OutPoint, 0, len(outPoints))
|
||||
for ops := range outPoints {
|
||||
flatOutpoints = append(flatOutpoints, &ops)
|
||||
}
|
||||
|
||||
return c.Client.Rescan(startHash, addrs, flatOutpoints)
|
||||
}
|
||||
|
||||
// WaitForShutdown blocks until both the client has finished disconnecting
|
||||
// and all handlers have exited.
|
||||
func (c *RPCClient) WaitForShutdown() {
|
||||
c.Client.WaitForShutdown()
|
||||
c.wg.Wait()
|
||||
}
|
||||
|
||||
// Notifications returns a channel of parsed notifications sent by the remote
|
||||
// bitcoin RPC server. This channel must be continually read or the process
|
||||
// may abort for running out memory, as unread notifications are queued for
|
||||
// later reads.
|
||||
func (c *RPCClient) Notifications() <-chan interface{} {
|
||||
return c.dequeueNotification
|
||||
}
|
||||
|
||||
// BlockStamp returns the latest block notified by the client, or an error
|
||||
// if the client has been shut down.
|
||||
func (c *RPCClient) BlockStamp() (*waddrmgr.BlockStamp, error) {
|
||||
select {
|
||||
case bs := <-c.currentBlock:
|
||||
return bs, nil
|
||||
case <-c.quit:
|
||||
return nil, errors.New("disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
// FilterBlocks scans the blocks contained in the FilterBlocksRequest for any
|
||||
// addresses of interest. For each requested block, the corresponding compact
|
||||
// filter will first be checked for matches, skipping those that do not report
|
||||
// anything. If the filter returns a postive match, the full block will be
|
||||
// fetched and filtered. This method returns a FilterBlocksReponse for the first
|
||||
// block containing a matching address. If no matches are found in the range of
|
||||
// blocks requested, the returned response will be nil.
|
||||
func (c *RPCClient) FilterBlocks(
|
||||
req *FilterBlocksRequest) (*FilterBlocksResponse, error) {
|
||||
|
||||
blockFilterer := NewBlockFilterer(c.chainParams, req)
|
||||
|
||||
// Construct the watchlist using the addresses and outpoints contained
|
||||
// in the filter blocks request.
|
||||
watchList, err := buildFilterBlocksWatchList(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Iterate over the requested blocks, fetching the compact filter for
|
||||
// each one, and matching it against the watchlist generated above. If
|
||||
// the filter returns a positive match, the full block is then requested
|
||||
// and scanned for addresses using the block filterer.
|
||||
for i, blk := range req.Blocks {
|
||||
rawFilter, err := c.GetCFilter(&blk.Hash, wire.GCSFilterRegular)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure the filter is large enough to be deserialized.
|
||||
if len(rawFilter.Data) < 4 {
|
||||
continue
|
||||
}
|
||||
|
||||
filter, err := gcs.FromNBytes(
|
||||
builder.DefaultP, builder.DefaultM, rawFilter.Data,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Skip any empty filters.
|
||||
if filter.N() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := builder.DeriveKey(&blk.Hash)
|
||||
matched, err := filter.MatchAny(key, watchList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Fetching block height=%d hash=%v",
|
||||
blk.Height, blk.Hash)
|
||||
|
||||
rawBlock, err := c.GetBlock(&blk.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !blockFilterer.FilterBlock(rawBlock) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If any external or internal addresses were detected in this
|
||||
// block, we return them to the caller so that the rescan
|
||||
// windows can widened with subsequent addresses. The
|
||||
// `BatchIndex` is returned so that the caller can compute the
|
||||
// *next* block from which to begin again.
|
||||
resp := &FilterBlocksResponse{
|
||||
BatchIndex: uint32(i),
|
||||
BlockMeta: blk,
|
||||
FoundExternalAddrs: blockFilterer.FoundExternal,
|
||||
FoundInternalAddrs: blockFilterer.FoundInternal,
|
||||
FoundOutPoints: blockFilterer.FoundOutPoints,
|
||||
RelevantTxns: blockFilterer.RelevantTxns,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// No addresses were found for this range.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// parseBlock parses a btcws definition of the block a tx is mined it to the
|
||||
// Block structure of the wtxmgr package, and the block index. This is done
|
||||
// here since rpcclient doesn't parse this nicely for us.
|
||||
func parseBlock(block *btcjson.BlockDetails) (*wtxmgr.BlockMeta, error) {
|
||||
if block == nil {
|
||||
return nil, nil
|
||||
}
|
||||
blkHash, err := chainhash.NewHashFromStr(block.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blk := &wtxmgr.BlockMeta{
|
||||
Block: wtxmgr.Block{
|
||||
Height: block.Height,
|
||||
Hash: *blkHash,
|
||||
},
|
||||
Time: time.Unix(block.Time, 0),
|
||||
}
|
||||
return blk, nil
|
||||
}
|
||||
|
||||
func (c *RPCClient) onClientConnect() {
|
||||
select {
|
||||
case c.enqueueNotification <- ClientConnected{}:
|
||||
case <-c.quit:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCClient) onBlockConnected(hash *chainhash.Hash, height int32, time time.Time) {
|
||||
select {
|
||||
case c.enqueueNotification <- BlockConnected{
|
||||
Block: wtxmgr.Block{
|
||||
Hash: *hash,
|
||||
Height: height,
|
||||
},
|
||||
Time: time,
|
||||
}:
|
||||
case <-c.quit:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCClient) onBlockDisconnected(hash *chainhash.Hash, height int32, time time.Time) {
|
||||
select {
|
||||
case c.enqueueNotification <- BlockDisconnected{
|
||||
Block: wtxmgr.Block{
|
||||
Hash: *hash,
|
||||
Height: height,
|
||||
},
|
||||
Time: time,
|
||||
}:
|
||||
case <-c.quit:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCClient) onRecvTx(tx *btcutil.Tx, block *btcjson.BlockDetails) {
|
||||
blk, err := parseBlock(block)
|
||||
if err != nil {
|
||||
// Log and drop improper notification.
|
||||
log.Errorf("recvtx notification bad block: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
rec, err := wtxmgr.NewTxRecordFromMsgTx(tx.MsgTx(), time.Now())
|
||||
if err != nil {
|
||||
log.Errorf("Cannot create transaction record for relevant "+
|
||||
"tx: %v", err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case c.enqueueNotification <- RelevantTx{rec, blk}:
|
||||
case <-c.quit:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCClient) onRedeemingTx(tx *btcutil.Tx, block *btcjson.BlockDetails) {
|
||||
// Handled exactly like recvtx notifications.
|
||||
c.onRecvTx(tx, block)
|
||||
}
|
||||
|
||||
func (c *RPCClient) onRescanProgress(hash *chainhash.Hash, height int32, blkTime time.Time) {
|
||||
select {
|
||||
case c.enqueueNotification <- &RescanProgress{hash, height, blkTime}:
|
||||
case <-c.quit:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RPCClient) onRescanFinished(hash *chainhash.Hash, height int32, blkTime time.Time) {
|
||||
select {
|
||||
case c.enqueueNotification <- &RescanFinished{hash, height, blkTime}:
|
||||
case <-c.quit:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// handler maintains a queue of notifications and the current state (best
|
||||
// block) of the chain.
|
||||
func (c *RPCClient) handler() {
|
||||
hash, height, err := c.GetBestBlock()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to receive best block from chain server: %v", err)
|
||||
c.Stop()
|
||||
c.wg.Done()
|
||||
return
|
||||
}
|
||||
|
||||
bs := &waddrmgr.BlockStamp{Hash: *hash, Height: height}
|
||||
|
||||
// TODO: Rather than leaving this as an unbounded queue for all types of
|
||||
// notifications, try dropping ones where a later enqueued notification
|
||||
// can fully invalidate one waiting to be processed. For example,
|
||||
// blockconnected notifications for greater block heights can remove the
|
||||
// need to process earlier blockconnected notifications still waiting
|
||||
// here.
|
||||
|
||||
var notifications []interface{}
|
||||
enqueue := c.enqueueNotification
|
||||
var dequeue chan interface{}
|
||||
var next interface{}
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case n, ok := <-enqueue:
|
||||
if !ok {
|
||||
// If no notifications are queued for handling,
|
||||
// the queue is finished.
|
||||
if len(notifications) == 0 {
|
||||
break out
|
||||
}
|
||||
// nil channel so no more reads can occur.
|
||||
enqueue = nil
|
||||
continue
|
||||
}
|
||||
if len(notifications) == 0 {
|
||||
next = n
|
||||
dequeue = c.dequeueNotification
|
||||
}
|
||||
notifications = append(notifications, n)
|
||||
|
||||
case dequeue <- next:
|
||||
if n, ok := next.(BlockConnected); ok {
|
||||
bs = &waddrmgr.BlockStamp{
|
||||
Height: n.Height,
|
||||
Hash: n.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
notifications[0] = nil
|
||||
notifications = notifications[1:]
|
||||
if len(notifications) != 0 {
|
||||
next = notifications[0]
|
||||
} else {
|
||||
// If no more notifications can be enqueued, the
|
||||
// queue is finished.
|
||||
if enqueue == nil {
|
||||
break out
|
||||
}
|
||||
dequeue = nil
|
||||
}
|
||||
|
||||
case c.currentBlock <- bs:
|
||||
|
||||
case <-c.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
c.Stop()
|
||||
close(c.dequeueNotification)
|
||||
c.wg.Done()
|
||||
}
|
||||
|
||||
// POSTClient creates the equivalent HTTP POST rpcclient.Client.
|
||||
func (c *RPCClient) POSTClient() (*rpcclient.Client, error) {
|
||||
configCopy := *c.connConfig
|
||||
configCopy.HTTPPostMode = true
|
||||
return rpcclient.New(&configCopy, nil)
|
||||
}
|
||||
17
vendor/github.com/btcsuite/btcwallet/internal/zero/array.go
generated
vendored
Normal file
17
vendor/github.com/btcsuite/btcwallet/internal/zero/array.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package zero
|
||||
|
||||
// Bytea32 clears the 32-byte array by filling it with the zero value.
|
||||
// This is used to explicitly clear private key material from memory.
|
||||
func Bytea32(b *[32]byte) {
|
||||
*b = [32]byte{}
|
||||
}
|
||||
|
||||
// Bytea64 clears the 64-byte array by filling it with the zero value.
|
||||
// This is used to explicitly clear sensitive material from memory.
|
||||
func Bytea64(b *[64]byte) {
|
||||
*b = [64]byte{}
|
||||
}
|
||||
3
vendor/github.com/btcsuite/btcwallet/internal/zero/doc.go
generated
vendored
Normal file
3
vendor/github.com/btcsuite/btcwallet/internal/zero/doc.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package zero contains functions to clear data from byte slices and
|
||||
// multi-precision integers.
|
||||
package zero
|
||||
34
vendor/github.com/btcsuite/btcwallet/internal/zero/slice.go
generated
vendored
Normal file
34
vendor/github.com/btcsuite/btcwallet/internal/zero/slice.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file implements range-based zeroing, which as of Go 1.5 is
|
||||
// optimized using a Duff's device.
|
||||
|
||||
package zero
|
||||
|
||||
import "math/big"
|
||||
|
||||
// Bytes sets all bytes in the passed slice to zero. This is used to
|
||||
// explicitly clear private key material from memory.
|
||||
//
|
||||
// In general, prefer to use the fixed-sized zeroing functions (Bytea*)
|
||||
// when zeroing bytes as they are much more efficient than the variable
|
||||
// sized zeroing func Bytes.
|
||||
func Bytes(b []byte) {
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// BigInt sets all bytes in the passed big int to zero and then sets the
|
||||
// value to 0. This differs from simply setting the value in that it
|
||||
// specifically clears the underlying bytes whereas simply setting the value
|
||||
// does not. This is mostly useful to forcefully clear private keys.
|
||||
func BigInt(x *big.Int) {
|
||||
b := x.Bits()
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
x.SetInt64(0)
|
||||
}
|
||||
248
vendor/github.com/btcsuite/btcwallet/snacl/snacl.go
generated
vendored
Normal file
248
vendor/github.com/btcsuite/btcwallet/snacl/snacl.go
generated
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
// Copyright (c) 2014-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package snacl
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/btcsuite/btcwallet/internal/zero"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
prng = rand.Reader
|
||||
)
|
||||
|
||||
// Error types and messages.
|
||||
var (
|
||||
ErrInvalidPassword = errors.New("invalid password")
|
||||
ErrMalformed = errors.New("malformed data")
|
||||
ErrDecryptFailed = errors.New("unable to decrypt")
|
||||
)
|
||||
|
||||
// Various constants needed for encryption scheme.
|
||||
const (
|
||||
// Expose secretbox's Overhead const here for convenience.
|
||||
Overhead = secretbox.Overhead
|
||||
KeySize = 32
|
||||
NonceSize = 24
|
||||
DefaultN = 16384 // 2^14
|
||||
DefaultR = 8
|
||||
DefaultP = 1
|
||||
)
|
||||
|
||||
// CryptoKey represents a secret key which can be used to encrypt and decrypt
|
||||
// data.
|
||||
type CryptoKey [KeySize]byte
|
||||
|
||||
// Encrypt encrypts the passed data.
|
||||
func (ck *CryptoKey) Encrypt(in []byte) ([]byte, error) {
|
||||
var nonce [NonceSize]byte
|
||||
_, err := io.ReadFull(prng, nonce[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blob := secretbox.Seal(nil, in, &nonce, (*[KeySize]byte)(ck))
|
||||
return append(nonce[:], blob...), nil
|
||||
}
|
||||
|
||||
// Decrypt decrypts the passed data. The must be the output of the Encrypt
|
||||
// function.
|
||||
func (ck *CryptoKey) Decrypt(in []byte) ([]byte, error) {
|
||||
if len(in) < NonceSize {
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
|
||||
var nonce [NonceSize]byte
|
||||
copy(nonce[:], in[:NonceSize])
|
||||
blob := in[NonceSize:]
|
||||
|
||||
opened, ok := secretbox.Open(nil, blob, &nonce, (*[KeySize]byte)(ck))
|
||||
if !ok {
|
||||
return nil, ErrDecryptFailed
|
||||
}
|
||||
|
||||
return opened, nil
|
||||
}
|
||||
|
||||
// Zero clears the key by manually zeroing all memory. This is for security
|
||||
// conscience application which wish to zero the memory after they've used it
|
||||
// rather than waiting until it's reclaimed by the garbage collector. The
|
||||
// key is no longer usable after this call.
|
||||
func (ck *CryptoKey) Zero() {
|
||||
zero.Bytea32((*[KeySize]byte)(ck))
|
||||
}
|
||||
|
||||
// GenerateCryptoKey generates a new crypotgraphically random key.
|
||||
func GenerateCryptoKey() (*CryptoKey, error) {
|
||||
var key CryptoKey
|
||||
_, err := io.ReadFull(prng, key[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &key, nil
|
||||
}
|
||||
|
||||
// Parameters are not secret and can be stored in plain text.
|
||||
type Parameters struct {
|
||||
Salt [KeySize]byte
|
||||
Digest [sha256.Size]byte
|
||||
N int
|
||||
R int
|
||||
P int
|
||||
}
|
||||
|
||||
// SecretKey houses a crypto key and the parameters needed to derive it from a
|
||||
// passphrase. It should only be used in memory.
|
||||
type SecretKey struct {
|
||||
Key *CryptoKey
|
||||
Parameters Parameters
|
||||
}
|
||||
|
||||
// deriveKey fills out the Key field.
|
||||
func (sk *SecretKey) deriveKey(password *[]byte) error {
|
||||
key, err := scrypt.Key(*password, sk.Parameters.Salt[:],
|
||||
sk.Parameters.N,
|
||||
sk.Parameters.R,
|
||||
sk.Parameters.P,
|
||||
len(sk.Key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy(sk.Key[:], key)
|
||||
zero.Bytes(key)
|
||||
|
||||
// I'm not a fan of forced garbage collections, but scrypt allocates a
|
||||
// ton of memory and calling it back to back without a GC cycle in
|
||||
// between means you end up needing twice the amount of memory. For
|
||||
// example, if your scrypt parameters are such that you require 1GB and
|
||||
// you call it twice in a row, without this you end up allocating 2GB
|
||||
// since the first GB probably hasn't been released yet.
|
||||
debug.FreeOSMemory()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal returns the Parameters field marshalled into a format suitable for
|
||||
// storage. This result of this can be stored in clear text.
|
||||
func (sk *SecretKey) Marshal() []byte {
|
||||
params := &sk.Parameters
|
||||
|
||||
// The marshalled format for the the params is as follows:
|
||||
// <salt><digest><N><R><P>
|
||||
//
|
||||
// KeySize + sha256.Size + N (8 bytes) + R (8 bytes) + P (8 bytes)
|
||||
marshalled := make([]byte, KeySize+sha256.Size+24)
|
||||
|
||||
b := marshalled
|
||||
copy(b[:KeySize], params.Salt[:])
|
||||
b = b[KeySize:]
|
||||
copy(b[:sha256.Size], params.Digest[:])
|
||||
b = b[sha256.Size:]
|
||||
binary.LittleEndian.PutUint64(b[:8], uint64(params.N))
|
||||
b = b[8:]
|
||||
binary.LittleEndian.PutUint64(b[:8], uint64(params.R))
|
||||
b = b[8:]
|
||||
binary.LittleEndian.PutUint64(b[:8], uint64(params.P))
|
||||
|
||||
return marshalled
|
||||
}
|
||||
|
||||
// Unmarshal unmarshalls the parameters needed to derive the secret key from a
|
||||
// passphrase into sk.
|
||||
func (sk *SecretKey) Unmarshal(marshalled []byte) error {
|
||||
if sk.Key == nil {
|
||||
sk.Key = (*CryptoKey)(&[KeySize]byte{})
|
||||
}
|
||||
|
||||
// The marshalled format for the the params is as follows:
|
||||
// <salt><digest><N><R><P>
|
||||
//
|
||||
// KeySize + sha256.Size + N (8 bytes) + R (8 bytes) + P (8 bytes)
|
||||
if len(marshalled) != KeySize+sha256.Size+24 {
|
||||
return ErrMalformed
|
||||
}
|
||||
|
||||
params := &sk.Parameters
|
||||
copy(params.Salt[:], marshalled[:KeySize])
|
||||
marshalled = marshalled[KeySize:]
|
||||
copy(params.Digest[:], marshalled[:sha256.Size])
|
||||
marshalled = marshalled[sha256.Size:]
|
||||
params.N = int(binary.LittleEndian.Uint64(marshalled[:8]))
|
||||
marshalled = marshalled[8:]
|
||||
params.R = int(binary.LittleEndian.Uint64(marshalled[:8]))
|
||||
marshalled = marshalled[8:]
|
||||
params.P = int(binary.LittleEndian.Uint64(marshalled[:8]))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Zero zeroes the underlying secret key while leaving the parameters intact.
|
||||
// This effectively makes the key unusable until it is derived again via the
|
||||
// DeriveKey function.
|
||||
func (sk *SecretKey) Zero() {
|
||||
sk.Key.Zero()
|
||||
}
|
||||
|
||||
// DeriveKey derives the underlying secret key and ensures it matches the
|
||||
// expected digest. This should only be called after previously calling the
|
||||
// Zero function or on an initial Unmarshal.
|
||||
func (sk *SecretKey) DeriveKey(password *[]byte) error {
|
||||
if err := sk.deriveKey(password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// verify password
|
||||
digest := sha256.Sum256(sk.Key[:])
|
||||
if subtle.ConstantTimeCompare(digest[:], sk.Parameters.Digest[:]) != 1 {
|
||||
return ErrInvalidPassword
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts in bytes and returns a JSON blob.
|
||||
func (sk *SecretKey) Encrypt(in []byte) ([]byte, error) {
|
||||
return sk.Key.Encrypt(in)
|
||||
}
|
||||
|
||||
// Decrypt takes in a JSON blob and returns it's decrypted form.
|
||||
func (sk *SecretKey) Decrypt(in []byte) ([]byte, error) {
|
||||
return sk.Key.Decrypt(in)
|
||||
}
|
||||
|
||||
// NewSecretKey returns a SecretKey structure based on the passed parameters.
|
||||
func NewSecretKey(password *[]byte, N, r, p int) (*SecretKey, error) {
|
||||
sk := SecretKey{
|
||||
Key: (*CryptoKey)(&[KeySize]byte{}),
|
||||
}
|
||||
// setup parameters
|
||||
sk.Parameters.N = N
|
||||
sk.Parameters.R = r
|
||||
sk.Parameters.P = p
|
||||
_, err := io.ReadFull(prng, sk.Parameters.Salt[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// derive key
|
||||
err = sk.deriveKey(password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// store digest
|
||||
sk.Parameters.Digest = sha256.Sum256(sk.Key[:])
|
||||
|
||||
return &sk, nil
|
||||
}
|
||||
62
vendor/github.com/btcsuite/btcwallet/waddrmgr/README.md
generated
vendored
Normal file
62
vendor/github.com/btcsuite/btcwallet/waddrmgr/README.md
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
waddrmgr
|
||||
========
|
||||
|
||||
[]
|
||||
(https://travis-ci.org/btcsuite/btcwallet)
|
||||
|
||||
Package waddrmgr provides a secure hierarchical deterministic wallet address
|
||||
manager.
|
||||
|
||||
A suite of tests is provided to ensure proper functionality. See
|
||||
`test_coverage.txt` for the gocov coverage report. Alternatively, if you are
|
||||
running a POSIX OS, you can run the `cov_report.sh` script for a real-time
|
||||
report. Package waddrmgr is licensed under the liberal ISC license.
|
||||
|
||||
## Feature Overview
|
||||
|
||||
- BIP0032 hierarchical deterministic keys
|
||||
- BIP0043/BIP0044 multi-account hierarchy
|
||||
- Strong focus on security:
|
||||
- Fully encrypted database including public information such as addresses as
|
||||
well as private information such as private keys and scripts needed to
|
||||
redeem pay-to-script-hash transactions
|
||||
- Hardened against memory scraping through the use of actively clearing
|
||||
private material from memory when locked
|
||||
- Different crypto keys used for public, private, and script data
|
||||
- Ability for different passphrases for public and private data
|
||||
- Scrypt-based key derivation
|
||||
- NaCl-based secretbox cryptography (XSalsa20 and Poly1305)
|
||||
- Scalable design:
|
||||
- Multi-tier key design to allow instant password changes regardless of the
|
||||
number of addresses stored
|
||||
- Import WIF keys
|
||||
- Import pay-to-script-hash scripts for things such as multi-signature
|
||||
transactions
|
||||
- Ability to export a watching-only version which does not contain any private
|
||||
key material
|
||||
- Programmatically detectable errors, including encapsulation of errors from
|
||||
packages it relies on
|
||||
- Address synchronization capabilities
|
||||
- Comprehensive test coverage
|
||||
|
||||
## Documentation
|
||||
|
||||
[]
|
||||
(http://godoc.org/github.com/btcsuite/btcwallet/waddrmgr)
|
||||
|
||||
Full `go doc` style documentation for the project can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://godoc.org/github.com/btcsuite/btcwallet/waddrmgr
|
||||
|
||||
You can also view the documentation locally once the package is installed with
|
||||
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||
http://localhost:6060/pkg/github.com/btcsuite/btcwallet/waddrmgr
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ go get github.com/btcsuite/btcwallet/waddrmgr
|
||||
```
|
||||
|
||||
Package waddrmgr is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
648
vendor/github.com/btcsuite/btcwallet/waddrmgr/address.go
generated
vendored
Normal file
648
vendor/github.com/btcsuite/btcwallet/waddrmgr/address.go
generated
vendored
Normal file
@@ -0,0 +1,648 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package waddrmgr
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcwallet/internal/zero"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
// AddressType represents the various address types waddrmgr is currently able
|
||||
// to generate, and maintain.
|
||||
//
|
||||
// NOTE: These MUST be stable as they're used for scope address schema
|
||||
// recognition within the database.
|
||||
type AddressType uint8
|
||||
|
||||
const (
|
||||
// PubKeyHash is a regular p2pkh address.
|
||||
PubKeyHash AddressType = iota
|
||||
|
||||
// Script reprints a raw script address.
|
||||
Script
|
||||
|
||||
// RawPubKey is just raw public key to be used within scripts, This
|
||||
// type indicates that a scoped manager with this address type
|
||||
// shouldn't be consulted during historical rescans.
|
||||
RawPubKey
|
||||
|
||||
// NestedWitnessPubKey represents a p2wkh output nested within a p2sh
|
||||
// output. Using this address type, the wallet can receive funds from
|
||||
// other wallet's which don't yet recognize the new segwit standard
|
||||
// output types. Receiving funds to this address maintains the
|
||||
// scalability, and malleability fixes due to segwit in a backwards
|
||||
// compatible manner.
|
||||
NestedWitnessPubKey
|
||||
|
||||
// WitnessPubKey represents a p2wkh (pay-to-witness-key-hash) address
|
||||
// type.
|
||||
WitnessPubKey
|
||||
)
|
||||
|
||||
// ManagedAddress is an interface that provides acces to information regarding
|
||||
// an address managed by an address manager. Concrete implementations of this
|
||||
// type may provide further fields to provide information specific to that type
|
||||
// of address.
|
||||
type ManagedAddress interface {
|
||||
// Account returns the account the address is associated with.
|
||||
Account() uint32
|
||||
|
||||
// Address returns a btcutil.Address for the backing address.
|
||||
Address() btcutil.Address
|
||||
|
||||
// AddrHash returns the key or script hash related to the address
|
||||
AddrHash() []byte
|
||||
|
||||
// Imported returns true if the backing address was imported instead
|
||||
// of being part of an address chain.
|
||||
Imported() bool
|
||||
|
||||
// Internal returns true if the backing address was created for internal
|
||||
// use such as a change output of a transaction.
|
||||
Internal() bool
|
||||
|
||||
// Compressed returns true if the backing address is compressed.
|
||||
Compressed() bool
|
||||
|
||||
// Used returns true if the backing address has been used in a transaction.
|
||||
Used(ns walletdb.ReadBucket) bool
|
||||
|
||||
// AddrType returns the address type of the managed address. This can
|
||||
// be used to quickly discern the address type without further
|
||||
// processing
|
||||
AddrType() AddressType
|
||||
}
|
||||
|
||||
// ManagedPubKeyAddress extends ManagedAddress and additionally provides the
|
||||
// public and private keys for pubkey-based addresses.
|
||||
type ManagedPubKeyAddress interface {
|
||||
ManagedAddress
|
||||
|
||||
// PubKey returns the public key associated with the address.
|
||||
PubKey() *btcec.PublicKey
|
||||
|
||||
// ExportPubKey returns the public key associated with the address
|
||||
// serialized as a hex encoded string.
|
||||
ExportPubKey() string
|
||||
|
||||
// PrivKey returns the private key for the address. It can fail if the
|
||||
// address manager is watching-only or locked, or the address does not
|
||||
// have any keys.
|
||||
PrivKey() (*btcec.PrivateKey, error)
|
||||
|
||||
// ExportPrivKey returns the private key associated with the address
|
||||
// serialized as Wallet Import Format (WIF).
|
||||
ExportPrivKey() (*btcutil.WIF, error)
|
||||
|
||||
// DerivationInfo contains the information required to derive the key
|
||||
// that backs the address via traditional methods from the HD root. For
|
||||
// imported keys, the first value will be set to false to indicate that
|
||||
// we don't know exactly how the key was derived.
|
||||
DerivationInfo() (KeyScope, DerivationPath, bool)
|
||||
}
|
||||
|
||||
// ManagedScriptAddress extends ManagedAddress and represents a pay-to-script-hash
|
||||
// style of bitcoin addresses. It additionally provides information about the
|
||||
// script.
|
||||
type ManagedScriptAddress interface {
|
||||
ManagedAddress
|
||||
|
||||
// Script returns the script associated with the address.
|
||||
Script() ([]byte, error)
|
||||
}
|
||||
|
||||
// managedAddress represents a public key address. It also may or may not have
|
||||
// the private key associated with the public key.
|
||||
type managedAddress struct {
|
||||
manager *ScopedKeyManager
|
||||
derivationPath DerivationPath
|
||||
address btcutil.Address
|
||||
imported bool
|
||||
internal bool
|
||||
compressed bool
|
||||
used bool
|
||||
addrType AddressType
|
||||
pubKey *btcec.PublicKey
|
||||
privKeyEncrypted []byte
|
||||
privKeyCT []byte // non-nil if unlocked
|
||||
privKeyMutex sync.Mutex
|
||||
}
|
||||
|
||||
// Enforce managedAddress satisfies the ManagedPubKeyAddress interface.
|
||||
var _ ManagedPubKeyAddress = (*managedAddress)(nil)
|
||||
|
||||
// unlock decrypts and stores a pointer to the associated private key. It will
|
||||
// fail if the key is invalid or the encrypted private key is not available.
|
||||
// The returned clear text private key will always be a copy that may be safely
|
||||
// used by the caller without worrying about it being zeroed during an address
|
||||
// lock.
|
||||
func (a *managedAddress) unlock(key EncryptorDecryptor) ([]byte, error) {
|
||||
// Protect concurrent access to clear text private key.
|
||||
a.privKeyMutex.Lock()
|
||||
defer a.privKeyMutex.Unlock()
|
||||
|
||||
if len(a.privKeyCT) == 0 {
|
||||
privKey, err := key.Decrypt(a.privKeyEncrypted)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to decrypt private key for "+
|
||||
"%s", a.address)
|
||||
return nil, managerError(ErrCrypto, str, err)
|
||||
}
|
||||
|
||||
a.privKeyCT = privKey
|
||||
}
|
||||
|
||||
privKeyCopy := make([]byte, len(a.privKeyCT))
|
||||
copy(privKeyCopy, a.privKeyCT)
|
||||
return privKeyCopy, nil
|
||||
}
|
||||
|
||||
// lock zeroes the associated clear text private key.
|
||||
func (a *managedAddress) lock() {
|
||||
// Zero and nil the clear text private key associated with this
|
||||
// address.
|
||||
a.privKeyMutex.Lock()
|
||||
zero.Bytes(a.privKeyCT)
|
||||
a.privKeyCT = nil
|
||||
a.privKeyMutex.Unlock()
|
||||
}
|
||||
|
||||
// Account returns the account number the address is associated with.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *managedAddress) Account() uint32 {
|
||||
return a.derivationPath.Account
|
||||
}
|
||||
|
||||
// AddrType returns the address type of the managed address. This can be used
|
||||
// to quickly discern the address type without further processing
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *managedAddress) AddrType() AddressType {
|
||||
return a.addrType
|
||||
}
|
||||
|
||||
// Address returns the btcutil.Address which represents the managed address.
|
||||
// This will be a pay-to-pubkey-hash address.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *managedAddress) Address() btcutil.Address {
|
||||
return a.address
|
||||
}
|
||||
|
||||
// AddrHash returns the public key hash for the address.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *managedAddress) AddrHash() []byte {
|
||||
var hash []byte
|
||||
|
||||
switch n := a.address.(type) {
|
||||
case *btcutil.AddressPubKeyHash:
|
||||
hash = n.Hash160()[:]
|
||||
case *btcutil.AddressScriptHash:
|
||||
hash = n.Hash160()[:]
|
||||
case *btcutil.AddressWitnessPubKeyHash:
|
||||
hash = n.Hash160()[:]
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
// Imported returns true if the address was imported instead of being part of an
|
||||
// address chain.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *managedAddress) Imported() bool {
|
||||
return a.imported
|
||||
}
|
||||
|
||||
// Internal returns true if the address was created for internal use such as a
|
||||
// change output of a transaction.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *managedAddress) Internal() bool {
|
||||
return a.internal
|
||||
}
|
||||
|
||||
// Compressed returns true if the address is compressed.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *managedAddress) Compressed() bool {
|
||||
return a.compressed
|
||||
}
|
||||
|
||||
// Used returns true if the address has been used in a transaction.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *managedAddress) Used(ns walletdb.ReadBucket) bool {
|
||||
return a.manager.fetchUsed(ns, a.AddrHash())
|
||||
}
|
||||
|
||||
// PubKey returns the public key associated with the address.
|
||||
//
|
||||
// This is part of the ManagedPubKeyAddress interface implementation.
|
||||
func (a *managedAddress) PubKey() *btcec.PublicKey {
|
||||
return a.pubKey
|
||||
}
|
||||
|
||||
// pubKeyBytes returns the serialized public key bytes for the managed address
|
||||
// based on whether or not the managed address is marked as compressed.
|
||||
func (a *managedAddress) pubKeyBytes() []byte {
|
||||
if a.compressed {
|
||||
return a.pubKey.SerializeCompressed()
|
||||
}
|
||||
return a.pubKey.SerializeUncompressed()
|
||||
}
|
||||
|
||||
// ExportPubKey returns the public key associated with the address
|
||||
// serialized as a hex encoded string.
|
||||
//
|
||||
// This is part of the ManagedPubKeyAddress interface implementation.
|
||||
func (a *managedAddress) ExportPubKey() string {
|
||||
return hex.EncodeToString(a.pubKeyBytes())
|
||||
}
|
||||
|
||||
// PrivKey returns the private key for the address. It can fail if the address
|
||||
// manager is watching-only or locked, or the address does not have any keys.
|
||||
//
|
||||
// This is part of the ManagedPubKeyAddress interface implementation.
|
||||
func (a *managedAddress) PrivKey() (*btcec.PrivateKey, error) {
|
||||
// No private keys are available for a watching-only address manager.
|
||||
if a.manager.rootManager.WatchOnly() {
|
||||
return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil)
|
||||
}
|
||||
|
||||
a.manager.mtx.Lock()
|
||||
defer a.manager.mtx.Unlock()
|
||||
|
||||
// Account manager must be unlocked to decrypt the private key.
|
||||
if a.manager.rootManager.IsLocked() {
|
||||
return nil, managerError(ErrLocked, errLocked, nil)
|
||||
}
|
||||
|
||||
// Decrypt the key as needed. Also, make sure it's a copy since the
|
||||
// private key stored in memory can be cleared at any time. Otherwise
|
||||
// the returned private key could be invalidated from under the caller.
|
||||
privKeyCopy, err := a.unlock(a.manager.rootManager.cryptoKeyPriv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privKeyCopy)
|
||||
zero.Bytes(privKeyCopy)
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
// ExportPrivKey returns the private key associated with the address in Wallet
|
||||
// Import Format (WIF).
|
||||
//
|
||||
// This is part of the ManagedPubKeyAddress interface implementation.
|
||||
func (a *managedAddress) ExportPrivKey() (*btcutil.WIF, error) {
|
||||
pk, err := a.PrivKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return btcutil.NewWIF(pk, a.manager.rootManager.chainParams, a.compressed)
|
||||
}
|
||||
|
||||
// Derivationinfo contains the information required to derive the key that
|
||||
// backs the address via traditional methods from the HD root. For imported
|
||||
// keys, the first value will be set to false to indicate that we don't know
|
||||
// exactly how the key was derived.
|
||||
//
|
||||
// This is part of the ManagedPubKeyAddress interface implementation.
|
||||
func (a *managedAddress) DerivationInfo() (KeyScope, DerivationPath, bool) {
|
||||
var (
|
||||
scope KeyScope
|
||||
path DerivationPath
|
||||
)
|
||||
|
||||
// If this key is imported, then we can't return any information as we
|
||||
// don't know precisely how the key was derived.
|
||||
if a.imported {
|
||||
return scope, path, false
|
||||
}
|
||||
|
||||
return a.manager.Scope(), a.derivationPath, true
|
||||
}
|
||||
|
||||
// newManagedAddressWithoutPrivKey returns a new managed address based on the
|
||||
// passed account, public key, and whether or not the public key should be
|
||||
// compressed.
|
||||
func newManagedAddressWithoutPrivKey(m *ScopedKeyManager,
|
||||
derivationPath DerivationPath, pubKey *btcec.PublicKey, compressed bool,
|
||||
addrType AddressType) (*managedAddress, error) {
|
||||
|
||||
// Create a pay-to-pubkey-hash address from the public key.
|
||||
var pubKeyHash []byte
|
||||
if compressed {
|
||||
pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed())
|
||||
} else {
|
||||
pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed())
|
||||
}
|
||||
|
||||
var address btcutil.Address
|
||||
var err error
|
||||
|
||||
switch addrType {
|
||||
|
||||
case NestedWitnessPubKey:
|
||||
// For this address type we'l generate an address which is
|
||||
// backwards compatible to Bitcoin nodes running 0.6.0 onwards, but
|
||||
// allows us to take advantage of segwit's scripting improvments,
|
||||
// and malleability fixes.
|
||||
|
||||
// First, we'll generate a normal p2wkh address from the pubkey hash.
|
||||
witAddr, err := btcutil.NewAddressWitnessPubKeyHash(
|
||||
pubKeyHash, m.rootManager.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next we'll generate the witness program which can be used as a
|
||||
// pkScript to pay to this generated address.
|
||||
witnessProgram, err := txscript.PayToAddrScript(witAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, we'll use the witness program itself as the pre-image
|
||||
// to a p2sh address. In order to spend, we first use the
|
||||
// witnessProgram as the sigScript, then present the proper
|
||||
// <sig, pubkey> pair as the witness.
|
||||
address, err = btcutil.NewAddressScriptHash(
|
||||
witnessProgram, m.rootManager.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case PubKeyHash:
|
||||
address, err = btcutil.NewAddressPubKeyHash(
|
||||
pubKeyHash, m.rootManager.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case WitnessPubKey:
|
||||
address, err = btcutil.NewAddressWitnessPubKeyHash(
|
||||
pubKeyHash, m.rootManager.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &managedAddress{
|
||||
manager: m,
|
||||
address: address,
|
||||
derivationPath: derivationPath,
|
||||
imported: false,
|
||||
internal: false,
|
||||
addrType: addrType,
|
||||
compressed: compressed,
|
||||
pubKey: pubKey,
|
||||
privKeyEncrypted: nil,
|
||||
privKeyCT: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newManagedAddress returns a new managed address based on the passed account,
|
||||
// private key, and whether or not the public key is compressed. The managed
|
||||
// address will have access to the private and public keys.
|
||||
func newManagedAddress(s *ScopedKeyManager, derivationPath DerivationPath,
|
||||
privKey *btcec.PrivateKey, compressed bool,
|
||||
addrType AddressType) (*managedAddress, error) {
|
||||
|
||||
// Encrypt the private key.
|
||||
//
|
||||
// NOTE: The privKeyBytes here are set into the managed address which
|
||||
// are cleared when locked, so they aren't cleared here.
|
||||
privKeyBytes := privKey.Serialize()
|
||||
privKeyEncrypted, err := s.rootManager.cryptoKeyPriv.Encrypt(privKeyBytes)
|
||||
if err != nil {
|
||||
str := "failed to encrypt private key"
|
||||
return nil, managerError(ErrCrypto, str, err)
|
||||
}
|
||||
|
||||
// Leverage the code to create a managed address without a private key
|
||||
// and then add the private key to it.
|
||||
ecPubKey := (*btcec.PublicKey)(&privKey.PublicKey)
|
||||
managedAddr, err := newManagedAddressWithoutPrivKey(
|
||||
s, derivationPath, ecPubKey, compressed, addrType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
managedAddr.privKeyEncrypted = privKeyEncrypted
|
||||
managedAddr.privKeyCT = privKeyBytes
|
||||
|
||||
return managedAddr, nil
|
||||
}
|
||||
|
||||
// newManagedAddressFromExtKey returns a new managed address based on the passed
|
||||
// account and extended key. The managed address will have access to the
|
||||
// private and public keys if the provided extended key is private, otherwise it
|
||||
// will only have access to the public key.
|
||||
func newManagedAddressFromExtKey(s *ScopedKeyManager,
|
||||
derivationPath DerivationPath, key *hdkeychain.ExtendedKey,
|
||||
addrType AddressType) (*managedAddress, error) {
|
||||
|
||||
// Create a new managed address based on the public or private key
|
||||
// depending on whether the generated key is private.
|
||||
var managedAddr *managedAddress
|
||||
if key.IsPrivate() {
|
||||
privKey, err := key.ECPrivKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure the temp private key big integer is cleared after
|
||||
// use.
|
||||
managedAddr, err = newManagedAddress(
|
||||
s, derivationPath, privKey, true, addrType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
pubKey, err := key.ECPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
managedAddr, err = newManagedAddressWithoutPrivKey(
|
||||
s, derivationPath, pubKey, true,
|
||||
addrType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return managedAddr, nil
|
||||
}
|
||||
|
||||
// scriptAddress represents a pay-to-script-hash address.
|
||||
type scriptAddress struct {
|
||||
manager *ScopedKeyManager
|
||||
account uint32
|
||||
address *btcutil.AddressScriptHash
|
||||
scriptEncrypted []byte
|
||||
scriptCT []byte
|
||||
scriptMutex sync.Mutex
|
||||
used bool
|
||||
}
|
||||
|
||||
// Enforce scriptAddress satisfies the ManagedScriptAddress interface.
|
||||
var _ ManagedScriptAddress = (*scriptAddress)(nil)
|
||||
|
||||
// unlock decrypts and stores the associated script. It will fail if the key is
|
||||
// invalid or the encrypted script is not available. The returned clear text
|
||||
// script will always be a copy that may be safely used by the caller without
|
||||
// worrying about it being zeroed during an address lock.
|
||||
func (a *scriptAddress) unlock(key EncryptorDecryptor) ([]byte, error) {
|
||||
// Protect concurrent access to clear text script.
|
||||
a.scriptMutex.Lock()
|
||||
defer a.scriptMutex.Unlock()
|
||||
|
||||
if len(a.scriptCT) == 0 {
|
||||
script, err := key.Decrypt(a.scriptEncrypted)
|
||||
if err != nil {
|
||||
str := fmt.Sprintf("failed to decrypt script for %s",
|
||||
a.address)
|
||||
return nil, managerError(ErrCrypto, str, err)
|
||||
}
|
||||
|
||||
a.scriptCT = script
|
||||
}
|
||||
|
||||
scriptCopy := make([]byte, len(a.scriptCT))
|
||||
copy(scriptCopy, a.scriptCT)
|
||||
return scriptCopy, nil
|
||||
}
|
||||
|
||||
// lock zeroes the associated clear text private key.
|
||||
func (a *scriptAddress) lock() {
|
||||
// Zero and nil the clear text script associated with this address.
|
||||
a.scriptMutex.Lock()
|
||||
zero.Bytes(a.scriptCT)
|
||||
a.scriptCT = nil
|
||||
a.scriptMutex.Unlock()
|
||||
}
|
||||
|
||||
// Account returns the account the address is associated with. This will always
|
||||
// be the ImportedAddrAccount constant for script addresses.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *scriptAddress) Account() uint32 {
|
||||
return a.account
|
||||
}
|
||||
|
||||
// AddrType returns the address type of the managed address. This can be used
|
||||
// to quickly discern the address type without further processing
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *scriptAddress) AddrType() AddressType {
|
||||
return Script
|
||||
}
|
||||
|
||||
// Address returns the btcutil.Address which represents the managed address.
|
||||
// This will be a pay-to-script-hash address.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *scriptAddress) Address() btcutil.Address {
|
||||
return a.address
|
||||
}
|
||||
|
||||
// AddrHash returns the script hash for the address.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *scriptAddress) AddrHash() []byte {
|
||||
return a.address.Hash160()[:]
|
||||
}
|
||||
|
||||
// Imported always returns true since script addresses are always imported
|
||||
// addresses and not part of any chain.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *scriptAddress) Imported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Internal always returns false since script addresses are always imported
|
||||
// addresses and not part of any chain in order to be for internal use.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *scriptAddress) Internal() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compressed returns false since script addresses are never compressed.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *scriptAddress) Compressed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Used returns true if the address has been used in a transaction.
|
||||
//
|
||||
// This is part of the ManagedAddress interface implementation.
|
||||
func (a *scriptAddress) Used(ns walletdb.ReadBucket) bool {
|
||||
return a.manager.fetchUsed(ns, a.AddrHash())
|
||||
}
|
||||
|
||||
// Script returns the script associated with the address.
|
||||
//
|
||||
// This implements the ScriptAddress interface.
|
||||
func (a *scriptAddress) Script() ([]byte, error) {
|
||||
// No script is available for a watching-only address manager.
|
||||
if a.manager.rootManager.WatchOnly() {
|
||||
return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil)
|
||||
}
|
||||
|
||||
a.manager.mtx.Lock()
|
||||
defer a.manager.mtx.Unlock()
|
||||
|
||||
// Account manager must be unlocked to decrypt the script.
|
||||
if a.manager.rootManager.IsLocked() {
|
||||
return nil, managerError(ErrLocked, errLocked, nil)
|
||||
}
|
||||
|
||||
// Decrypt the script as needed. Also, make sure it's a copy since the
|
||||
// script stored in memory can be cleared at any time. Otherwise,
|
||||
// the returned script could be invalidated from under the caller.
|
||||
return a.unlock(a.manager.rootManager.cryptoKeyScript)
|
||||
}
|
||||
|
||||
// newScriptAddress initializes and returns a new pay-to-script-hash address.
|
||||
func newScriptAddress(m *ScopedKeyManager, account uint32, scriptHash,
|
||||
scriptEncrypted []byte) (*scriptAddress, error) {
|
||||
|
||||
address, err := btcutil.NewAddressScriptHashFromHash(
|
||||
scriptHash, m.rootManager.chainParams,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &scriptAddress{
|
||||
manager: m,
|
||||
account: account,
|
||||
address: address,
|
||||
scriptEncrypted: scriptEncrypted,
|
||||
}, nil
|
||||
}
|
||||
17
vendor/github.com/btcsuite/btcwallet/waddrmgr/cov_report.sh
generated
vendored
Normal file
17
vendor/github.com/btcsuite/btcwallet/waddrmgr/cov_report.sh
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script uses gocov to generate a test coverage report.
|
||||
# The gocov tool my be obtained with the following command:
|
||||
# go get github.com/axw/gocov/gocov
|
||||
#
|
||||
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||
|
||||
# Check for gocov.
|
||||
type gocov >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo >&2 "This script requires the gocov tool."
|
||||
echo >&2 "You may obtain it with the following command:"
|
||||
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||
exit 1
|
||||
fi
|
||||
gocov test | gocov report
|
||||
2280
vendor/github.com/btcsuite/btcwallet/waddrmgr/db.go
generated
vendored
Normal file
2280
vendor/github.com/btcsuite/btcwallet/waddrmgr/db.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
155
vendor/github.com/btcsuite/btcwallet/waddrmgr/doc.go
generated
vendored
Normal file
155
vendor/github.com/btcsuite/btcwallet/waddrmgr/doc.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package waddrmgr provides a secure hierarchical deterministic wallet address
|
||||
manager.
|
||||
|
||||
Overview
|
||||
|
||||
One of the fundamental jobs of a wallet is to manage addresses, private keys,
|
||||
and script data associated with them. At a high level, this package provides
|
||||
the facilities to perform this task with a focus on security and also allows
|
||||
recovery through the use of hierarchical deterministic keys (BIP0032) generated
|
||||
from a caller provided seed. The specific structure used is as described in
|
||||
BIP0044. This setup means as long as the user writes the seed down (even better
|
||||
is to use a mnemonic for the seed), all their addresses and private keys can be
|
||||
regenerated from the seed.
|
||||
|
||||
There are two master keys which are protected by two independent passphrases.
|
||||
One is intended for public facing data, while the other is intended for private
|
||||
data. The public password can be hardcoded for callers who don't want the
|
||||
additional public data protection or the same password can be used if a single
|
||||
password is desired. These choices provide a usability versus security
|
||||
tradeoff. However, keep in mind that extended hd keys, as called out in BIP0032
|
||||
need to be handled more carefully than normal EC public keys because they can be
|
||||
used to generate all future addresses. While this is part of what makes them
|
||||
attractive, it also means an attacker getting access to your extended public key
|
||||
for an account will allow them to know all derived addresses you will use and
|
||||
hence reduces privacy. For this reason, it is highly recommended that you do
|
||||
not hard code a password which allows any attacker who gets a copy of your
|
||||
address manager database to access your effectively plain text extended public
|
||||
keys.
|
||||
|
||||
Each master key in turn protects the three real encryption keys (called crypto
|
||||
keys) for public, private, and script data. Some examples include payment
|
||||
addresses, extended hd keys, and scripts associated with pay-to-script-hash
|
||||
addresses. This scheme makes changing passphrases more efficient since only the
|
||||
crypto keys need to be re-encrypted versus every single piece of information
|
||||
(which is what is needed for *rekeying*). This results in a fully encrypted
|
||||
database where access to it does not compromise address, key, or script privacy.
|
||||
This differs from the handling by other wallets at the time of this writing in
|
||||
that they divulge your addresses, and worse, some even expose the chain code
|
||||
which can be used by the attacker to know all future addresses that will be
|
||||
used.
|
||||
|
||||
The address manager is also hardened against memory scrapers. This is
|
||||
accomplished by typically having the address manager locked meaning no private
|
||||
keys or scripts are in memory. Unlocking the address manager causes the crypto
|
||||
private and script keys to be decrypted and loaded in memory which in turn are
|
||||
used to decrypt private keys and scripts on demand. Relocking the address
|
||||
manager actively zeros all private material from memory. In addition, temp
|
||||
private key material used internally is zeroed as soon as it's used.
|
||||
|
||||
Locking and Unlocking
|
||||
|
||||
As previously mentioned, this package provide facilities for locking and
|
||||
unlocking the address manager to protect access to private material and remove
|
||||
it from memory when locked. The Lock, Unlock, and IsLocked functions are used
|
||||
for this purpose.
|
||||
|
||||
Creating a New Address Manager
|
||||
|
||||
A new address manager is created via the Create function. This function accepts
|
||||
a wallet database namespace, passphrases, network, and perhaps most importantly,
|
||||
a cryptographically random seed which is used to generate the master node of the
|
||||
hierarchical deterministic keychain which allows all addresses and private keys
|
||||
to be recovered with only the seed. The GenerateSeed function in the hdkeychain
|
||||
package can be used as a convenient way to create a random seed for use with
|
||||
this function. The address manager is locked immediately upon being created.
|
||||
|
||||
Opening an Existing Address Manager
|
||||
|
||||
An existing address manager is opened via the Open function. This function
|
||||
accepts an existing wallet database namespace, the public passphrase, and
|
||||
network. The address manager is opened locked as expected since the open
|
||||
function does not take the private passphrase to unlock it.
|
||||
|
||||
Closing the Address Manager
|
||||
|
||||
The Close method should be called on the address manager when the caller is done
|
||||
with it. While it is not required, it is recommended because it sanely shuts
|
||||
down the database and ensures all private and public key material is purged from
|
||||
memory.
|
||||
|
||||
Managed Addresses
|
||||
|
||||
Each address returned by the address manager satisifies the ManagedAddress
|
||||
interface as well as either the ManagedPubKeyAddress or ManagedScriptAddress
|
||||
interfaces. These interfaces provide the means to obtain relevant information
|
||||
about the addresses such as their private keys and scripts.
|
||||
|
||||
Chained Addresses
|
||||
|
||||
Most callers will make use of the chained addresses for normal operations.
|
||||
Internal addresses are intended for internal wallet uses such as change outputs,
|
||||
while external addresses are intended for uses such payment addresses that are
|
||||
shared. The NextInternalAddresses and NextExternalAddresses functions provide
|
||||
the means to acquire one or more of the next addresses that have not already
|
||||
been provided. In addition, the LastInternalAddress and LastExternalAddress
|
||||
functions can be used to get the most recently provided internal and external
|
||||
address, respectively.
|
||||
|
||||
Requesting Existing Addresses
|
||||
|
||||
In addition to generating new addresses, access to old addresses is often
|
||||
required. Most notably, to sign transactions in order to redeem them. The
|
||||
Address function provides this capability and returns a ManagedAddress.
|
||||
|
||||
Importing Addresses
|
||||
|
||||
While the recommended approach is to use the chained addresses discussed above
|
||||
because they can be deterministically regenerated to avoid losing funds as long
|
||||
as the user has the master seed, there are many addresses that already exist,
|
||||
and as a result, this package provides the ability to import existing private
|
||||
keys in Wallet Import Format (WIF) and hence the associated public key and
|
||||
address.
|
||||
|
||||
Importing Scripts
|
||||
|
||||
In order to support pay-to-script-hash transactions, the script must be securely
|
||||
stored as it is needed to redeem the transaction. This can be useful for a
|
||||
variety of scenarios, however the most common use is currently multi-signature
|
||||
transactions.
|
||||
|
||||
Syncing
|
||||
|
||||
The address manager also supports storing and retrieving a block hash and height
|
||||
which the manager is known to have all addresses synced through. The manager
|
||||
itself does not have any notion of which addresses are synced or not. It only
|
||||
provides the storage as a convenience for the caller.
|
||||
|
||||
Network
|
||||
|
||||
The address manager must be associated with a given network in order to provide
|
||||
appropriate addresses and reject imported addresses and scripts which don't
|
||||
apply to the associated network.
|
||||
|
||||
Errors
|
||||
|
||||
All errors returned from this package are of type ManagerError. This allows the
|
||||
caller to programmatically ascertain the specific reasons for failure by
|
||||
examining the ErrorCode field of the type asserted ManagerError. For certain
|
||||
error codes, as documented by the specific error codes, the underlying error
|
||||
will be contained in the Err field.
|
||||
|
||||
Bitcoin Improvement Proposals
|
||||
|
||||
This package includes concepts outlined by the following BIPs:
|
||||
|
||||
BIP0032 (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
||||
BIP0043 (https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki)
|
||||
BIP0044 (https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
|
||||
*/
|
||||
package waddrmgr
|
||||
219
vendor/github.com/btcsuite/btcwallet/waddrmgr/error.go
generated
vendored
Normal file
219
vendor/github.com/btcsuite/btcwallet/waddrmgr/error.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package waddrmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
)
|
||||
|
||||
var (
|
||||
// errAlreadyExists is the common error description used for the
|
||||
// ErrAlreadyExists error code.
|
||||
errAlreadyExists = "the specified address manager already exists"
|
||||
|
||||
// errCoinTypeTooHigh is the common error description used for the
|
||||
// ErrCoinTypeTooHigh error code.
|
||||
errCoinTypeTooHigh = "coin type may not exceed " +
|
||||
strconv.FormatUint(hdkeychain.HardenedKeyStart-1, 10)
|
||||
|
||||
// errAcctTooHigh is the common error description used for the
|
||||
// ErrAccountNumTooHigh error code.
|
||||
errAcctTooHigh = "account number may not exceed " +
|
||||
strconv.FormatUint(hdkeychain.HardenedKeyStart-1, 10)
|
||||
|
||||
// errLocked is the common error description used for the ErrLocked
|
||||
// error code.
|
||||
errLocked = "address manager is locked"
|
||||
|
||||
// errWatchingOnly is the common error description used for the
|
||||
// ErrWatchingOnly error code.
|
||||
errWatchingOnly = "address manager is watching-only"
|
||||
)
|
||||
|
||||
// ErrorCode identifies a kind of error.
|
||||
type ErrorCode int
|
||||
|
||||
// These constants are used to identify a specific ManagerError.
|
||||
const (
|
||||
// ErrDatabase indicates an error with the underlying database. When
|
||||
// this error code is set, the Err field of the ManagerError will be
|
||||
// set to the underlying error returned from the database.
|
||||
ErrDatabase ErrorCode = iota
|
||||
|
||||
// ErrUpgrade indicates the manager needs to be upgraded. This should
|
||||
// not happen in practice unless the version number has been increased
|
||||
// and there is not yet any code written to upgrade.
|
||||
ErrUpgrade
|
||||
|
||||
// ErrKeyChain indicates an error with the key chain typically either
|
||||
// due to the inability to create an extended key or deriving a child
|
||||
// extended key. When this error code is set, the Err field of the
|
||||
// ManagerError will be set to the underlying error.
|
||||
ErrKeyChain
|
||||
|
||||
// ErrCrypto indicates an error with the cryptography related operations
|
||||
// such as decrypting or encrypting data, parsing an EC public key,
|
||||
// or deriving a secret key from a password. When this error code is
|
||||
// set, the Err field of the ManagerError will be set to the underlying
|
||||
// error.
|
||||
ErrCrypto
|
||||
|
||||
// ErrInvalidKeyType indicates an error where an invalid crypto
|
||||
// key type has been selected.
|
||||
ErrInvalidKeyType
|
||||
|
||||
// ErrNoExist indicates that the specified database does not exist.
|
||||
ErrNoExist
|
||||
|
||||
// ErrAlreadyExists indicates that the specified database already exists.
|
||||
ErrAlreadyExists
|
||||
|
||||
// ErrCoinTypeTooHigh indicates that the coin type specified in the provided
|
||||
// network parameters is higher than the max allowed value as defined
|
||||
// by the maxCoinType constant.
|
||||
ErrCoinTypeTooHigh
|
||||
|
||||
// ErrAccountNumTooHigh indicates that the specified account number is higher
|
||||
// than the max allowed value as defined by the MaxAccountNum constant.
|
||||
ErrAccountNumTooHigh
|
||||
|
||||
// ErrLocked indicates that an operation, which requires the account
|
||||
// manager to be unlocked, was requested on a locked account manager.
|
||||
ErrLocked
|
||||
|
||||
// ErrWatchingOnly indicates that an operation, which requires the
|
||||
// account manager to have access to private data, was requested on
|
||||
// a watching-only account manager.
|
||||
ErrWatchingOnly
|
||||
|
||||
// ErrInvalidAccount indicates that the requested account is not valid.
|
||||
ErrInvalidAccount
|
||||
|
||||
// ErrAddressNotFound indicates that the requested address is not known to
|
||||
// the account manager.
|
||||
ErrAddressNotFound
|
||||
|
||||
// ErrAccountNotFound indicates that the requested account is not known to
|
||||
// the account manager.
|
||||
ErrAccountNotFound
|
||||
|
||||
// ErrDuplicateAddress indicates an address already exists.
|
||||
ErrDuplicateAddress
|
||||
|
||||
// ErrDuplicateAccount indicates an account already exists.
|
||||
ErrDuplicateAccount
|
||||
|
||||
// ErrTooManyAddresses indicates that more than the maximum allowed number of
|
||||
// addresses per account have been requested.
|
||||
ErrTooManyAddresses
|
||||
|
||||
// ErrWrongPassphrase indicates that the specified passphrase is incorrect.
|
||||
// This could be for either public or private master keys.
|
||||
ErrWrongPassphrase
|
||||
|
||||
// ErrWrongNet indicates that the private key to be imported is not for the
|
||||
// the same network the account manager is configured for.
|
||||
ErrWrongNet
|
||||
|
||||
// ErrCallBackBreak is used to break from a callback function passed
|
||||
// down to the manager.
|
||||
ErrCallBackBreak
|
||||
|
||||
// ErrEmptyPassphrase indicates that the private passphrase was refused
|
||||
// due to being empty.
|
||||
ErrEmptyPassphrase
|
||||
|
||||
// ErrScopeNotFound is returned when a target scope cannot be found
|
||||
// within the database.
|
||||
ErrScopeNotFound
|
||||
|
||||
// ErrBirthdayBlockNotSet is returned when we attempt to retrieve the
|
||||
// wallet's birthday but it has not been set yet.
|
||||
ErrBirthdayBlockNotSet
|
||||
|
||||
// ErrBlockNotFound is returned when we attempt to retrieve the hash for
|
||||
// a block that we do not know of.
|
||||
ErrBlockNotFound
|
||||
)
|
||||
|
||||
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||
var errorCodeStrings = map[ErrorCode]string{
|
||||
ErrDatabase: "ErrDatabase",
|
||||
ErrUpgrade: "ErrUpgrade",
|
||||
ErrKeyChain: "ErrKeyChain",
|
||||
ErrCrypto: "ErrCrypto",
|
||||
ErrInvalidKeyType: "ErrInvalidKeyType",
|
||||
ErrNoExist: "ErrNoExist",
|
||||
ErrAlreadyExists: "ErrAlreadyExists",
|
||||
ErrCoinTypeTooHigh: "ErrCoinTypeTooHigh",
|
||||
ErrAccountNumTooHigh: "ErrAccountNumTooHigh",
|
||||
ErrLocked: "ErrLocked",
|
||||
ErrWatchingOnly: "ErrWatchingOnly",
|
||||
ErrInvalidAccount: "ErrInvalidAccount",
|
||||
ErrAddressNotFound: "ErrAddressNotFound",
|
||||
ErrAccountNotFound: "ErrAccountNotFound",
|
||||
ErrDuplicateAddress: "ErrDuplicateAddress",
|
||||
ErrDuplicateAccount: "ErrDuplicateAccount",
|
||||
ErrTooManyAddresses: "ErrTooManyAddresses",
|
||||
ErrWrongPassphrase: "ErrWrongPassphrase",
|
||||
ErrWrongNet: "ErrWrongNet",
|
||||
ErrCallBackBreak: "ErrCallBackBreak",
|
||||
ErrEmptyPassphrase: "ErrEmptyPassphrase",
|
||||
ErrScopeNotFound: "ErrScopeNotFound",
|
||||
}
|
||||
|
||||
// String returns the ErrorCode as a human-readable name.
|
||||
func (e ErrorCode) String() string {
|
||||
if s := errorCodeStrings[e]; s != "" {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
|
||||
}
|
||||
|
||||
// ManagerError provides a single type for errors that can happen during address
|
||||
// manager operation. It is used to indicate several types of failures
|
||||
// including errors with caller requests such as invalid accounts or requesting
|
||||
// private keys against a locked address manager, errors with the database
|
||||
// (ErrDatabase), errors with key chain derivation (ErrKeyChain), and errors
|
||||
// related to crypto (ErrCrypto).
|
||||
//
|
||||
// The caller can use type assertions to determine if an error is a ManagerError
|
||||
// and access the ErrorCode field to ascertain the specific reason for the
|
||||
// failure.
|
||||
//
|
||||
// The ErrDatabase, ErrKeyChain, and ErrCrypto error codes will also have the
|
||||
// Err field set with the underlying error.
|
||||
type ManagerError struct {
|
||||
ErrorCode ErrorCode // Describes the kind of error
|
||||
Description string // Human readable description of the issue
|
||||
Err error // Underlying error
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e ManagerError) Error() string {
|
||||
if e.Err != nil {
|
||||
return e.Description + ": " + e.Err.Error()
|
||||
}
|
||||
return e.Description
|
||||
}
|
||||
|
||||
// managerError creates a ManagerError given a set of arguments.
|
||||
func managerError(c ErrorCode, desc string, err error) ManagerError {
|
||||
return ManagerError{ErrorCode: c, Description: desc, Err: err}
|
||||
}
|
||||
|
||||
// Break is a global err used to signal a break from the callback
|
||||
// function by returning an error with the code ErrCallBackBreak
|
||||
var Break = managerError(ErrCallBackBreak, "callback break", nil)
|
||||
|
||||
// IsError returns whether the error is a ManagerError with a matching error
|
||||
// code.
|
||||
func IsError(err error, code ErrorCode) bool {
|
||||
e, ok := err.(ManagerError)
|
||||
return ok && e.ErrorCode == code
|
||||
}
|
||||
43
vendor/github.com/btcsuite/btcwallet/waddrmgr/log.go
generated
vendored
Normal file
43
vendor/github.com/btcsuite/btcwallet/waddrmgr/log.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package waddrmgr
|
||||
|
||||
import "github.com/btcsuite/btclog"
|
||||
|
||||
// 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() {
|
||||
DisableLog()
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until either UseLogger or SetLogWriter are 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
|
||||
}
|
||||
|
||||
// LogClosure is a closure that can be printed with %v to be used to
|
||||
// generate expensive-to-create data for a detailed log level and avoid doing
|
||||
// the work if the data isn't printed.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the log closure and returns the results string.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over the passed function which allows
|
||||
// it to be used as a parameter in a logging function that is only invoked when
|
||||
// the logging level is such that the message will actually be logged.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
||||
1822
vendor/github.com/btcsuite/btcwallet/waddrmgr/manager.go
generated
vendored
Normal file
1822
vendor/github.com/btcsuite/btcwallet/waddrmgr/manager.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
412
vendor/github.com/btcsuite/btcwallet/waddrmgr/migrations.go
generated
vendored
Normal file
412
vendor/github.com/btcsuite/btcwallet/waddrmgr/migrations.go
generated
vendored
Normal file
@@ -0,0 +1,412 @@
|
||||
package waddrmgr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/walletdb/migration"
|
||||
)
|
||||
|
||||
// versions is a list of the different database versions. The last entry should
|
||||
// reflect the latest database state. If the database happens to be at a version
|
||||
// number lower than the latest, migrations will be performed in order to catch
|
||||
// it up.
|
||||
var versions = []migration.Version{
|
||||
{
|
||||
Number: 2,
|
||||
Migration: upgradeToVersion2,
|
||||
},
|
||||
{
|
||||
Number: 5,
|
||||
Migration: upgradeToVersion5,
|
||||
},
|
||||
{
|
||||
Number: 6,
|
||||
Migration: populateBirthdayBlock,
|
||||
},
|
||||
{
|
||||
Number: 7,
|
||||
Migration: resetSyncedBlockToBirthday,
|
||||
},
|
||||
{
|
||||
Number: 8,
|
||||
Migration: storeMaxReorgDepth,
|
||||
},
|
||||
}
|
||||
|
||||
// getLatestVersion returns the version number of the latest database version.
|
||||
func getLatestVersion() uint32 {
|
||||
return versions[len(versions)-1].Number
|
||||
}
|
||||
|
||||
// MigrationManager is an implementation of the migration.Manager interface that
|
||||
// will be used to handle migrations for the address manager. It exposes the
|
||||
// necessary parameters required to successfully perform migrations.
|
||||
type MigrationManager struct {
|
||||
ns walletdb.ReadWriteBucket
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that MigrationManager implements the
|
||||
// migration.Manager interface.
|
||||
var _ migration.Manager = (*MigrationManager)(nil)
|
||||
|
||||
// NewMigrationManager creates a new migration manager for the address manager.
|
||||
// The given bucket should reflect the top-level bucket in which all of the
|
||||
// address manager's data is contained within.
|
||||
func NewMigrationManager(ns walletdb.ReadWriteBucket) *MigrationManager {
|
||||
return &MigrationManager{ns: ns}
|
||||
}
|
||||
|
||||
// Name returns the name of the service we'll be attempting to upgrade.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Name() string {
|
||||
return "wallet address manager"
|
||||
}
|
||||
|
||||
// Namespace returns the top-level bucket of the service.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Namespace() walletdb.ReadWriteBucket {
|
||||
return m.ns
|
||||
}
|
||||
|
||||
// CurrentVersion returns the current version of the service's database.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) CurrentVersion(ns walletdb.ReadBucket) (uint32, error) {
|
||||
if ns == nil {
|
||||
ns = m.ns
|
||||
}
|
||||
return fetchManagerVersion(ns)
|
||||
}
|
||||
|
||||
// SetVersion sets the version of the service's database.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) SetVersion(ns walletdb.ReadWriteBucket,
|
||||
version uint32) error {
|
||||
|
||||
if ns == nil {
|
||||
ns = m.ns
|
||||
}
|
||||
return putManagerVersion(m.ns, version)
|
||||
}
|
||||
|
||||
// Versions returns all of the available database versions of the service.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Versions() []migration.Version {
|
||||
return versions
|
||||
}
|
||||
|
||||
// upgradeToVersion2 upgrades the database from version 1 to version 2
|
||||
// 'usedAddrBucketName' a bucket for storing addrs flagged as marked is
|
||||
// initialized and it will be updated on the next rescan.
|
||||
func upgradeToVersion2(ns walletdb.ReadWriteBucket) error {
|
||||
currentMgrVersion := uint32(2)
|
||||
|
||||
_, err := ns.CreateBucketIfNotExists(usedAddrBucketName)
|
||||
if err != nil {
|
||||
str := "failed to create used addresses bucket"
|
||||
return managerError(ErrDatabase, str, err)
|
||||
}
|
||||
|
||||
return putManagerVersion(ns, currentMgrVersion)
|
||||
}
|
||||
|
||||
// upgradeToVersion5 upgrades the database from version 4 to version 5. After
|
||||
// this update, the new ScopedKeyManager features cannot be used. This is due
|
||||
// to the fact that in version 5, we now store the encrypted master private
|
||||
// keys on disk. However, using the BIP0044 key scope, users will still be able
|
||||
// to create old p2pkh addresses.
|
||||
func upgradeToVersion5(ns walletdb.ReadWriteBucket) error {
|
||||
// First, we'll check if there are any existing segwit addresses, which
|
||||
// can't be upgraded to the new version. If so, we abort and warn the
|
||||
// user.
|
||||
err := ns.NestedReadBucket(addrBucketName).ForEach(
|
||||
func(k []byte, v []byte) error {
|
||||
row, err := deserializeAddressRow(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if row.addrType > adtScript {
|
||||
return fmt.Errorf("segwit address exists in " +
|
||||
"wallet, can't upgrade from v4 to " +
|
||||
"v5: well, we tried ¯\\_(ツ)_/¯")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next, we'll write out the new database version.
|
||||
if err := putManagerVersion(ns, 5); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// First, we'll need to create the new buckets that are used in the new
|
||||
// database version.
|
||||
scopeBucket, err := ns.CreateBucket(scopeBucketName)
|
||||
if err != nil {
|
||||
str := "failed to create scope bucket"
|
||||
return managerError(ErrDatabase, str, err)
|
||||
}
|
||||
scopeSchemas, err := ns.CreateBucket(scopeSchemaBucketName)
|
||||
if err != nil {
|
||||
str := "failed to create scope schema bucket"
|
||||
return managerError(ErrDatabase, str, err)
|
||||
}
|
||||
|
||||
// With the buckets created, we can now create the default BIP0044
|
||||
// scope which will be the only scope usable in the database after this
|
||||
// update.
|
||||
scopeKey := scopeToBytes(&KeyScopeBIP0044)
|
||||
scopeSchema := ScopeAddrMap[KeyScopeBIP0044]
|
||||
schemaBytes := scopeSchemaToBytes(&scopeSchema)
|
||||
if err := scopeSchemas.Put(scopeKey[:], schemaBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := createScopedManagerNS(scopeBucket, &KeyScopeBIP0044); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bip44Bucket := scopeBucket.NestedReadWriteBucket(scopeKey[:])
|
||||
|
||||
// With the buckets created, we now need to port over *each* item in
|
||||
// the prior main bucket, into the new default scope.
|
||||
mainBucket := ns.NestedReadWriteBucket(mainBucketName)
|
||||
|
||||
// First, we'll move over the encrypted coin type private and public
|
||||
// keys to the new sub-bucket.
|
||||
encCoinPrivKeys := mainBucket.Get(coinTypePrivKeyName)
|
||||
encCoinPubKeys := mainBucket.Get(coinTypePubKeyName)
|
||||
|
||||
err = bip44Bucket.Put(coinTypePrivKeyName, encCoinPrivKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bip44Bucket.Put(coinTypePubKeyName, encCoinPubKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mainBucket.Delete(coinTypePrivKeyName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mainBucket.Delete(coinTypePubKeyName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Next, we'll move over everything that was in the meta bucket to the
|
||||
// meta bucket within the new scope.
|
||||
metaBucket := ns.NestedReadWriteBucket(metaBucketName)
|
||||
lastAccount := metaBucket.Get(lastAccountName)
|
||||
if err := metaBucket.Delete(lastAccountName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scopedMetaBucket := bip44Bucket.NestedReadWriteBucket(metaBucketName)
|
||||
err = scopedMetaBucket.Put(lastAccountName, lastAccount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, we'll recursively move over a set of keys which were
|
||||
// formerly under the main bucket, into the new scoped buckets. We'll
|
||||
// do so by obtaining a slice of all the keys that we need to modify
|
||||
// and then recursing through each of them, moving both nested buckets
|
||||
// and key/value pairs.
|
||||
keysToMigrate := [][]byte{
|
||||
acctBucketName, addrBucketName, usedAddrBucketName,
|
||||
addrAcctIdxBucketName, acctNameIdxBucketName, acctIDIdxBucketName,
|
||||
}
|
||||
|
||||
// Migrate each bucket recursively.
|
||||
for _, bucketKey := range keysToMigrate {
|
||||
err := migrateRecursively(ns, bip44Bucket, bucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateRecursively moves a nested bucket from one bucket to another,
|
||||
// recursing into nested buckets as required.
|
||||
func migrateRecursively(src, dst walletdb.ReadWriteBucket,
|
||||
bucketKey []byte) error {
|
||||
// Within this bucket key, we'll migrate over, then delete each key.
|
||||
bucketToMigrate := src.NestedReadWriteBucket(bucketKey)
|
||||
newBucket, err := dst.CreateBucketIfNotExists(bucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucketToMigrate.ForEach(func(k, v []byte) error {
|
||||
if nestedBucket := bucketToMigrate.
|
||||
NestedReadBucket(k); nestedBucket != nil {
|
||||
// We have a nested bucket, so recurse into it.
|
||||
return migrateRecursively(bucketToMigrate, newBucket, k)
|
||||
}
|
||||
|
||||
if err := newBucket.Put(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucketToMigrate.Delete(k)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Finally, we'll delete the bucket itself.
|
||||
if err := src.DeleteNestedBucket(bucketKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateBirthdayBlock is a migration that attempts to populate the birthday
|
||||
// block of the wallet. This is needed so that in the event that we need to
|
||||
// perform a rescan of the wallet, we can do so starting from this block, rather
|
||||
// than from the genesis block.
|
||||
//
|
||||
// NOTE: This migration cannot guarantee the correctness of the birthday block
|
||||
// being set as we do not store block timestamps, so a sanity check must be done
|
||||
// upon starting the wallet to ensure we do not potentially miss any relevant
|
||||
// events when rescanning.
|
||||
func populateBirthdayBlock(ns walletdb.ReadWriteBucket) error {
|
||||
// We'll need to jump through some hoops in order to determine the
|
||||
// corresponding block height for our birthday timestamp. Since we do
|
||||
// not store block timestamps, we'll need to estimate our height by
|
||||
// looking at the genesis timestamp and assuming a block occurs every 10
|
||||
// minutes. This can be unsafe, and cause us to actually miss on-chain
|
||||
// events, so a sanity check is done before the wallet attempts to sync
|
||||
// itself.
|
||||
//
|
||||
// We'll start by fetching our birthday timestamp.
|
||||
birthdayTimestamp, err := fetchBirthday(ns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch birthday timestamp: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("Setting the wallet's birthday block from timestamp=%v",
|
||||
birthdayTimestamp)
|
||||
|
||||
// Now, we'll need to determine the timestamp of the genesis block for
|
||||
// the corresponding chain.
|
||||
genesisHash, err := fetchBlockHash(ns, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch genesis block hash: %v", err)
|
||||
}
|
||||
|
||||
var genesisTimestamp time.Time
|
||||
switch *genesisHash {
|
||||
case *chaincfg.MainNetParams.GenesisHash:
|
||||
genesisTimestamp =
|
||||
chaincfg.MainNetParams.GenesisBlock.Header.Timestamp
|
||||
|
||||
case *chaincfg.TestNet3Params.GenesisHash:
|
||||
genesisTimestamp =
|
||||
chaincfg.TestNet3Params.GenesisBlock.Header.Timestamp
|
||||
|
||||
case *chaincfg.RegressionNetParams.GenesisHash:
|
||||
genesisTimestamp =
|
||||
chaincfg.RegressionNetParams.GenesisBlock.Header.Timestamp
|
||||
|
||||
case *chaincfg.SimNetParams.GenesisHash:
|
||||
genesisTimestamp =
|
||||
chaincfg.SimNetParams.GenesisBlock.Header.Timestamp
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown genesis hash %v", genesisHash)
|
||||
}
|
||||
|
||||
// With the timestamps retrieved, we can estimate a block height by
|
||||
// taking the difference between them and dividing by the average block
|
||||
// time (10 minutes).
|
||||
birthdayHeight := int32((birthdayTimestamp.Sub(genesisTimestamp).Seconds() / 600))
|
||||
|
||||
// Now that we have the height estimate, we can fetch the corresponding
|
||||
// block and set it as our birthday block.
|
||||
birthdayHash, err := fetchBlockHash(ns, birthdayHeight)
|
||||
|
||||
// To ensure we record a height that is known to us from the chain,
|
||||
// we'll make sure this height estimate can be found. Otherwise, we'll
|
||||
// continue subtracting a day worth of blocks until we can find one.
|
||||
for IsError(err, ErrBlockNotFound) {
|
||||
birthdayHeight -= 144
|
||||
if birthdayHeight < 0 {
|
||||
birthdayHeight = 0
|
||||
}
|
||||
birthdayHash, err = fetchBlockHash(ns, birthdayHeight)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Estimated birthday block from timestamp=%v: height=%d, "+
|
||||
"hash=%v", birthdayTimestamp, birthdayHeight, birthdayHash)
|
||||
|
||||
// NOTE: The timestamp of the birthday block isn't set since we do not
|
||||
// store each block's timestamp.
|
||||
return PutBirthdayBlock(ns, BlockStamp{
|
||||
Height: birthdayHeight,
|
||||
Hash: *birthdayHash,
|
||||
})
|
||||
}
|
||||
|
||||
// resetSyncedBlockToBirthday is a migration that resets the wallet's currently
|
||||
// synced block to its birthday block. This essentially serves as a migration to
|
||||
// force a rescan of the wallet.
|
||||
func resetSyncedBlockToBirthday(ns walletdb.ReadWriteBucket) error {
|
||||
syncBucket := ns.NestedReadWriteBucket(syncBucketName)
|
||||
if syncBucket == nil {
|
||||
return errors.New("sync bucket does not exist")
|
||||
}
|
||||
|
||||
birthdayBlock, err := FetchBirthdayBlock(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return PutSyncedTo(ns, &birthdayBlock)
|
||||
}
|
||||
|
||||
// storeMaxReorgDepth is a migration responsible for allowing the wallet to only
|
||||
// maintain MaxReorgDepth block hashes stored in order to recover from long
|
||||
// reorgs.
|
||||
func storeMaxReorgDepth(ns walletdb.ReadWriteBucket) error {
|
||||
// Retrieve the current tip of the wallet. We'll use this to determine
|
||||
// the highest stale height we currently have stored within it.
|
||||
syncedTo, err := fetchSyncedTo(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
maxStaleHeight := staleHeight(syncedTo.Height)
|
||||
|
||||
// It's possible for this height to be non-sensical if we have less than
|
||||
// MaxReorgDepth blocks stored, so we can end the migration now.
|
||||
if maxStaleHeight < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("Removing block hash entries beyond maximum reorg depth of "+
|
||||
"%v from current tip %v", MaxReorgDepth, syncedTo.Height)
|
||||
|
||||
// Otherwise, since we currently store all block hashes of the chain
|
||||
// before this migration, we'll remove all stale block hash entries
|
||||
// above the genesis block. This would leave us with only MaxReorgDepth
|
||||
// blocks stored.
|
||||
for height := maxStaleHeight; height > 0; height-- {
|
||||
if err := deleteBlockHash(ns, height); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
1762
vendor/github.com/btcsuite/btcwallet/waddrmgr/scoped_manager.go
generated
vendored
Normal file
1762
vendor/github.com/btcsuite/btcwallet/waddrmgr/scoped_manager.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
135
vendor/github.com/btcsuite/btcwallet/waddrmgr/sync.go
generated
vendored
Normal file
135
vendor/github.com/btcsuite/btcwallet/waddrmgr/sync.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package waddrmgr
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
// BlockStamp defines a block (by height and a unique hash) and is used to mark
|
||||
// a point in the blockchain that an address manager element is
|
||||
// synced to.
|
||||
type BlockStamp struct {
|
||||
Height int32
|
||||
Hash chainhash.Hash
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// syncState houses the sync state of the manager. It consists of the recently
|
||||
// seen blocks as height, as well as the start and current sync block stamps.
|
||||
type syncState struct {
|
||||
// startBlock is the first block that can be safely used to start a
|
||||
// rescan. It is either the block the manager was created with, or the
|
||||
// earliest block provided with imported addresses or scripts.
|
||||
startBlock BlockStamp
|
||||
|
||||
// syncedTo is the current block the addresses in the manager are known
|
||||
// to be synced against.
|
||||
syncedTo BlockStamp
|
||||
}
|
||||
|
||||
// newSyncState returns a new sync state with the provided parameters.
|
||||
func newSyncState(startBlock, syncedTo *BlockStamp) *syncState {
|
||||
|
||||
return &syncState{
|
||||
startBlock: *startBlock,
|
||||
syncedTo: *syncedTo,
|
||||
}
|
||||
}
|
||||
|
||||
// SetSyncedTo marks the address manager to be in sync with the recently-seen
|
||||
// block described by the blockstamp. When the provided blockstamp is nil, the
|
||||
// oldest blockstamp of the block the manager was created at and of all
|
||||
// imported addresses will be used. This effectively allows the manager to be
|
||||
// marked as unsynced back to the oldest known point any of the addresses have
|
||||
// appeared in the block chain.
|
||||
func (m *Manager) SetSyncedTo(ns walletdb.ReadWriteBucket, bs *BlockStamp) error {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// Use the stored start blockstamp and reset recent hashes and height
|
||||
// when the provided blockstamp is nil.
|
||||
if bs == nil {
|
||||
bs = &m.syncState.startBlock
|
||||
}
|
||||
|
||||
// Update the database.
|
||||
err := PutSyncedTo(ns, bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update memory now that the database is updated.
|
||||
m.syncState.syncedTo = *bs
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncedTo returns details about the block height and hash that the address
|
||||
// manager is synced through at the very least. The intention is that callers
|
||||
// can use this information for intelligently initiating rescans to sync back to
|
||||
// the best chain from the last known good block.
|
||||
func (m *Manager) SyncedTo() BlockStamp {
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
|
||||
return m.syncState.syncedTo
|
||||
}
|
||||
|
||||
// BlockHash returns the block hash at a particular block height. This
|
||||
// information is useful for comparing against the chain back-end to see if a
|
||||
// reorg is taking place and how far back it goes.
|
||||
func (m *Manager) BlockHash(ns walletdb.ReadBucket, height int32) (
|
||||
*chainhash.Hash, error) {
|
||||
|
||||
return fetchBlockHash(ns, height)
|
||||
}
|
||||
|
||||
// Birthday returns the birthday, or earliest time a key could have been used,
|
||||
// for the manager.
|
||||
func (m *Manager) Birthday() time.Time {
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
|
||||
return m.birthday
|
||||
}
|
||||
|
||||
// SetBirthday sets the birthday, or earliest time a key could have been used,
|
||||
// for the manager.
|
||||
func (m *Manager) SetBirthday(ns walletdb.ReadWriteBucket,
|
||||
birthday time.Time) error {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
m.birthday = birthday
|
||||
return putBirthday(ns, birthday)
|
||||
}
|
||||
|
||||
// BirthdayBlock returns the birthday block, or earliest block a key could have
|
||||
// been used, for the manager. A boolean is also returned to indicate whether
|
||||
// the birthday block has been verified as correct.
|
||||
func (m *Manager) BirthdayBlock(ns walletdb.ReadBucket) (BlockStamp, bool, error) {
|
||||
birthdayBlock, err := FetchBirthdayBlock(ns)
|
||||
if err != nil {
|
||||
return BlockStamp{}, false, err
|
||||
}
|
||||
|
||||
return birthdayBlock, fetchBirthdayBlockVerification(ns), nil
|
||||
}
|
||||
|
||||
// SetBirthdayBlock sets the birthday block, or earliest time a key could have
|
||||
// been used, for the manager. The verified boolean can be used to specify
|
||||
// whether this birthday block should be sanity checked to determine if there
|
||||
// exists a better candidate to prevent less block fetching.
|
||||
func (m *Manager) SetBirthdayBlock(ns walletdb.ReadWriteBucket,
|
||||
block BlockStamp, verified bool) error {
|
||||
|
||||
if err := PutBirthdayBlock(ns, block); err != nil {
|
||||
return err
|
||||
}
|
||||
return putBirthdayBlockVerification(ns, verified)
|
||||
}
|
||||
126
vendor/github.com/btcsuite/btcwallet/waddrmgr/test_coverage.txt
generated
vendored
Normal file
126
vendor/github.com/btcsuite/btcwallet/waddrmgr/test_coverage.txt
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go serializeBIP0044AccountRow 100.00% (19/19)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.lock 100.00% (12/12)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go serializeScriptAddress 100.00% (10/10)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go serializeImportedAddress 100.00% (10/10)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go serializeAddressRow 100.00% (9/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.Address 100.00% (8/8)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.Lock 100.00% (8/8)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.Script 100.00% (7/7)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go serializeAccountRow 100.00% (6/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/sync.go BlockIterator.Prev 100.00% (6/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go zeroBigInt 100.00% (5/5)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.zeroSensitivePublicData 100.00% (5/5)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.lock 100.00% (4/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.lock 100.00% (4/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go serializeChainedAddress 100.00% (4/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.ExportPrivKey 100.00% (4/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go fileExists 100.00% (4/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/error.go ManagerError.Error 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/error.go ErrorCode.String 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.pubKeyBytes 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/sync.go Manager.NewIterateRecentBlocks 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/sync.go BlockIterator.BlockStamp 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go accountKey 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/sync.go Manager.SyncedTo 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutAccountInfo 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.IsLocked 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutImportedAddress 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.ExistsAddress 100.00% (3/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go zero 100.00% (2/2)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.Account 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.Compressed 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/sync.go newSyncState 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.Internal 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go cryptoKey.CopyBytes 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go newManager 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.AddrHash 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go defaultNewSecretKey 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.Imported 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.AddrHash 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.Address 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.Account 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.Internal 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.Net 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.ExportPubKey 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/error.go managerError 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.PubKey 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go cryptoKey.Bytes 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.Compressed 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.Address 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.Imported 100.00% (1/1)
|
||||
github.com/conformal/btcwallet/waddrmgr/sync.go Manager.SetSyncedTo 93.94% (31/33)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.PrivKey 91.67% (11/12)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go deserializeBIP0044AccountRow 90.48% (19/21)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.keyToManaged 90.00% (9/10)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchCryptoKeys 88.89% (16/18)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.Close 88.89% (8/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go newManagedAddressWithoutPrivKey 87.50% (7/8)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutRecentBlocks 85.71% (12/14)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Open 85.71% (6/7)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go deserializeScriptAddress 84.62% (11/13)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go deserializeImportedAddress 84.62% (11/13)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchRecentBlocks 84.62% (11/13)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchMasterKeyParams 84.62% (11/13)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go deserializeAddressRow 83.33% (10/12)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.loadAndCacheAddress 83.33% (10/12)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go managedAddress.unlock 81.82% (9/11)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go scriptAddress.unlock 81.82% (9/11)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.nextAddresses 80.00% (52/65)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutScriptAddress 80.00% (4/5)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.ChangePassphrase 79.10% (53/67)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutChainedAddress 78.26% (18/23)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchSyncedTo 77.78% (7/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutStartBlock 77.78% (7/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchStartBlock 77.78% (7/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutSyncedTo 77.78% (7/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.existsAddress 77.78% (7/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go deserializeAccountRow 77.78% (7/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.ExportWatchingOnly 75.00% (12/16)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go newManagedAddressFromExtKey 75.00% (12/16)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go newManagedAddress 75.00% (9/12)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutWatchingOnly 75.00% (6/8)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutNumAccounts 75.00% (6/8)
|
||||
github.com/conformal/btcwallet/waddrmgr/address.go newScriptAddress 75.00% (3/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go defaultNewCryptoKey 75.00% (3/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.chainAddressRowToManaged 75.00% (3/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go checkBranchKeys 75.00% (3/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.deriveKeyFromPath 75.00% (3/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go loadManager 72.55% (37/51)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.putAddress 71.43% (5/7)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go deserializeChainedAddress 71.43% (5/7)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.deriveKey 69.23% (9/13)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.ImportScript 67.44% (29/43)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.Unlock 67.35% (33/49)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchAddress 66.67% (10/15)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.importedAddressRowToManaged 66.67% (10/15)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutMasterKeyParams 66.67% (8/12)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.LastInternalAddress 66.67% (6/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.LastExternalAddress 66.67% (6/9)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.putAccountRow 66.67% (4/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.rowInterfaceToManaged 66.67% (4/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.NextExternalAddresses 66.67% (4/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.NextInternalAddresses 66.67% (4/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchWatchingOnly 66.67% (4/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/sync.go syncState.iter 66.67% (2/3)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.DeletePrivateKeys 66.04% (35/53)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go openOrCreateDB 66.04% (35/53)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.ImportPrivateKey 64.71% (33/51)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.PutCryptoKeys 64.71% (11/17)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.loadAccountInfo 62.96% (34/54)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchAccountInfo 61.54% (8/13)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.scriptAddressRowToManaged 60.00% (3/5)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Create 58.59% (58/99)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go deriveAccountKey 53.85% (7/13)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerDB.Update 50.00% (4/8)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerDB.View 50.00% (4/8)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerDB.Close 50.00% (2/4)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerDB.CopyDB 45.45% (5/11)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchAllAddresses 0.00% (0/20)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.AllActiveAddresses 0.00% (0/16)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerDB.WriteTo 0.00% (0/11)
|
||||
github.com/conformal/btcwallet/waddrmgr/sync.go BlockIterator.Next 0.00% (0/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/db.go managerTx.FetchNumAccounts 0.00% (0/6)
|
||||
github.com/conformal/btcwallet/waddrmgr/manager.go Manager.Export 0.00% (0/3)
|
||||
github.com/conformal/btcwallet/waddrmgr ----------------------------------- 72.59% (1030/1419)
|
||||
|
||||
16
vendor/github.com/btcsuite/btcwallet/walletdb/LICENSE
generated
vendored
Normal file
16
vendor/github.com/btcsuite/btcwallet/walletdb/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2013-2017 The btcsuite developers
|
||||
Copyright (c) 2015-2016 The Decred developers
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
75
vendor/github.com/btcsuite/btcwallet/walletdb/README.md
generated
vendored
Normal file
75
vendor/github.com/btcsuite/btcwallet/walletdb/README.md
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
walletdb
|
||||
========
|
||||
|
||||
[]
|
||||
(https://travis-ci.org/btcsuite/btcwallet)
|
||||
|
||||
Package walletdb provides a namespaced database interface for btcwallet.
|
||||
|
||||
A wallet essentially consists of a multitude of stored data such as private
|
||||
and public keys, key derivation bits, pay-to-script-hash scripts, and various
|
||||
metadata. One of the issues with many wallets is they are tightly integrated.
|
||||
Designing a wallet with loosely coupled components that provide specific
|
||||
functionality is ideal, however it presents a challenge in regards to data
|
||||
storage since each component needs to store its own data without knowing the
|
||||
internals of other components or breaking atomicity.
|
||||
|
||||
This package solves this issue by providing a namespaced database interface that
|
||||
is intended to be used by the main wallet daemon. This allows the potential for
|
||||
any backend database type with a suitable driver. Each component, which will
|
||||
typically be a package, can then implement various functionality such as address
|
||||
management, voting pools, and colored coin metadata in their own namespace
|
||||
without having to worry about conflicts with other packages even though they are
|
||||
sharing the same database that is managed by the wallet.
|
||||
|
||||
A suite of tests is provided to ensure proper functionality. See
|
||||
`test_coverage.txt` for the gocov coverage report. Alternatively, if you are
|
||||
running a POSIX OS, you can run the `cov_report.sh` script for a real-time
|
||||
report. Package walletdb is licensed under the copyfree ISC license.
|
||||
|
||||
This interfaces provided by this package were heavily inspired by the excellent
|
||||
boltdb project at https://github.com/boltdb/bolt by Ben B. Johnson.
|
||||
|
||||
## Feature Overview
|
||||
|
||||
- Key/value store
|
||||
- Namespace support
|
||||
- Allows multiple packages to have their own area in the database without
|
||||
worrying about conflicts
|
||||
- Read-only and read-write transactions with both manual and managed modes
|
||||
- Nested buckets
|
||||
- Supports registration of backend databases
|
||||
- Comprehensive test coverage
|
||||
|
||||
## Documentation
|
||||
|
||||
[]
|
||||
(http://godoc.org/github.com/btcsuite/btcwallet/walletdb)
|
||||
|
||||
Full `go doc` style documentation for the project can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://godoc.org/github.com/btcsuite/btcwallet/walletdb
|
||||
|
||||
You can also view the documentation locally once the package is installed with
|
||||
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||
http://localhost:6060/pkg/github.com/btcsuite/btcwallet/walletdb
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ go get github.com/btcsuite/btcwallet/walletdb
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
* [Basic Usage Example]
|
||||
(http://godoc.org/github.com/btcsuite/btcwallet/walletdb#example-package--BasicUsage)
|
||||
Demonstrates creating a new database, getting a namespace from it, and using a
|
||||
managed read-write transaction against the namespace to store and retrieve
|
||||
data.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Package walletdb is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
46
vendor/github.com/btcsuite/btcwallet/walletdb/bdb/README.md
generated
vendored
Normal file
46
vendor/github.com/btcsuite/btcwallet/walletdb/bdb/README.md
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
bdb
|
||||
===
|
||||
|
||||
[]
|
||||
(https://travis-ci.org/btcsuite/btcwallet)
|
||||
|
||||
Package bdb implements an driver for walletdb that uses boltdb for the backing
|
||||
datastore. Package bdb is licensed under the copyfree ISC license.
|
||||
|
||||
## Usage
|
||||
|
||||
This package is only a driver to the walletdb package and provides the database
|
||||
type of "bdb". The only parameter the Open and Create functions take is the
|
||||
database path as a string:
|
||||
|
||||
```Go
|
||||
db, err := walletdb.Open("bdb", "path/to/database.db")
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
```Go
|
||||
db, err := walletdb.Create("bdb", "path/to/database.db")
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
[]
|
||||
(http://godoc.org/github.com/btcsuite/btcwallet/walletdb/bdb)
|
||||
|
||||
Full `go doc` style documentation for the project can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://godoc.org/github.com/btcsuite/btcwallet/walletdb/bdb
|
||||
|
||||
You can also view the documentation locally once the package is installed with
|
||||
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||
http://localhost:6060/pkg/github.com/btcsuite/btcwallet/walletdb/bdb
|
||||
|
||||
## License
|
||||
|
||||
Package bdb is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
356
vendor/github.com/btcsuite/btcwallet/walletdb/bdb/db.go
generated
vendored
Normal file
356
vendor/github.com/btcsuite/btcwallet/walletdb/bdb/db.go
generated
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bdb
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/coreos/bbolt"
|
||||
)
|
||||
|
||||
// convertErr converts some bolt errors to the equivalent walletdb error.
|
||||
func convertErr(err error) error {
|
||||
switch err {
|
||||
// Database open/create errors.
|
||||
case bbolt.ErrDatabaseNotOpen:
|
||||
return walletdb.ErrDbNotOpen
|
||||
case bbolt.ErrInvalid:
|
||||
return walletdb.ErrInvalid
|
||||
|
||||
// Transaction errors.
|
||||
case bbolt.ErrTxNotWritable:
|
||||
return walletdb.ErrTxNotWritable
|
||||
case bbolt.ErrTxClosed:
|
||||
return walletdb.ErrTxClosed
|
||||
|
||||
// Value/bucket errors.
|
||||
case bbolt.ErrBucketNotFound:
|
||||
return walletdb.ErrBucketNotFound
|
||||
case bbolt.ErrBucketExists:
|
||||
return walletdb.ErrBucketExists
|
||||
case bbolt.ErrBucketNameRequired:
|
||||
return walletdb.ErrBucketNameRequired
|
||||
case bbolt.ErrKeyRequired:
|
||||
return walletdb.ErrKeyRequired
|
||||
case bbolt.ErrKeyTooLarge:
|
||||
return walletdb.ErrKeyTooLarge
|
||||
case bbolt.ErrValueTooLarge:
|
||||
return walletdb.ErrValueTooLarge
|
||||
case bbolt.ErrIncompatibleValue:
|
||||
return walletdb.ErrIncompatibleValue
|
||||
}
|
||||
|
||||
// Return the original error if none of the above applies.
|
||||
return err
|
||||
}
|
||||
|
||||
// transaction represents a database transaction. It can either by read-only or
|
||||
// read-write and implements the walletdb Tx interfaces. The transaction
|
||||
// provides a root bucket against which all read and writes occur.
|
||||
type transaction struct {
|
||||
boltTx *bbolt.Tx
|
||||
}
|
||||
|
||||
func (tx *transaction) ReadBucket(key []byte) walletdb.ReadBucket {
|
||||
return tx.ReadWriteBucket(key)
|
||||
}
|
||||
|
||||
func (tx *transaction) ReadWriteBucket(key []byte) walletdb.ReadWriteBucket {
|
||||
boltBucket := tx.boltTx.Bucket(key)
|
||||
if boltBucket == nil {
|
||||
return nil
|
||||
}
|
||||
return (*bucket)(boltBucket)
|
||||
}
|
||||
|
||||
func (tx *transaction) CreateTopLevelBucket(key []byte) (walletdb.ReadWriteBucket, error) {
|
||||
boltBucket, err := tx.boltTx.CreateBucket(key)
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
return (*bucket)(boltBucket), nil
|
||||
}
|
||||
|
||||
func (tx *transaction) DeleteTopLevelBucket(key []byte) error {
|
||||
err := tx.boltTx.DeleteBucket(key)
|
||||
if err != nil {
|
||||
return convertErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit commits all changes that have been made through the root bucket and
|
||||
// all of its sub-buckets to persistent storage.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteTx interface implementation.
|
||||
func (tx *transaction) Commit() error {
|
||||
return convertErr(tx.boltTx.Commit())
|
||||
}
|
||||
|
||||
// Rollback undoes all changes that have been made to the root bucket and all of
|
||||
// its sub-buckets.
|
||||
//
|
||||
// This function is part of the walletdb.ReadTx interface implementation.
|
||||
func (tx *transaction) Rollback() error {
|
||||
return convertErr(tx.boltTx.Rollback())
|
||||
}
|
||||
|
||||
// OnCommit takes a function closure that will be executed when the transaction
|
||||
// successfully gets committed.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteTx interface implementation.
|
||||
func (tx *transaction) OnCommit(f func()) {
|
||||
tx.boltTx.OnCommit(f)
|
||||
}
|
||||
|
||||
// bucket is an internal type used to represent a collection of key/value pairs
|
||||
// and implements the walletdb Bucket interfaces.
|
||||
type bucket bbolt.Bucket
|
||||
|
||||
// Enforce bucket implements the walletdb Bucket interfaces.
|
||||
var _ walletdb.ReadWriteBucket = (*bucket)(nil)
|
||||
|
||||
// NestedReadWriteBucket retrieves a nested bucket with the given key. Returns
|
||||
// nil if the bucket does not exist.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteBucket interface implementation.
|
||||
func (b *bucket) NestedReadWriteBucket(key []byte) walletdb.ReadWriteBucket {
|
||||
boltBucket := (*bbolt.Bucket)(b).Bucket(key)
|
||||
// Don't return a non-nil interface to a nil pointer.
|
||||
if boltBucket == nil {
|
||||
return nil
|
||||
}
|
||||
return (*bucket)(boltBucket)
|
||||
}
|
||||
|
||||
func (b *bucket) NestedReadBucket(key []byte) walletdb.ReadBucket {
|
||||
return b.NestedReadWriteBucket(key)
|
||||
}
|
||||
|
||||
// CreateBucket creates and returns a new nested bucket with the given key.
|
||||
// Returns ErrBucketExists if the bucket already exists, ErrBucketNameRequired
|
||||
// if the key is empty, or ErrIncompatibleValue if the key value is otherwise
|
||||
// invalid.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteBucket interface implementation.
|
||||
func (b *bucket) CreateBucket(key []byte) (walletdb.ReadWriteBucket, error) {
|
||||
boltBucket, err := (*bbolt.Bucket)(b).CreateBucket(key)
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
return (*bucket)(boltBucket), nil
|
||||
}
|
||||
|
||||
// CreateBucketIfNotExists creates and returns a new nested bucket with the
|
||||
// given key if it does not already exist. Returns ErrBucketNameRequired if the
|
||||
// key is empty or ErrIncompatibleValue if the key value is otherwise invalid.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteBucket interface implementation.
|
||||
func (b *bucket) CreateBucketIfNotExists(key []byte) (walletdb.ReadWriteBucket, error) {
|
||||
boltBucket, err := (*bbolt.Bucket)(b).CreateBucketIfNotExists(key)
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
return (*bucket)(boltBucket), nil
|
||||
}
|
||||
|
||||
// DeleteNestedBucket removes a nested bucket with the given key. Returns
|
||||
// ErrTxNotWritable if attempted against a read-only transaction and
|
||||
// ErrBucketNotFound if the specified bucket does not exist.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteBucket interface implementation.
|
||||
func (b *bucket) DeleteNestedBucket(key []byte) error {
|
||||
return convertErr((*bbolt.Bucket)(b).DeleteBucket(key))
|
||||
}
|
||||
|
||||
// ForEach invokes the passed function with every key/value pair in the bucket.
|
||||
// This includes nested buckets, in which case the value is nil, but it does not
|
||||
// include the key/value pairs within those nested buckets.
|
||||
//
|
||||
// NOTE: The values returned by this function are only valid during a
|
||||
// transaction. Attempting to access them after a transaction has ended will
|
||||
// likely result in an access violation.
|
||||
//
|
||||
// This function is part of the walletdb.ReadBucket interface implementation.
|
||||
func (b *bucket) ForEach(fn func(k, v []byte) error) error {
|
||||
return convertErr((*bbolt.Bucket)(b).ForEach(fn))
|
||||
}
|
||||
|
||||
// Put saves the specified key/value pair to the bucket. Keys that do not
|
||||
// already exist are added and keys that already exist are overwritten. Returns
|
||||
// ErrTxNotWritable if attempted against a read-only transaction.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteBucket interface implementation.
|
||||
func (b *bucket) Put(key, value []byte) error {
|
||||
return convertErr((*bbolt.Bucket)(b).Put(key, value))
|
||||
}
|
||||
|
||||
// Get returns the value for the given key. Returns nil if the key does
|
||||
// not exist in this bucket (or nested buckets).
|
||||
//
|
||||
// NOTE: The value returned by this function is only valid during a
|
||||
// transaction. Attempting to access it after a transaction has ended
|
||||
// will likely result in an access violation.
|
||||
//
|
||||
// This function is part of the walletdb.ReadBucket interface implementation.
|
||||
func (b *bucket) Get(key []byte) []byte {
|
||||
return (*bbolt.Bucket)(b).Get(key)
|
||||
}
|
||||
|
||||
// Delete removes the specified key from the bucket. Deleting a key that does
|
||||
// not exist does not return an error. Returns ErrTxNotWritable if attempted
|
||||
// against a read-only transaction.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteBucket interface implementation.
|
||||
func (b *bucket) Delete(key []byte) error {
|
||||
return convertErr((*bbolt.Bucket)(b).Delete(key))
|
||||
}
|
||||
|
||||
func (b *bucket) ReadCursor() walletdb.ReadCursor {
|
||||
return b.ReadWriteCursor()
|
||||
}
|
||||
|
||||
// ReadWriteCursor returns a new cursor, allowing for iteration over the bucket's
|
||||
// key/value pairs and nested buckets in forward or backward order.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteBucket interface implementation.
|
||||
func (b *bucket) ReadWriteCursor() walletdb.ReadWriteCursor {
|
||||
return (*cursor)((*bbolt.Bucket)(b).Cursor())
|
||||
}
|
||||
|
||||
// Tx returns the bucket's transaction.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteBucket interface implementation.
|
||||
func (b *bucket) Tx() walletdb.ReadWriteTx {
|
||||
return &transaction{
|
||||
(*bbolt.Bucket)(b).Tx(),
|
||||
}
|
||||
}
|
||||
|
||||
// cursor represents a cursor over key/value pairs and nested buckets of a
|
||||
// bucket.
|
||||
//
|
||||
// Note that open cursors are not tracked on bucket changes and any
|
||||
// modifications to the bucket, with the exception of cursor.Delete, invalidate
|
||||
// the cursor. After invalidation, the cursor must be repositioned, or the keys
|
||||
// and values returned may be unpredictable.
|
||||
type cursor bbolt.Cursor
|
||||
|
||||
// Delete removes the current key/value pair the cursor is at without
|
||||
// invalidating the cursor. Returns ErrTxNotWritable if attempted on a read-only
|
||||
// transaction, or ErrIncompatibleValue if attempted when the cursor points to a
|
||||
// nested bucket.
|
||||
//
|
||||
// This function is part of the walletdb.ReadWriteCursor interface implementation.
|
||||
func (c *cursor) Delete() error {
|
||||
return convertErr((*bbolt.Cursor)(c).Delete())
|
||||
}
|
||||
|
||||
// First positions the cursor at the first key/value pair and returns the pair.
|
||||
//
|
||||
// This function is part of the walletdb.ReadCursor interface implementation.
|
||||
func (c *cursor) First() (key, value []byte) {
|
||||
return (*bbolt.Cursor)(c).First()
|
||||
}
|
||||
|
||||
// Last positions the cursor at the last key/value pair and returns the pair.
|
||||
//
|
||||
// This function is part of the walletdb.ReadCursor interface implementation.
|
||||
func (c *cursor) Last() (key, value []byte) {
|
||||
return (*bbolt.Cursor)(c).Last()
|
||||
}
|
||||
|
||||
// Next moves the cursor one key/value pair forward and returns the new pair.
|
||||
//
|
||||
// This function is part of the walletdb.ReadCursor interface implementation.
|
||||
func (c *cursor) Next() (key, value []byte) {
|
||||
return (*bbolt.Cursor)(c).Next()
|
||||
}
|
||||
|
||||
// Prev moves the cursor one key/value pair backward and returns the new pair.
|
||||
//
|
||||
// This function is part of the walletdb.ReadCursor interface implementation.
|
||||
func (c *cursor) Prev() (key, value []byte) {
|
||||
return (*bbolt.Cursor)(c).Prev()
|
||||
}
|
||||
|
||||
// Seek positions the cursor at the passed seek key. If the key does not exist,
|
||||
// the cursor is moved to the next key after seek. Returns the new pair.
|
||||
//
|
||||
// This function is part of the walletdb.ReadCursor interface implementation.
|
||||
func (c *cursor) Seek(seek []byte) (key, value []byte) {
|
||||
return (*bbolt.Cursor)(c).Seek(seek)
|
||||
}
|
||||
|
||||
// db represents a collection of namespaces which are persisted and implements
|
||||
// the walletdb.Db interface. All database access is performed through
|
||||
// transactions which are obtained through the specific Namespace.
|
||||
type db bbolt.DB
|
||||
|
||||
// Enforce db implements the walletdb.Db interface.
|
||||
var _ walletdb.DB = (*db)(nil)
|
||||
|
||||
func (db *db) beginTx(writable bool) (*transaction, error) {
|
||||
boltTx, err := (*bbolt.DB)(db).Begin(writable)
|
||||
if err != nil {
|
||||
return nil, convertErr(err)
|
||||
}
|
||||
return &transaction{boltTx: boltTx}, nil
|
||||
}
|
||||
|
||||
func (db *db) BeginReadTx() (walletdb.ReadTx, error) {
|
||||
return db.beginTx(false)
|
||||
}
|
||||
|
||||
func (db *db) BeginReadWriteTx() (walletdb.ReadWriteTx, error) {
|
||||
return db.beginTx(true)
|
||||
}
|
||||
|
||||
// Copy writes a copy of the database to the provided writer. This call will
|
||||
// start a read-only transaction to perform all operations.
|
||||
//
|
||||
// This function is part of the walletdb.Db interface implementation.
|
||||
func (db *db) Copy(w io.Writer) error {
|
||||
return convertErr((*bbolt.DB)(db).View(func(tx *bbolt.Tx) error {
|
||||
return tx.Copy(w)
|
||||
}))
|
||||
}
|
||||
|
||||
// Close cleanly shuts down the database and syncs all data.
|
||||
//
|
||||
// This function is part of the walletdb.Db interface implementation.
|
||||
func (db *db) Close() error {
|
||||
return convertErr((*bbolt.DB)(db).Close())
|
||||
}
|
||||
|
||||
// filesExists reports whether the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// openDB opens the database at the provided path. walletdb.ErrDbDoesNotExist
|
||||
// is returned if the database doesn't exist and the create flag is not set.
|
||||
func openDB(dbPath string, create bool) (walletdb.DB, error) {
|
||||
if !create && !fileExists(dbPath) {
|
||||
return nil, walletdb.ErrDbDoesNotExist
|
||||
}
|
||||
|
||||
// Specify bbolt freelist options to reduce heap pressure in case the
|
||||
// freelist grows to be very large.
|
||||
options := &bbolt.Options{
|
||||
NoFreelistSync: true,
|
||||
FreelistType: bbolt.FreelistMapType,
|
||||
}
|
||||
|
||||
boltDB, err := bbolt.Open(dbPath, 0600, options)
|
||||
return (*db)(boltDB), convertErr(err)
|
||||
}
|
||||
25
vendor/github.com/btcsuite/btcwallet/walletdb/bdb/doc.go
generated
vendored
Normal file
25
vendor/github.com/btcsuite/btcwallet/walletdb/bdb/doc.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package bdb implements an instance of walletdb that uses boltdb for the backing
|
||||
datastore.
|
||||
|
||||
Usage
|
||||
|
||||
This package is only a driver to the walletdb package and provides the database
|
||||
type of "bdb". The only parameter the Open and Create functions take is the
|
||||
database path as a string:
|
||||
|
||||
db, err := walletdb.Open("bdb", "path/to/database.db")
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
db, err := walletdb.Create("bdb", "path/to/database.db")
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
*/
|
||||
package bdb
|
||||
66
vendor/github.com/btcsuite/btcwallet/walletdb/bdb/driver.go
generated
vendored
Normal file
66
vendor/github.com/btcsuite/btcwallet/walletdb/bdb/driver.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
const (
|
||||
dbType = "bdb"
|
||||
)
|
||||
|
||||
// parseArgs parses the arguments from the walletdb Open/Create methods.
|
||||
func parseArgs(funcName string, args ...interface{}) (string, error) {
|
||||
if len(args) != 1 {
|
||||
return "", fmt.Errorf("invalid arguments to %s.%s -- "+
|
||||
"expected database path", dbType, funcName)
|
||||
}
|
||||
|
||||
dbPath, ok := args[0].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("first argument to %s.%s is invalid -- "+
|
||||
"expected database path string", dbType, funcName)
|
||||
}
|
||||
|
||||
return dbPath, nil
|
||||
}
|
||||
|
||||
// openDBDriver is the callback provided during driver registration that opens
|
||||
// an existing database for use.
|
||||
func openDBDriver(args ...interface{}) (walletdb.DB, error) {
|
||||
dbPath, err := parseArgs("Open", args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return openDB(dbPath, false)
|
||||
}
|
||||
|
||||
// createDBDriver is the callback provided during driver registration that
|
||||
// creates, initializes, and opens a database for use.
|
||||
func createDBDriver(args ...interface{}) (walletdb.DB, error) {
|
||||
dbPath, err := parseArgs("Create", args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return openDB(dbPath, true)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register the driver.
|
||||
driver := walletdb.Driver{
|
||||
DbType: dbType,
|
||||
Create: createDBDriver,
|
||||
Open: openDBDriver,
|
||||
}
|
||||
if err := walletdb.RegisterDriver(driver); err != nil {
|
||||
panic(fmt.Sprintf("Failed to regiser database driver '%s': %v",
|
||||
dbType, err))
|
||||
}
|
||||
}
|
||||
7
vendor/github.com/btcsuite/btcwallet/walletdb/cov_report.sh
generated
vendored
Normal file
7
vendor/github.com/btcsuite/btcwallet/walletdb/cov_report.sh
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script uses go tool cover to generate a test coverage report.
|
||||
go test -coverprofile=cov.out && go tool cover -func=cov.out && rm -f cov.out
|
||||
echo "============================================================"
|
||||
(cd bdb && go test -coverprofile=cov.out && go tool cover -func=cov.out && \
|
||||
rm -f cov.out)
|
||||
103
vendor/github.com/btcsuite/btcwallet/walletdb/doc.go
generated
vendored
Normal file
103
vendor/github.com/btcsuite/btcwallet/walletdb/doc.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package walletdb provides a namespaced database interface for btcwallet.
|
||||
|
||||
Overview
|
||||
|
||||
A wallet essentially consists of a multitude of stored data such as private
|
||||
and public keys, key derivation bits, pay-to-script-hash scripts, and various
|
||||
metadata. One of the issues with many wallets is they are tightly integrated.
|
||||
Designing a wallet with loosely coupled components that provide specific
|
||||
functionality is ideal, however it presents a challenge in regards to data
|
||||
storage since each component needs to store its own data without knowing the
|
||||
internals of other components or breaking atomicity.
|
||||
|
||||
This package solves this issue by providing a pluggable driver, namespaced
|
||||
database interface that is intended to be used by the main wallet daemon. This
|
||||
allows the potential for any backend database type with a suitable driver. Each
|
||||
component, which will typically be a package, can then implement various
|
||||
functionality such as address management, voting pools, and colored coin
|
||||
metadata in their own namespace without having to worry about conflicts with
|
||||
other packages even though they are sharing the same database that is managed by
|
||||
the wallet.
|
||||
|
||||
A quick overview of the features walletdb provides are as follows:
|
||||
|
||||
- Key/value store
|
||||
- Namespace support
|
||||
- Allows multiple packages to have their own area in the database without
|
||||
worrying about conflicts
|
||||
- Read-only and read-write transactions with both manual and managed modes
|
||||
- Nested buckets
|
||||
- Supports registration of backend databases
|
||||
- Comprehensive test coverage
|
||||
|
||||
Database
|
||||
|
||||
The main entry point is the DB interface. It exposes functionality for
|
||||
creating, retrieving, and removing namespaces. It is obtained via the Create
|
||||
and Open functions which take a database type string that identifies the
|
||||
specific database driver (backend) to use as well as arguments specific to the
|
||||
specified driver.
|
||||
|
||||
Namespaces
|
||||
|
||||
The Namespace interface is an abstraction that provides facilities for obtaining
|
||||
transactions (the Tx interface) that are the basis of all database reads and
|
||||
writes. Unlike some database interfaces that support reading and writing
|
||||
without transactions, this interface requires transactions even when only
|
||||
reading or writing a single key.
|
||||
|
||||
The Begin function provides an unmanaged transaction while the View and Update
|
||||
functions provide a managed transaction. These are described in more detail
|
||||
below.
|
||||
|
||||
Transactions
|
||||
|
||||
The Tx interface provides facilities for rolling back or commiting changes that
|
||||
took place while the transaction was active. It also provides the root bucket
|
||||
under which all keys, values, and nested buckets are stored. A transaction
|
||||
can either be read-only or read-write and managed or unmanaged.
|
||||
|
||||
Managed versus Unmanaged Transactions
|
||||
|
||||
A managed transaction is one where the caller provides a function to execute
|
||||
within the context of the transaction and the commit or rollback is handled
|
||||
automatically depending on whether or not the provided function returns an
|
||||
error. Attempting to manually call Rollback or Commit on the managed
|
||||
transaction will result in a panic.
|
||||
|
||||
An unmanaged transaction, on the other hand, requires the caller to manually
|
||||
call Commit or Rollback when they are finished with it. Leaving transactions
|
||||
open for long periods of time can have several adverse effects, so it is
|
||||
recommended that managed transactions are used instead.
|
||||
|
||||
Buckets
|
||||
|
||||
The Bucket interface provides the ability to manipulate key/value pairs and
|
||||
nested buckets as well as iterate through them.
|
||||
|
||||
The Get, Put, and Delete functions work with key/value pairs, while the Bucket,
|
||||
CreateBucket, CreateBucketIfNotExists, and DeleteBucket functions work with
|
||||
buckets. The ForEach function allows the caller to provide a function to be
|
||||
called with each key/value pair and nested bucket in the current bucket.
|
||||
|
||||
Root Bucket
|
||||
|
||||
As discussed above, all of the functions which are used to manipulate key/value
|
||||
pairs and nested buckets exist on the Bucket interface. The root bucket is the
|
||||
upper-most bucket in a namespace under which data is stored and is created at
|
||||
the same time as the namespace. Use the RootBucket function on the Tx interface
|
||||
to retrieve it.
|
||||
|
||||
Nested Buckets
|
||||
|
||||
The CreateBucket and CreateBucketIfNotExists functions on the Bucket interface
|
||||
provide the ability to create an arbitrary number of nested buckets. It is
|
||||
a good idea to avoid a lot of buckets with little data in them as it could lead
|
||||
to poor page utilization depending on the specific driver in use.
|
||||
*/
|
||||
package walletdb
|
||||
80
vendor/github.com/btcsuite/btcwallet/walletdb/error.go
generated
vendored
Normal file
80
vendor/github.com/btcsuite/btcwallet/walletdb/error.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package walletdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Errors that can occur during driver registration.
|
||||
var (
|
||||
// ErrDbTypeRegistered is returned when two different database drivers
|
||||
// attempt to register with the name database type.
|
||||
ErrDbTypeRegistered = errors.New("database type already registered")
|
||||
)
|
||||
|
||||
// Errors that the various database functions may return.
|
||||
var (
|
||||
// ErrDbUnknownType is returned when there is no driver registered for
|
||||
// the specified database type.
|
||||
ErrDbUnknownType = errors.New("unknown database type")
|
||||
|
||||
// ErrDbDoesNotExist is returned when open is called for a database that
|
||||
// does not exist.
|
||||
ErrDbDoesNotExist = errors.New("database does not exist")
|
||||
|
||||
// ErrDbExists is returned when create is called for a database that
|
||||
// already exists.
|
||||
ErrDbExists = errors.New("database already exists")
|
||||
|
||||
// ErrDbNotOpen is returned when a database instance is accessed before
|
||||
// it is opened or after it is closed.
|
||||
ErrDbNotOpen = errors.New("database not open")
|
||||
|
||||
// ErrDbAlreadyOpen is returned when open is called on a database that
|
||||
// is already open.
|
||||
ErrDbAlreadyOpen = errors.New("database already open")
|
||||
|
||||
// ErrInvalid is returned if the specified database is not valid.
|
||||
ErrInvalid = errors.New("invalid database")
|
||||
)
|
||||
|
||||
// Errors that can occur when beginning or committing a transaction.
|
||||
var (
|
||||
// ErrTxClosed is returned when attempting to commit or rollback a
|
||||
// transaction that has already had one of those operations performed.
|
||||
ErrTxClosed = errors.New("tx closed")
|
||||
|
||||
// ErrTxNotWritable is returned when an operation that requires write
|
||||
// access to the database is attempted against a read-only transaction.
|
||||
ErrTxNotWritable = errors.New("tx not writable")
|
||||
)
|
||||
|
||||
// Errors that can occur when putting or deleting a value or bucket.
|
||||
var (
|
||||
// ErrBucketNotFound is returned when trying to access a bucket that has
|
||||
// not been created yet.
|
||||
ErrBucketNotFound = errors.New("bucket not found")
|
||||
|
||||
// ErrBucketExists is returned when creating a bucket that already exists.
|
||||
ErrBucketExists = errors.New("bucket already exists")
|
||||
|
||||
// ErrBucketNameRequired is returned when creating a bucket with a blank name.
|
||||
ErrBucketNameRequired = errors.New("bucket name required")
|
||||
|
||||
// ErrKeyRequired is returned when inserting a zero-length key.
|
||||
ErrKeyRequired = errors.New("key required")
|
||||
|
||||
// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
|
||||
ErrKeyTooLarge = errors.New("key too large")
|
||||
|
||||
// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
|
||||
ErrValueTooLarge = errors.New("value too large")
|
||||
|
||||
// ErrIncompatibleValue is returned when trying create or delete a
|
||||
// bucket on an existing non-bucket key or when trying to create or
|
||||
// delete a non-bucket key on an existing bucket key.
|
||||
ErrIncompatibleValue = errors.New("incompatible value")
|
||||
)
|
||||
11
vendor/github.com/btcsuite/btcwallet/walletdb/go.mod
generated
vendored
Normal file
11
vendor/github.com/btcsuite/btcwallet/walletdb/go.mod
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
module github.com/btcsuite/btcwallet/walletdb
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
||||
github.com/coreos/bbolt v1.3.3
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
|
||||
)
|
||||
10
vendor/github.com/btcsuite/btcwallet/walletdb/go.sum
generated
vendored
Normal file
10
vendor/github.com/btcsuite/btcwallet/walletdb/go.sum
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
304
vendor/github.com/btcsuite/btcwallet/walletdb/interface.go
generated
vendored
Normal file
304
vendor/github.com/btcsuite/btcwallet/walletdb/interface.go
generated
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This interface was inspired heavily by the excellent boltdb project at
|
||||
// https://github.com/boltdb/bolt by Ben B. Johnson.
|
||||
|
||||
package walletdb
|
||||
|
||||
import "io"
|
||||
|
||||
// ReadTx represents a database transaction that can only be used for reads. If
|
||||
// a database update must occur, use a ReadWriteTx.
|
||||
type ReadTx interface {
|
||||
// ReadBucket opens the root bucket for read only access. If the bucket
|
||||
// described by the key does not exist, nil is returned.
|
||||
ReadBucket(key []byte) ReadBucket
|
||||
|
||||
// Rollback closes the transaction, discarding changes (if any) if the
|
||||
// database was modified by a write transaction.
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
// ReadWriteTx represents a database transaction that can be used for both reads
|
||||
// and writes. When only reads are necessary, consider using a ReadTx instead.
|
||||
type ReadWriteTx interface {
|
||||
ReadTx
|
||||
|
||||
// ReadWriteBucket opens the root bucket for read/write access. If the
|
||||
// bucket described by the key does not exist, nil is returned.
|
||||
ReadWriteBucket(key []byte) ReadWriteBucket
|
||||
|
||||
// CreateTopLevelBucket creates the top level bucket for a key if it
|
||||
// does not exist. The newly-created bucket it returned.
|
||||
CreateTopLevelBucket(key []byte) (ReadWriteBucket, error)
|
||||
|
||||
// DeleteTopLevelBucket deletes the top level bucket for a key. This
|
||||
// errors if the bucket can not be found or the key keys a single value
|
||||
// instead of a bucket.
|
||||
DeleteTopLevelBucket(key []byte) error
|
||||
|
||||
// Commit commits all changes that have been on the transaction's root
|
||||
// buckets and all of their sub-buckets to persistent storage.
|
||||
Commit() error
|
||||
|
||||
// OnCommit takes a function closure that will be executed when the
|
||||
// transaction successfully gets committed.
|
||||
OnCommit(func())
|
||||
}
|
||||
|
||||
// ReadBucket represents a bucket (a hierarchical structure within the database)
|
||||
// that is only allowed to perform read operations.
|
||||
type ReadBucket interface {
|
||||
// NestedReadBucket retrieves a nested bucket with the given key.
|
||||
// Returns nil if the bucket does not exist.
|
||||
NestedReadBucket(key []byte) ReadBucket
|
||||
|
||||
// ForEach invokes the passed function with every key/value pair in
|
||||
// the bucket. This includes nested buckets, in which case the value
|
||||
// is nil, but it does not include the key/value pairs within those
|
||||
// nested buckets.
|
||||
//
|
||||
// NOTE: The values returned by this function are only valid during a
|
||||
// transaction. Attempting to access them after a transaction has ended
|
||||
// results in undefined behavior. This constraint prevents additional
|
||||
// data copies and allows support for memory-mapped database
|
||||
// implementations.
|
||||
ForEach(func(k, v []byte) error) error
|
||||
|
||||
// Get returns the value for the given key. Returns nil if the key does
|
||||
// not exist in this bucket (or nested buckets).
|
||||
//
|
||||
// NOTE: The value returned by this function is only valid during a
|
||||
// transaction. Attempting to access it after a transaction has ended
|
||||
// results in undefined behavior. This constraint prevents additional
|
||||
// data copies and allows support for memory-mapped database
|
||||
// implementations.
|
||||
Get(key []byte) []byte
|
||||
|
||||
ReadCursor() ReadCursor
|
||||
}
|
||||
|
||||
// ReadWriteBucket represents a bucket (a hierarchical structure within the
|
||||
// database) that is allowed to perform both read and write operations.
|
||||
type ReadWriteBucket interface {
|
||||
ReadBucket
|
||||
|
||||
// NestedReadWriteBucket retrieves a nested bucket with the given key.
|
||||
// Returns nil if the bucket does not exist.
|
||||
NestedReadWriteBucket(key []byte) ReadWriteBucket
|
||||
|
||||
// CreateBucket creates and returns a new nested bucket with the given
|
||||
// key. Returns ErrBucketExists if the bucket already exists,
|
||||
// ErrBucketNameRequired if the key is empty, or ErrIncompatibleValue
|
||||
// if the key value is otherwise invalid for the particular database
|
||||
// implementation. Other errors are possible depending on the
|
||||
// implementation.
|
||||
CreateBucket(key []byte) (ReadWriteBucket, error)
|
||||
|
||||
// CreateBucketIfNotExists creates and returns a new nested bucket with
|
||||
// the given key if it does not already exist. Returns
|
||||
// ErrBucketNameRequired if the key is empty or ErrIncompatibleValue
|
||||
// if the key value is otherwise invalid for the particular database
|
||||
// backend. Other errors are possible depending on the implementation.
|
||||
CreateBucketIfNotExists(key []byte) (ReadWriteBucket, error)
|
||||
|
||||
// DeleteNestedBucket removes a nested bucket with the given key.
|
||||
// Returns ErrTxNotWritable if attempted against a read-only transaction
|
||||
// and ErrBucketNotFound if the specified bucket does not exist.
|
||||
DeleteNestedBucket(key []byte) error
|
||||
|
||||
// Put saves the specified key/value pair to the bucket. Keys that do
|
||||
// not already exist are added and keys that already exist are
|
||||
// overwritten. Returns ErrTxNotWritable if attempted against a
|
||||
// read-only transaction.
|
||||
Put(key, value []byte) error
|
||||
|
||||
// Delete removes the specified key from the bucket. Deleting a key
|
||||
// that does not exist does not return an error. Returns
|
||||
// ErrTxNotWritable if attempted against a read-only transaction.
|
||||
Delete(key []byte) error
|
||||
|
||||
// Cursor returns a new cursor, allowing for iteration over the bucket's
|
||||
// key/value pairs and nested buckets in forward or backward order.
|
||||
ReadWriteCursor() ReadWriteCursor
|
||||
|
||||
// Tx returns the bucket's transaction.
|
||||
Tx() ReadWriteTx
|
||||
}
|
||||
|
||||
// ReadCursor represents a bucket cursor that can be positioned at the start or
|
||||
// end of the bucket's key/value pairs and iterate over pairs in the bucket.
|
||||
// This type is only allowed to perform database read operations.
|
||||
type ReadCursor interface {
|
||||
// First positions the cursor at the first key/value pair and returns
|
||||
// the pair.
|
||||
First() (key, value []byte)
|
||||
|
||||
// Last positions the cursor at the last key/value pair and returns the
|
||||
// pair.
|
||||
Last() (key, value []byte)
|
||||
|
||||
// Next moves the cursor one key/value pair forward and returns the new
|
||||
// pair.
|
||||
Next() (key, value []byte)
|
||||
|
||||
// Prev moves the cursor one key/value pair backward and returns the new
|
||||
// pair.
|
||||
Prev() (key, value []byte)
|
||||
|
||||
// Seek positions the cursor at the passed seek key. If the key does
|
||||
// not exist, the cursor is moved to the next key after seek. Returns
|
||||
// the new pair.
|
||||
Seek(seek []byte) (key, value []byte)
|
||||
}
|
||||
|
||||
// ReadWriteCursor represents a bucket cursor that can be positioned at the
|
||||
// start or end of the bucket's key/value pairs and iterate over pairs in the
|
||||
// bucket. This abstraction is allowed to perform both database read and write
|
||||
// operations.
|
||||
type ReadWriteCursor interface {
|
||||
ReadCursor
|
||||
|
||||
// Delete removes the current key/value pair the cursor is at without
|
||||
// invalidating the cursor. Returns ErrIncompatibleValue if attempted
|
||||
// when the cursor points to a nested bucket.
|
||||
Delete() error
|
||||
}
|
||||
|
||||
// BucketIsEmpty returns whether the bucket is empty, that is, whether there are
|
||||
// no key/value pairs or nested buckets.
|
||||
func BucketIsEmpty(bucket ReadBucket) bool {
|
||||
k, v := bucket.ReadCursor().First()
|
||||
return k == nil && v == nil
|
||||
}
|
||||
|
||||
// DB represents an ACID database. All database access is performed through
|
||||
// read or read+write transactions.
|
||||
type DB interface {
|
||||
// BeginReadTx opens a database read transaction.
|
||||
BeginReadTx() (ReadTx, error)
|
||||
|
||||
// BeginReadWriteTx opens a database read+write transaction.
|
||||
BeginReadWriteTx() (ReadWriteTx, error)
|
||||
|
||||
// Copy writes a copy of the database to the provided writer. This
|
||||
// call will start a read-only transaction to perform all operations.
|
||||
Copy(w io.Writer) error
|
||||
|
||||
// Close cleanly shuts down the database and syncs all data.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// View opens a database read transaction and executes the function f with the
|
||||
// transaction passed as a parameter. After f exits, the transaction is rolled
|
||||
// back. If f errors, its error is returned, not a rollback error (if any
|
||||
// occur).
|
||||
func View(db DB, f func(tx ReadTx) error) error {
|
||||
tx, err := db.BeginReadTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = f(tx)
|
||||
rollbackErr := tx.Rollback()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rollbackErr != nil {
|
||||
return rollbackErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update opens a database read/write transaction and executes the function f
|
||||
// with the transaction passed as a parameter. After f exits, if f did not
|
||||
// error, the transaction is committed. Otherwise, if f did error, the
|
||||
// transaction is rolled back. If the rollback fails, the original error
|
||||
// returned by f is still returned. If the commit fails, the commit error is
|
||||
// returned.
|
||||
func Update(db DB, f func(tx ReadWriteTx) error) error {
|
||||
tx, err := db.BeginReadWriteTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = f(tx)
|
||||
if err != nil {
|
||||
// Want to return the original error, not a rollback error if
|
||||
// any occur.
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Driver defines a structure for backend drivers to use when they registered
|
||||
// themselves as a backend which implements the Db interface.
|
||||
type Driver struct {
|
||||
// DbType is the identifier used to uniquely identify a specific
|
||||
// database driver. There can be only one driver with the same name.
|
||||
DbType string
|
||||
|
||||
// Create is the function that will be invoked with all user-specified
|
||||
// arguments to create the database. This function must return
|
||||
// ErrDbExists if the database already exists.
|
||||
Create func(args ...interface{}) (DB, error)
|
||||
|
||||
// Open is the function that will be invoked with all user-specified
|
||||
// arguments to open the database. This function must return
|
||||
// ErrDbDoesNotExist if the database has not already been created.
|
||||
Open func(args ...interface{}) (DB, error)
|
||||
}
|
||||
|
||||
// driverList holds all of the registered database backends.
|
||||
var drivers = make(map[string]*Driver)
|
||||
|
||||
// RegisterDriver adds a backend database driver to available interfaces.
|
||||
// ErrDbTypeRegistered will be retruned if the database type for the driver has
|
||||
// already been registered.
|
||||
func RegisterDriver(driver Driver) error {
|
||||
if _, exists := drivers[driver.DbType]; exists {
|
||||
return ErrDbTypeRegistered
|
||||
}
|
||||
|
||||
drivers[driver.DbType] = &driver
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportedDrivers returns a slice of strings that represent the database
|
||||
// drivers that have been registered and are therefore supported.
|
||||
func SupportedDrivers() []string {
|
||||
supportedDBs := make([]string, 0, len(drivers))
|
||||
for _, drv := range drivers {
|
||||
supportedDBs = append(supportedDBs, drv.DbType)
|
||||
}
|
||||
return supportedDBs
|
||||
}
|
||||
|
||||
// Create intializes and opens a database for the specified type. The arguments
|
||||
// are specific to the database type driver. See the documentation for the
|
||||
// database driver for further details.
|
||||
//
|
||||
// ErrDbUnknownType will be returned if the the database type is not registered.
|
||||
func Create(dbType string, args ...interface{}) (DB, error) {
|
||||
drv, exists := drivers[dbType]
|
||||
if !exists {
|
||||
return nil, ErrDbUnknownType
|
||||
}
|
||||
|
||||
return drv.Create(args...)
|
||||
}
|
||||
|
||||
// Open opens an existing database for the specified type. The arguments are
|
||||
// specific to the database type driver. See the documentation for the database
|
||||
// driver for further details.
|
||||
//
|
||||
// ErrDbUnknownType will be returned if the the database type is not registered.
|
||||
func Open(dbType string, args ...interface{}) (DB, error) {
|
||||
drv, exists := drivers[dbType]
|
||||
if !exists {
|
||||
return nil, ErrDbUnknownType
|
||||
}
|
||||
|
||||
return drv.Open(args...)
|
||||
}
|
||||
43
vendor/github.com/btcsuite/btcwallet/walletdb/migration/log.go
generated
vendored
Normal file
43
vendor/github.com/btcsuite/btcwallet/walletdb/migration/log.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package migration
|
||||
|
||||
import "github.com/btcsuite/btclog"
|
||||
|
||||
// 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() {
|
||||
DisableLog()
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until either UseLogger or SetLogWriter are 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
|
||||
}
|
||||
|
||||
// LogClosure is a closure that can be printed with %v to be used to
|
||||
// generate expensive-to-create data for a detailed log level and avoid doing
|
||||
// the work if the data isn't printed.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the log closure and returns the results string.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over the passed function which allows
|
||||
// it to be used as a parameter in a logging function that is only invoked when
|
||||
// the logging level is such that the message will actually be logged.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
||||
162
vendor/github.com/btcsuite/btcwallet/walletdb/migration/manager.go
generated
vendored
Normal file
162
vendor/github.com/btcsuite/btcwallet/walletdb/migration/manager.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrReversion is an error returned when an attempt to revert to a
|
||||
// previous version is detected. This is done to provide safety to users
|
||||
// as some upgrades may not be backwards-compatible.
|
||||
ErrReversion = errors.New("reverting to a previous version is not " +
|
||||
"supported")
|
||||
)
|
||||
|
||||
// Version denotes the version number of the database. A migration can be used
|
||||
// to bring a previous version of the database to a later one.
|
||||
type Version struct {
|
||||
// Number represents the number of this version.
|
||||
Number uint32
|
||||
|
||||
// Migration represents a migration function that modifies the database
|
||||
// state. Care must be taken so that consequent migrations build off of
|
||||
// the previous one in order to ensure the consistency of the database.
|
||||
Migration func(walletdb.ReadWriteBucket) error
|
||||
}
|
||||
|
||||
// Manager is an interface that exposes the necessary methods needed in order to
|
||||
// migrate/upgrade a service. Each service (i.e., an implementation of this
|
||||
// interface) can then use the Upgrade function to perform any required database
|
||||
// migrations.
|
||||
type Manager interface {
|
||||
// Name returns the name of the service we'll be attempting to upgrade.
|
||||
Name() string
|
||||
|
||||
// Namespace returns the top-level bucket of the service.
|
||||
Namespace() walletdb.ReadWriteBucket
|
||||
|
||||
// CurrentVersion returns the current version of the service's database.
|
||||
CurrentVersion(walletdb.ReadBucket) (uint32, error)
|
||||
|
||||
// SetVersion sets the version of the service's database.
|
||||
SetVersion(walletdb.ReadWriteBucket, uint32) error
|
||||
|
||||
// Versions returns all of the available database versions of the
|
||||
// service.
|
||||
Versions() []Version
|
||||
}
|
||||
|
||||
// GetLatestVersion returns the latest version available from the given slice.
|
||||
func GetLatestVersion(versions []Version) uint32 {
|
||||
if len(versions) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Before determining the latest version number, we'll sort the slice to
|
||||
// ensure it reflects the last element.
|
||||
sort.Slice(versions, func(i, j int) bool {
|
||||
return versions[i].Number < versions[j].Number
|
||||
})
|
||||
|
||||
return versions[len(versions)-1].Number
|
||||
}
|
||||
|
||||
// VersionsToApply determines which versions should be applied as migrations
|
||||
// based on the current version.
|
||||
func VersionsToApply(currentVersion uint32, versions []Version) []Version {
|
||||
// Assuming the migration versions are in increasing order, we'll apply
|
||||
// any migrations that have a version number lower than our current one.
|
||||
var upgradeVersions []Version
|
||||
for _, version := range versions {
|
||||
if version.Number > currentVersion {
|
||||
upgradeVersions = append(upgradeVersions, version)
|
||||
}
|
||||
}
|
||||
|
||||
// Before returning, we'll sort the slice by its version number to
|
||||
// ensure the migrations are applied in their intended order.
|
||||
sort.Slice(upgradeVersions, func(i, j int) bool {
|
||||
return upgradeVersions[i].Number < upgradeVersions[j].Number
|
||||
})
|
||||
|
||||
return upgradeVersions
|
||||
}
|
||||
|
||||
// Upgrade attempts to upgrade a group of services exposed through the Manager
|
||||
// interface. Each service will go through its available versions and determine
|
||||
// whether it needs to apply any.
|
||||
//
|
||||
// NOTE: In order to guarantee fault-tolerance, each service upgrade should
|
||||
// happen within the same database transaction.
|
||||
func Upgrade(mgrs ...Manager) error {
|
||||
for _, mgr := range mgrs {
|
||||
if err := upgrade(mgr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgrade attempts to upgrade a service expose through its implementation of
|
||||
// the Manager interface. This function will determine whether any new versions
|
||||
// need to be applied based on the service's current version and latest
|
||||
// available one.
|
||||
func upgrade(mgr Manager) error {
|
||||
// We'll start by fetching the service's current and latest version.
|
||||
ns := mgr.Namespace()
|
||||
currentVersion, err := mgr.CurrentVersion(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versions := mgr.Versions()
|
||||
latestVersion := GetLatestVersion(versions)
|
||||
|
||||
switch {
|
||||
// If the current version is greater than the latest, then the service
|
||||
// is attempting to revert to a previous version that's possibly
|
||||
// backwards-incompatible. To prevent this, we'll return an error
|
||||
// indicating so.
|
||||
case currentVersion > latestVersion:
|
||||
return ErrReversion
|
||||
|
||||
// If the current version is behind the latest version, we'll need to
|
||||
// apply all of the newer versions in order to catch up to the latest.
|
||||
case currentVersion < latestVersion:
|
||||
versions := VersionsToApply(currentVersion, versions)
|
||||
mgrName := mgr.Name()
|
||||
ns := mgr.Namespace()
|
||||
|
||||
for _, version := range versions {
|
||||
log.Infof("Applying %v migration #%d", mgrName,
|
||||
version.Number)
|
||||
|
||||
// We'll only run a migration if there is one available
|
||||
// for this version.
|
||||
if version.Migration != nil {
|
||||
err := version.Migration(ns)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to apply %v "+
|
||||
"migration #%d: %v", mgrName,
|
||||
version.Number, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With all of the versions applied, we can now reflect the
|
||||
// latest version upon the service.
|
||||
if err := mgr.SetVersion(ns, latestVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the current version matches the latest one, there's no upgrade
|
||||
// needed and we can safely exit.
|
||||
case currentVersion == latestVersion:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
39
vendor/github.com/btcsuite/btcwallet/walletdb/test_coverage.txt
generated
vendored
Normal file
39
vendor/github.com/btcsuite/btcwallet/walletdb/test_coverage.txt
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
PASS
|
||||
coverage: 100.0% of statements
|
||||
ok github.com/conformal/btcwallet/walletdb 0.130s
|
||||
github.com\conformal\btcwallet\walletdb\interface.go:190: RegisterDriver 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\interface.go:201: SupportedDrivers 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\interface.go:214: Create 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\interface.go:228: Open 100.0%
|
||||
total: (statements) 100.0%
|
||||
============================================================
|
||||
PASS
|
||||
coverage: 91.7% of statements
|
||||
ok github.com/conformal/btcwallet/walletdb/bdb 0.149s
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:28: convertErr 76.9%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:74: Bucket 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:90: CreateBucket 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:103: CreateBucketIfNotExists 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:116: DeleteBucket 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:129: ForEach 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:136: Writable 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:145: Put 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:157: Get 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:166: Delete 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:185: RootBucket 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:193: Commit 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:201: Rollback 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:227: Begin 85.7%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:249: View 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:270: Update 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:294: Namespace 93.3%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:329: DeleteNamespace 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:339: WriteTo 0.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:348: Close 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:353: fileExists 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\db.go:364: openDB 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\driver.go:34: parseArgs 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\driver.go:50: openDBDriver 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\driver.go:60: createDBDriver 100.0%
|
||||
github.com\conformal\btcwallet\walletdb\bdb\driver.go:69: init 66.7%
|
||||
total: (statements) 91.7%
|
||||
16
vendor/github.com/btcsuite/btcwallet/wtxmgr/LICENSE
generated
vendored
Normal file
16
vendor/github.com/btcsuite/btcwallet/wtxmgr/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2013-2017 The btcsuite developers
|
||||
Copyright (c) 2015-2016 The Decred developers
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
45
vendor/github.com/btcsuite/btcwallet/wtxmgr/README.md
generated
vendored
Normal file
45
vendor/github.com/btcsuite/btcwallet/wtxmgr/README.md
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
wtxmgr
|
||||
======
|
||||
|
||||
[]
|
||||
(https://travis-ci.org/btcsuite/btcwallet)
|
||||
|
||||
Package wtxmgr provides storage and spend tracking of wallet transactions and
|
||||
their relevant input and outputs.
|
||||
|
||||
## Feature overview
|
||||
|
||||
- Storage for relevant wallet transactions
|
||||
- Ability to mark outputs as controlled by wallet
|
||||
- Unspent transaction output index
|
||||
- Balance tracking
|
||||
- Automatic spend tracking for transaction inserts and removals
|
||||
- Double spend detection and correction after blockchain reorgs
|
||||
- Scalable design:
|
||||
- Utilizes similar prefixes to allow cursor iteration over relevant transaction
|
||||
inputs and outputs
|
||||
- Programmatically detectable errors, including encapsulation of errors from
|
||||
packages it relies on
|
||||
- Operates under its own walletdb namespace
|
||||
|
||||
## Documentation
|
||||
|
||||
[]
|
||||
(http://godoc.org/github.com/btcsuite/btcwallet/wtxmgr)
|
||||
|
||||
Full `go doc` style documentation for the project can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://godoc.org/github.com/btcsuite/btcwallet/wtxmgr
|
||||
|
||||
You can also view the documentation locally once the package is installed with
|
||||
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||
http://localhost:6060/pkg/github.com/btcsuite/btcwallet/wtxmgr
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ go get github.com/btcsuite/btcwallet/wtxmgr
|
||||
```
|
||||
|
||||
Package wtxmgr is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
1496
vendor/github.com/btcsuite/btcwallet/wtxmgr/db.go
generated
vendored
Normal file
1496
vendor/github.com/btcsuite/btcwallet/wtxmgr/db.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
31
vendor/github.com/btcsuite/btcwallet/wtxmgr/doc.go
generated
vendored
Normal file
31
vendor/github.com/btcsuite/btcwallet/wtxmgr/doc.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package wtxmgr provides an implementation of a transaction database handling
|
||||
// spend tracking for a bitcoin wallet. Its primary purpose is to save
|
||||
// transactions with outputs spendable with wallet keys and transactions that
|
||||
// are signed by wallet keys in memory, handle spend tracking for unspent
|
||||
// outputs and newly-inserted transactions, and report the spendable balance
|
||||
// from each unspent transaction output. It uses walletdb as the backend for
|
||||
// storing the serialized transaction objects in buckets.
|
||||
//
|
||||
// Transaction outputs which are spendable by wallet keys are called credits
|
||||
// (because they credit to a wallet's total spendable balance). Transaction
|
||||
// inputs which spend previously-inserted credits are called debits (because
|
||||
// they debit from the wallet's spendable balance).
|
||||
//
|
||||
// Spend tracking is mostly automatic. When a new transaction is inserted, if
|
||||
// it spends from any unspent credits, they are automatically marked spent by
|
||||
// the new transaction, and each input which spent a credit is marked as a
|
||||
// debit. However, transaction outputs of inserted transactions must manually
|
||||
// marked as credits, as this package has no knowledge of wallet keys or
|
||||
// addresses, and therefore cannot determine which outputs may be spent.
|
||||
//
|
||||
// Details regarding individual transactions and their credits and debits may be
|
||||
// queried either by just a transaction hash, or by hash and block. When
|
||||
// querying for just a transaction hash, the most recent transaction with a
|
||||
// matching hash will be queried. However, because transaction hashes may
|
||||
// collide with other transaction hashes, methods to query for specific
|
||||
// transactions in the chain (or unmined) are provided as well.
|
||||
package wtxmgr
|
||||
97
vendor/github.com/btcsuite/btcwallet/wtxmgr/error.go
generated
vendored
Normal file
97
vendor/github.com/btcsuite/btcwallet/wtxmgr/error.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2015-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2016 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wtxmgr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrorCode identifies a category of error.
|
||||
type ErrorCode uint8
|
||||
|
||||
// These constants are used to identify a specific Error.
|
||||
const (
|
||||
// ErrDatabase indicates an error with the underlying database. When
|
||||
// this error code is set, the Err field of the Error will be
|
||||
// set to the underlying error returned from the database.
|
||||
ErrDatabase ErrorCode = iota
|
||||
|
||||
// ErrData describes an error where data stored in the transaction
|
||||
// database is incorrect. This may be due to missing values, values of
|
||||
// wrong sizes, or data from different buckets that is inconsistent with
|
||||
// itself. Recovering from an ErrData requires rebuilding all
|
||||
// transaction history or manual database surgery. If the failure was
|
||||
// not due to data corruption, this error category indicates a
|
||||
// programming error in this package.
|
||||
ErrData
|
||||
|
||||
// ErrInput describes an error where the variables passed into this
|
||||
// function by the caller are obviously incorrect. Examples include
|
||||
// passing transactions which do not serialize, or attempting to insert
|
||||
// a credit at an index for which no transaction output exists.
|
||||
ErrInput
|
||||
|
||||
// ErrAlreadyExists describes an error where creating the store cannot
|
||||
// continue because a store already exists in the namespace.
|
||||
ErrAlreadyExists
|
||||
|
||||
// ErrNoExists describes an error where the store cannot be opened due to
|
||||
// it not already existing in the namespace. This error should be
|
||||
// handled by creating a new store.
|
||||
ErrNoExists
|
||||
|
||||
// ErrNeedsUpgrade describes an error during store opening where the
|
||||
// database contains an older version of the store.
|
||||
ErrNeedsUpgrade
|
||||
|
||||
// ErrUnknownVersion describes an error where the store already exists
|
||||
// but the database version is newer than latest version known to this
|
||||
// software. This likely indicates an outdated binary.
|
||||
ErrUnknownVersion
|
||||
)
|
||||
|
||||
var errStrs = [...]string{
|
||||
ErrDatabase: "ErrDatabase",
|
||||
ErrData: "ErrData",
|
||||
ErrInput: "ErrInput",
|
||||
ErrAlreadyExists: "ErrAlreadyExists",
|
||||
ErrNoExists: "ErrNoExists",
|
||||
ErrNeedsUpgrade: "ErrNeedsUpgrade",
|
||||
ErrUnknownVersion: "ErrUnknownVersion",
|
||||
}
|
||||
|
||||
// String returns the ErrorCode as a human-readable name.
|
||||
func (e ErrorCode) String() string {
|
||||
if e < ErrorCode(len(errStrs)) {
|
||||
return errStrs[e]
|
||||
}
|
||||
return fmt.Sprintf("ErrorCode(%d)", e)
|
||||
}
|
||||
|
||||
// Error provides a single type for errors that can happen during Store
|
||||
// operation.
|
||||
type Error struct {
|
||||
Code ErrorCode // Describes the kind of error
|
||||
Desc string // Human readable description of the issue
|
||||
Err error // Underlying error, optional
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e Error) Error() string {
|
||||
if e.Err != nil {
|
||||
return e.Desc + ": " + e.Err.Error()
|
||||
}
|
||||
return e.Desc
|
||||
}
|
||||
|
||||
func storeError(c ErrorCode, desc string, err error) Error {
|
||||
return Error{Code: c, Desc: desc, Err: err}
|
||||
}
|
||||
|
||||
// IsNoExists returns whether an error is a Error with the ErrNoExists error
|
||||
// code.
|
||||
func IsNoExists(err error) bool {
|
||||
serr, ok := err.(Error)
|
||||
return ok && serr.Code == ErrNoExists
|
||||
}
|
||||
10
vendor/github.com/btcsuite/btcwallet/wtxmgr/go.mod
generated
vendored
Normal file
10
vendor/github.com/btcsuite/btcwallet/wtxmgr/go.mod
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
module github.com/btcsuite/btcwallet/wtxmgr
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
|
||||
github.com/btcsuite/btcwallet/walletdb v1.0.0
|
||||
)
|
||||
56
vendor/github.com/btcsuite/btcwallet/wtxmgr/go.sum
generated
vendored
Normal file
56
vendor/github.com/btcsuite/btcwallet/wtxmgr/go.sum
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3 h1:A/EVblehb75cUgXA5njHPn0kLAsykn6mJGz7rnmW5W0=
|
||||
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.0.0 h1:mheT7vCWK5EP6rZzhxsQ7ms9+yX4VE8bwiJctECBeNw=
|
||||
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
117
vendor/github.com/btcsuite/btcwallet/wtxmgr/kahnsort.go
generated
vendored
Normal file
117
vendor/github.com/btcsuite/btcwallet/wtxmgr/kahnsort.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wtxmgr
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
type graphNode struct {
|
||||
value *wire.MsgTx
|
||||
outEdges []*chainhash.Hash
|
||||
inDegree int
|
||||
}
|
||||
|
||||
type hashGraph map[chainhash.Hash]graphNode
|
||||
|
||||
func makeGraph(set map[chainhash.Hash]*wire.MsgTx) hashGraph {
|
||||
graph := make(hashGraph)
|
||||
|
||||
for _, tx := range set {
|
||||
// Add a node for every transaction. The output edges and input
|
||||
// degree are set by iterating over each transaction's inputs
|
||||
// below.
|
||||
txHash := tx.TxHash()
|
||||
if _, ok := graph[txHash]; !ok {
|
||||
graph[txHash] = graphNode{value: tx}
|
||||
}
|
||||
|
||||
inputLoop:
|
||||
for _, input := range tx.TxIn {
|
||||
// Transaction inputs that reference transactions not
|
||||
// included in the set do not create any (local) graph
|
||||
// edges.
|
||||
if _, ok := set[input.PreviousOutPoint.Hash]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
inputNode := graph[input.PreviousOutPoint.Hash]
|
||||
|
||||
// Skip duplicate edges.
|
||||
for _, outEdge := range inputNode.outEdges {
|
||||
if *outEdge == input.PreviousOutPoint.Hash {
|
||||
continue inputLoop
|
||||
}
|
||||
}
|
||||
|
||||
// Mark a directed edge from the previous transaction
|
||||
// hash to this transaction and increase the input
|
||||
// degree for this transaction's node.
|
||||
inputTx := inputNode.value
|
||||
if inputTx == nil {
|
||||
inputTx = set[input.PreviousOutPoint.Hash]
|
||||
}
|
||||
graph[input.PreviousOutPoint.Hash] = graphNode{
|
||||
value: inputTx,
|
||||
outEdges: append(inputNode.outEdges, &txHash),
|
||||
inDegree: inputNode.inDegree,
|
||||
}
|
||||
node := graph[txHash]
|
||||
graph[txHash] = graphNode{
|
||||
value: tx,
|
||||
outEdges: node.outEdges,
|
||||
inDegree: node.inDegree + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
// graphRoots returns the roots of the graph. That is, it returns the node's
|
||||
// values for all nodes which contain an input degree of 0.
|
||||
func graphRoots(graph hashGraph) []*wire.MsgTx {
|
||||
roots := make([]*wire.MsgTx, 0, len(graph))
|
||||
for _, node := range graph {
|
||||
if node.inDegree == 0 {
|
||||
roots = append(roots, node.value)
|
||||
}
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
// DependencySort topologically sorts a set of transactions by their dependency
|
||||
// order. It is implemented using Kahn's algorithm.
|
||||
func DependencySort(txs map[chainhash.Hash]*wire.MsgTx) []*wire.MsgTx {
|
||||
graph := makeGraph(txs)
|
||||
s := graphRoots(graph)
|
||||
|
||||
// If there are no edges (no transactions from the map reference each
|
||||
// other), then Kahn's algorithm is unnecessary.
|
||||
if len(s) == len(txs) {
|
||||
return s
|
||||
}
|
||||
|
||||
sorted := make([]*wire.MsgTx, 0, len(txs))
|
||||
for len(s) != 0 {
|
||||
tx := s[0]
|
||||
s = s[1:]
|
||||
sorted = append(sorted, tx)
|
||||
|
||||
n := graph[tx.TxHash()]
|
||||
for _, mHash := range n.outEdges {
|
||||
m := graph[*mHash]
|
||||
if m.inDegree != 0 {
|
||||
m.inDegree--
|
||||
graph[*mHash] = m
|
||||
if m.inDegree == 0 {
|
||||
s = append(s, m.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sorted
|
||||
}
|
||||
47
vendor/github.com/btcsuite/btcwallet/wtxmgr/log.go
generated
vendored
Normal file
47
vendor/github.com/btcsuite/btcwallet/wtxmgr/log.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wtxmgr
|
||||
|
||||
import "github.com/btcsuite/btclog"
|
||||
|
||||
// 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() {
|
||||
DisableLog()
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until either UseLogger or SetLogWriter are 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
|
||||
}
|
||||
|
||||
// LogClosure is a closure that can be printed with %v to be used to
|
||||
// generate expensive-to-create data for a detailed log level and avoid doing
|
||||
// the work if the data isn't printed.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the log closure and returns the results string.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over the passed function which allows
|
||||
// it to be used as a parameter in a logging function that is only invoked when
|
||||
// the logging level is such that the message will actually be logged.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
||||
110
vendor/github.com/btcsuite/btcwallet/wtxmgr/migrations.go
generated
vendored
Normal file
110
vendor/github.com/btcsuite/btcwallet/wtxmgr/migrations.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
package wtxmgr
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/btcsuite/btcwallet/walletdb/migration"
|
||||
)
|
||||
|
||||
// versions is a list of the different database versions. The last entry should
|
||||
// reflect the latest database state. If the database happens to be at a version
|
||||
// number lower than the latest, migrations will be performed in order to catch
|
||||
// it up.
|
||||
var versions = []migration.Version{
|
||||
{
|
||||
Number: 1,
|
||||
Migration: nil,
|
||||
},
|
||||
{
|
||||
Number: 2,
|
||||
Migration: dropTransactionHistory,
|
||||
},
|
||||
}
|
||||
|
||||
// getLatestVersion returns the version number of the latest database version.
|
||||
func getLatestVersion() uint32 {
|
||||
return versions[len(versions)-1].Number
|
||||
}
|
||||
|
||||
// MigrationManager is an implementation of the migration.Manager interface that
|
||||
// will be used to handle migrations for the address manager. It exposes the
|
||||
// necessary parameters required to successfully perform migrations.
|
||||
type MigrationManager struct {
|
||||
ns walletdb.ReadWriteBucket
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure that MigrationManager implements the
|
||||
// migration.Manager interface.
|
||||
var _ migration.Manager = (*MigrationManager)(nil)
|
||||
|
||||
// NewMigrationManager creates a new migration manager for the transaction
|
||||
// manager. The given bucket should reflect the top-level bucket in which all
|
||||
// of the transaction manager's data is contained within.
|
||||
func NewMigrationManager(ns walletdb.ReadWriteBucket) *MigrationManager {
|
||||
return &MigrationManager{ns: ns}
|
||||
}
|
||||
|
||||
// Name returns the name of the service we'll be attempting to upgrade.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Name() string {
|
||||
return "wallet transaction manager"
|
||||
}
|
||||
|
||||
// Namespace returns the top-level bucket of the service.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Namespace() walletdb.ReadWriteBucket {
|
||||
return m.ns
|
||||
}
|
||||
|
||||
// CurrentVersion returns the current version of the service's database.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) CurrentVersion(ns walletdb.ReadBucket) (uint32, error) {
|
||||
if ns == nil {
|
||||
ns = m.ns
|
||||
}
|
||||
return fetchVersion(m.ns)
|
||||
}
|
||||
|
||||
// SetVersion sets the version of the service's database.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) SetVersion(ns walletdb.ReadWriteBucket,
|
||||
version uint32) error {
|
||||
|
||||
if ns == nil {
|
||||
ns = m.ns
|
||||
}
|
||||
return putVersion(m.ns, version)
|
||||
}
|
||||
|
||||
// Versions returns all of the available database versions of the service.
|
||||
//
|
||||
// NOTE: This method is part of the migration.Manager interface.
|
||||
func (m *MigrationManager) Versions() []migration.Version {
|
||||
return versions
|
||||
}
|
||||
|
||||
// dropTransactionHistory is a migration that attempts to recreate the
|
||||
// transaction store with a clean state.
|
||||
func dropTransactionHistory(ns walletdb.ReadWriteBucket) error {
|
||||
log.Info("Dropping wallet transaction history")
|
||||
|
||||
// To drop the store's transaction history, we'll need to remove all of
|
||||
// the relevant descendant buckets and key/value pairs.
|
||||
if err := deleteBuckets(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.Delete(rootMinedBalance); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// With everything removed, we'll now recreate our buckets.
|
||||
if err := createBuckets(ns); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finally, we'll insert a 0 value for our mined balance.
|
||||
return putMinedBalance(ns, 0)
|
||||
}
|
||||
455
vendor/github.com/btcsuite/btcwallet/wtxmgr/query.go
generated
vendored
Normal file
455
vendor/github.com/btcsuite/btcwallet/wtxmgr/query.go
generated
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
// Copyright (c) 2015-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2016 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wtxmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
// CreditRecord contains metadata regarding a transaction credit for a known
|
||||
// transaction. Further details may be looked up by indexing a wire.MsgTx.TxOut
|
||||
// with the Index field.
|
||||
type CreditRecord struct {
|
||||
Amount btcutil.Amount
|
||||
Index uint32
|
||||
Spent bool
|
||||
Change bool
|
||||
}
|
||||
|
||||
// DebitRecord contains metadata regarding a transaction debit for a known
|
||||
// transaction. Further details may be looked up by indexing a wire.MsgTx.TxIn
|
||||
// with the Index field.
|
||||
type DebitRecord struct {
|
||||
Amount btcutil.Amount
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// TxDetails is intended to provide callers with access to rich details
|
||||
// regarding a relevant transaction and which inputs and outputs are credit or
|
||||
// debits.
|
||||
type TxDetails struct {
|
||||
TxRecord
|
||||
Block BlockMeta
|
||||
Credits []CreditRecord
|
||||
Debits []DebitRecord
|
||||
}
|
||||
|
||||
// minedTxDetails fetches the TxDetails for the mined transaction with hash
|
||||
// txHash and the passed tx record key and value.
|
||||
func (s *Store) minedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, recKey, recVal []byte) (*TxDetails, error) {
|
||||
var details TxDetails
|
||||
|
||||
// Parse transaction record k/v, lookup the full block record for the
|
||||
// block time, and read all matching credits, debits.
|
||||
err := readRawTxRecord(txHash, recVal, &details.TxRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = readRawTxRecordBlock(recKey, &details.Block.Block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details.Block.Time, err = fetchBlockTime(ns, details.Block.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credIter := makeReadCreditIterator(ns, recKey)
|
||||
for credIter.next() {
|
||||
if int(credIter.elem.Index) >= len(details.MsgTx.TxOut) {
|
||||
str := "saved credit index exceeds number of outputs"
|
||||
return nil, storeError(ErrData, str, nil)
|
||||
}
|
||||
|
||||
// The credit iterator does not record whether this credit was
|
||||
// spent by an unmined transaction, so check that here.
|
||||
if !credIter.elem.Spent {
|
||||
k := canonicalOutPoint(txHash, credIter.elem.Index)
|
||||
spent := existsRawUnminedInput(ns, k) != nil
|
||||
credIter.elem.Spent = spent
|
||||
}
|
||||
details.Credits = append(details.Credits, credIter.elem)
|
||||
}
|
||||
if credIter.err != nil {
|
||||
return nil, credIter.err
|
||||
}
|
||||
|
||||
debIter := makeReadDebitIterator(ns, recKey)
|
||||
for debIter.next() {
|
||||
if int(debIter.elem.Index) >= len(details.MsgTx.TxIn) {
|
||||
str := "saved debit index exceeds number of inputs"
|
||||
return nil, storeError(ErrData, str, nil)
|
||||
}
|
||||
|
||||
details.Debits = append(details.Debits, debIter.elem)
|
||||
}
|
||||
return &details, debIter.err
|
||||
}
|
||||
|
||||
// unminedTxDetails fetches the TxDetails for the unmined transaction with the
|
||||
// hash txHash and the passed unmined record value.
|
||||
func (s *Store) unminedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, v []byte) (*TxDetails, error) {
|
||||
details := TxDetails{
|
||||
Block: BlockMeta{Block: Block{Height: -1}},
|
||||
}
|
||||
err := readRawTxRecord(txHash, v, &details.TxRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
it := makeReadUnminedCreditIterator(ns, txHash)
|
||||
for it.next() {
|
||||
if int(it.elem.Index) >= len(details.MsgTx.TxOut) {
|
||||
str := "saved credit index exceeds number of outputs"
|
||||
return nil, storeError(ErrData, str, nil)
|
||||
}
|
||||
|
||||
// Set the Spent field since this is not done by the iterator.
|
||||
it.elem.Spent = existsRawUnminedInput(ns, it.ck) != nil
|
||||
details.Credits = append(details.Credits, it.elem)
|
||||
}
|
||||
if it.err != nil {
|
||||
return nil, it.err
|
||||
}
|
||||
|
||||
// Debit records are not saved for unmined transactions. Instead, they
|
||||
// must be looked up for each transaction input manually. There are two
|
||||
// kinds of previous credits that may be debited by an unmined
|
||||
// transaction: mined unspent outputs (which remain marked unspent even
|
||||
// when spent by an unmined transaction), and credits from other unmined
|
||||
// transactions. Both situations must be considered.
|
||||
for i, output := range details.MsgTx.TxIn {
|
||||
opKey := canonicalOutPoint(&output.PreviousOutPoint.Hash,
|
||||
output.PreviousOutPoint.Index)
|
||||
credKey := existsRawUnspent(ns, opKey)
|
||||
if credKey != nil {
|
||||
v := existsRawCredit(ns, credKey)
|
||||
amount, err := fetchRawCreditAmount(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details.Debits = append(details.Debits, DebitRecord{
|
||||
Amount: amount,
|
||||
Index: uint32(i),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
v := existsRawUnminedCredit(ns, opKey)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
amount, err := fetchRawCreditAmount(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details.Debits = append(details.Debits, DebitRecord{
|
||||
Amount: amount,
|
||||
Index: uint32(i),
|
||||
})
|
||||
}
|
||||
|
||||
return &details, nil
|
||||
}
|
||||
|
||||
// TxDetails looks up all recorded details regarding a transaction with some
|
||||
// hash. In case of a hash collision, the most recent transaction with a
|
||||
// matching hash is returned.
|
||||
//
|
||||
// Not finding a transaction with this hash is not an error. In this case,
|
||||
// a nil TxDetails is returned.
|
||||
func (s *Store) TxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash) (*TxDetails, error) {
|
||||
// First, check whether there exists an unmined transaction with this
|
||||
// hash. Use it if found.
|
||||
v := existsRawUnmined(ns, txHash[:])
|
||||
if v != nil {
|
||||
return s.unminedTxDetails(ns, txHash, v)
|
||||
}
|
||||
|
||||
// Otherwise, if there exists a mined transaction with this matching
|
||||
// hash, skip over to the newest and begin fetching all details.
|
||||
k, v := latestTxRecord(ns, txHash)
|
||||
if v == nil {
|
||||
// not found
|
||||
return nil, nil
|
||||
}
|
||||
return s.minedTxDetails(ns, txHash, k, v)
|
||||
}
|
||||
|
||||
// UniqueTxDetails looks up all recorded details for a transaction recorded
|
||||
// mined in some particular block, or an unmined transaction if block is nil.
|
||||
//
|
||||
// Not finding a transaction with this hash from this block is not an error. In
|
||||
// this case, a nil TxDetails is returned.
|
||||
func (s *Store) UniqueTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash,
|
||||
block *Block) (*TxDetails, error) {
|
||||
|
||||
if block == nil {
|
||||
v := existsRawUnmined(ns, txHash[:])
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return s.unminedTxDetails(ns, txHash, v)
|
||||
}
|
||||
|
||||
k, v := existsTxRecord(ns, txHash, block)
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return s.minedTxDetails(ns, txHash, k, v)
|
||||
}
|
||||
|
||||
// rangeUnminedTransactions executes the function f with TxDetails for every
|
||||
// unmined transaction. f is not executed if no unmined transactions exist.
|
||||
// Error returns from f (if any) are propigated to the caller. Returns true
|
||||
// (signaling breaking out of a RangeTransactions) iff f executes and returns
|
||||
// true.
|
||||
func (s *Store) rangeUnminedTransactions(ns walletdb.ReadBucket, f func([]TxDetails) (bool, error)) (bool, error) {
|
||||
var details []TxDetails
|
||||
err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error {
|
||||
if len(k) < 32 {
|
||||
str := fmt.Sprintf("%s: short key (expected %d "+
|
||||
"bytes, read %d)", bucketUnmined, 32, len(k))
|
||||
return storeError(ErrData, str, nil)
|
||||
}
|
||||
|
||||
var txHash chainhash.Hash
|
||||
copy(txHash[:], k)
|
||||
detail, err := s.unminedTxDetails(ns, &txHash, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Because the key was created while foreach-ing over the
|
||||
// bucket, it should be impossible for unminedTxDetails to ever
|
||||
// successfully return a nil details struct.
|
||||
details = append(details, *detail)
|
||||
return nil
|
||||
})
|
||||
if err == nil && len(details) > 0 {
|
||||
return f(details)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// rangeBlockTransactions executes the function f with TxDetails for every block
|
||||
// between heights begin and end (reverse order when end > begin) until f
|
||||
// returns true, or the transactions from block is processed. Returns true iff
|
||||
// f executes and returns true.
|
||||
func (s *Store) rangeBlockTransactions(ns walletdb.ReadBucket, begin, end int32,
|
||||
f func([]TxDetails) (bool, error)) (bool, error) {
|
||||
|
||||
// Mempool height is considered a high bound.
|
||||
if begin < 0 {
|
||||
begin = int32(^uint32(0) >> 1)
|
||||
}
|
||||
if end < 0 {
|
||||
end = int32(^uint32(0) >> 1)
|
||||
}
|
||||
|
||||
var blockIter blockIterator
|
||||
var advance func(*blockIterator) bool
|
||||
if begin < end {
|
||||
// Iterate in forwards order
|
||||
blockIter = makeReadBlockIterator(ns, begin)
|
||||
advance = func(it *blockIterator) bool {
|
||||
if !it.next() {
|
||||
return false
|
||||
}
|
||||
return it.elem.Height <= end
|
||||
}
|
||||
} else {
|
||||
// Iterate in backwards order, from begin -> end.
|
||||
blockIter = makeReadBlockIterator(ns, begin)
|
||||
advance = func(it *blockIterator) bool {
|
||||
if !it.prev() {
|
||||
return false
|
||||
}
|
||||
return end <= it.elem.Height
|
||||
}
|
||||
}
|
||||
|
||||
var details []TxDetails
|
||||
for advance(&blockIter) {
|
||||
block := &blockIter.elem
|
||||
|
||||
if cap(details) < len(block.transactions) {
|
||||
details = make([]TxDetails, 0, len(block.transactions))
|
||||
} else {
|
||||
details = details[:0]
|
||||
}
|
||||
|
||||
for _, txHash := range block.transactions {
|
||||
k := keyTxRecord(&txHash, &block.Block)
|
||||
v := existsRawTxRecord(ns, k)
|
||||
if v == nil {
|
||||
str := fmt.Sprintf("missing transaction %v for "+
|
||||
"block %v", txHash, block.Height)
|
||||
return false, storeError(ErrData, str, nil)
|
||||
}
|
||||
detail := TxDetails{
|
||||
Block: BlockMeta{
|
||||
Block: block.Block,
|
||||
Time: block.Time,
|
||||
},
|
||||
}
|
||||
err := readRawTxRecord(&txHash, v, &detail.TxRecord)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
credIter := makeReadCreditIterator(ns, k)
|
||||
for credIter.next() {
|
||||
if int(credIter.elem.Index) >= len(detail.MsgTx.TxOut) {
|
||||
str := "saved credit index exceeds number of outputs"
|
||||
return false, storeError(ErrData, str, nil)
|
||||
}
|
||||
|
||||
// The credit iterator does not record whether
|
||||
// this credit was spent by an unmined
|
||||
// transaction, so check that here.
|
||||
if !credIter.elem.Spent {
|
||||
k := canonicalOutPoint(&txHash, credIter.elem.Index)
|
||||
spent := existsRawUnminedInput(ns, k) != nil
|
||||
credIter.elem.Spent = spent
|
||||
}
|
||||
detail.Credits = append(detail.Credits, credIter.elem)
|
||||
}
|
||||
if credIter.err != nil {
|
||||
return false, credIter.err
|
||||
}
|
||||
|
||||
debIter := makeReadDebitIterator(ns, k)
|
||||
for debIter.next() {
|
||||
if int(debIter.elem.Index) >= len(detail.MsgTx.TxIn) {
|
||||
str := "saved debit index exceeds number of inputs"
|
||||
return false, storeError(ErrData, str, nil)
|
||||
}
|
||||
|
||||
detail.Debits = append(detail.Debits, debIter.elem)
|
||||
}
|
||||
if debIter.err != nil {
|
||||
return false, debIter.err
|
||||
}
|
||||
|
||||
details = append(details, detail)
|
||||
}
|
||||
|
||||
// Every block record must have at least one transaction, so it
|
||||
// is safe to call f.
|
||||
brk, err := f(details)
|
||||
if err != nil || brk {
|
||||
return brk, err
|
||||
}
|
||||
}
|
||||
return false, blockIter.err
|
||||
}
|
||||
|
||||
// RangeTransactions runs the function f on all transaction details between
|
||||
// blocks on the best chain over the height range [begin,end]. The special
|
||||
// height -1 may be used to also include unmined transactions. If the end
|
||||
// height comes before the begin height, blocks are iterated in reverse order
|
||||
// and unmined transactions (if any) are processed first.
|
||||
//
|
||||
// The function f may return an error which, if non-nil, is propagated to the
|
||||
// caller. Additionally, a boolean return value allows exiting the function
|
||||
// early without reading any additional transactions early when true.
|
||||
//
|
||||
// All calls to f are guaranteed to be passed a slice with more than zero
|
||||
// elements. The slice may be reused for multiple blocks, so it is not safe to
|
||||
// use it after the loop iteration it was acquired.
|
||||
func (s *Store) RangeTransactions(ns walletdb.ReadBucket, begin, end int32,
|
||||
f func([]TxDetails) (bool, error)) error {
|
||||
|
||||
var addedUnmined bool
|
||||
if begin < 0 {
|
||||
brk, err := s.rangeUnminedTransactions(ns, f)
|
||||
if err != nil || brk {
|
||||
return err
|
||||
}
|
||||
addedUnmined = true
|
||||
}
|
||||
|
||||
brk, err := s.rangeBlockTransactions(ns, begin, end, f)
|
||||
if err == nil && !brk && !addedUnmined && end < 0 {
|
||||
_, err = s.rangeUnminedTransactions(ns, f)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// PreviousPkScripts returns a slice of previous output scripts for each credit
|
||||
// output this transaction record debits from.
|
||||
func (s *Store) PreviousPkScripts(ns walletdb.ReadBucket, rec *TxRecord, block *Block) ([][]byte, error) {
|
||||
var pkScripts [][]byte
|
||||
|
||||
if block == nil {
|
||||
for _, input := range rec.MsgTx.TxIn {
|
||||
prevOut := &input.PreviousOutPoint
|
||||
|
||||
// Input may spend a previous unmined output, a
|
||||
// mined output (which would still be marked
|
||||
// unspent), or neither.
|
||||
|
||||
v := existsRawUnmined(ns, prevOut.Hash[:])
|
||||
if v != nil {
|
||||
// Ensure a credit exists for this
|
||||
// unmined transaction before including
|
||||
// the output script.
|
||||
k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
|
||||
if existsRawUnminedCredit(ns, k) == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pkScript, err := fetchRawTxRecordPkScript(
|
||||
prevOut.Hash[:], v, prevOut.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScripts = append(pkScripts, pkScript)
|
||||
continue
|
||||
}
|
||||
|
||||
_, credKey := existsUnspent(ns, prevOut)
|
||||
if credKey != nil {
|
||||
k := extractRawCreditTxRecordKey(credKey)
|
||||
v = existsRawTxRecord(ns, k)
|
||||
pkScript, err := fetchRawTxRecordPkScript(k, v,
|
||||
prevOut.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScripts = append(pkScripts, pkScript)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return pkScripts, nil
|
||||
}
|
||||
|
||||
recKey := keyTxRecord(&rec.Hash, block)
|
||||
it := makeReadDebitIterator(ns, recKey)
|
||||
for it.next() {
|
||||
credKey := extractRawDebitCreditKey(it.cv)
|
||||
index := extractRawCreditIndex(credKey)
|
||||
k := extractRawCreditTxRecordKey(credKey)
|
||||
v := existsRawTxRecord(ns, k)
|
||||
pkScript, err := fetchRawTxRecordPkScript(k, v, index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScripts = append(pkScripts, pkScript)
|
||||
}
|
||||
if it.err != nil {
|
||||
return nil, it.err
|
||||
}
|
||||
|
||||
return pkScripts, nil
|
||||
}
|
||||
925
vendor/github.com/btcsuite/btcwallet/wtxmgr/tx.go
generated
vendored
Normal file
925
vendor/github.com/btcsuite/btcwallet/wtxmgr/tx.go
generated
vendored
Normal file
@@ -0,0 +1,925 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2016 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wtxmgr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
// Block contains the minimum amount of data to uniquely identify any block on
|
||||
// either the best or side chain.
|
||||
type Block struct {
|
||||
Hash chainhash.Hash
|
||||
Height int32
|
||||
}
|
||||
|
||||
// BlockMeta contains the unique identification for a block and any metadata
|
||||
// pertaining to the block. At the moment, this additional metadata only
|
||||
// includes the block time from the block header.
|
||||
type BlockMeta struct {
|
||||
Block
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// blockRecord is an in-memory representation of the block record saved in the
|
||||
// database.
|
||||
type blockRecord struct {
|
||||
Block
|
||||
Time time.Time
|
||||
transactions []chainhash.Hash
|
||||
}
|
||||
|
||||
// incidence records the block hash and blockchain height of a mined transaction.
|
||||
// Since a transaction hash alone is not enough to uniquely identify a mined
|
||||
// transaction (duplicate transaction hashes are allowed), the incidence is used
|
||||
// instead.
|
||||
type incidence struct {
|
||||
txHash chainhash.Hash
|
||||
block Block
|
||||
}
|
||||
|
||||
// indexedIncidence records the transaction incidence and an input or output
|
||||
// index.
|
||||
type indexedIncidence struct {
|
||||
incidence
|
||||
index uint32
|
||||
}
|
||||
|
||||
// debit records the debits a transaction record makes from previous wallet
|
||||
// transaction credits.
|
||||
type debit struct {
|
||||
txHash chainhash.Hash
|
||||
index uint32
|
||||
amount btcutil.Amount
|
||||
spends indexedIncidence
|
||||
}
|
||||
|
||||
// credit describes a transaction output which was or is spendable by wallet.
|
||||
type credit struct {
|
||||
outPoint wire.OutPoint
|
||||
block Block
|
||||
amount btcutil.Amount
|
||||
change bool
|
||||
spentBy indexedIncidence // Index == ^uint32(0) if unspent
|
||||
}
|
||||
|
||||
// TxRecord represents a transaction managed by the Store.
|
||||
type TxRecord struct {
|
||||
MsgTx wire.MsgTx
|
||||
Hash chainhash.Hash
|
||||
Received time.Time
|
||||
SerializedTx []byte // Optional: may be nil
|
||||
}
|
||||
|
||||
// NewTxRecord creates a new transaction record that may be inserted into the
|
||||
// store. It uses memoization to save the transaction hash and the serialized
|
||||
// transaction.
|
||||
func NewTxRecord(serializedTx []byte, received time.Time) (*TxRecord, error) {
|
||||
rec := &TxRecord{
|
||||
Received: received,
|
||||
SerializedTx: serializedTx,
|
||||
}
|
||||
err := rec.MsgTx.Deserialize(bytes.NewReader(serializedTx))
|
||||
if err != nil {
|
||||
str := "failed to deserialize transaction"
|
||||
return nil, storeError(ErrInput, str, err)
|
||||
}
|
||||
copy(rec.Hash[:], chainhash.DoubleHashB(serializedTx))
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
// NewTxRecordFromMsgTx creates a new transaction record that may be inserted
|
||||
// into the store.
|
||||
func NewTxRecordFromMsgTx(msgTx *wire.MsgTx, received time.Time) (*TxRecord, error) {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, msgTx.SerializeSize()))
|
||||
err := msgTx.Serialize(buf)
|
||||
if err != nil {
|
||||
str := "failed to serialize transaction"
|
||||
return nil, storeError(ErrInput, str, err)
|
||||
}
|
||||
rec := &TxRecord{
|
||||
MsgTx: *msgTx,
|
||||
Received: received,
|
||||
SerializedTx: buf.Bytes(),
|
||||
Hash: msgTx.TxHash(),
|
||||
}
|
||||
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
// Credit is the type representing a transaction output which was spent or
|
||||
// is still spendable by wallet. A UTXO is an unspent Credit, but not all
|
||||
// Credits are UTXOs.
|
||||
type Credit struct {
|
||||
wire.OutPoint
|
||||
BlockMeta
|
||||
Amount btcutil.Amount
|
||||
PkScript []byte
|
||||
Received time.Time
|
||||
FromCoinBase bool
|
||||
}
|
||||
|
||||
// Store implements a transaction store for storing and managing wallet
|
||||
// transactions.
|
||||
type Store struct {
|
||||
chainParams *chaincfg.Params
|
||||
|
||||
// Event callbacks. These execute in the same goroutine as the wtxmgr
|
||||
// caller.
|
||||
NotifyUnspent func(hash *chainhash.Hash, index uint32)
|
||||
}
|
||||
|
||||
// Open opens the wallet transaction store from a walletdb namespace. If the
|
||||
// store does not exist, ErrNoExist is returned.
|
||||
func Open(ns walletdb.ReadBucket, chainParams *chaincfg.Params) (*Store, error) {
|
||||
// Open the store.
|
||||
err := openStore(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &Store{chainParams, nil} // TODO: set callbacks
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Create creates a new persistent transaction store in the walletdb namespace.
|
||||
// Creating the store when one already exists in this namespace will error with
|
||||
// ErrAlreadyExists.
|
||||
func Create(ns walletdb.ReadWriteBucket) error {
|
||||
return createStore(ns)
|
||||
}
|
||||
|
||||
// updateMinedBalance updates the mined balance within the store, if changed,
|
||||
// after processing the given transaction record.
|
||||
func (s *Store) updateMinedBalance(ns walletdb.ReadWriteBucket, rec *TxRecord,
|
||||
block *BlockMeta) error {
|
||||
|
||||
// Fetch the mined balance in case we need to update it.
|
||||
minedBalance, err := fetchMinedBalance(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add a debit record for each unspent credit spent by this transaction.
|
||||
// The index is set in each iteration below.
|
||||
spender := indexedIncidence{
|
||||
incidence: incidence{
|
||||
txHash: rec.Hash,
|
||||
block: block.Block,
|
||||
},
|
||||
}
|
||||
|
||||
newMinedBalance := minedBalance
|
||||
for i, input := range rec.MsgTx.TxIn {
|
||||
unspentKey, credKey := existsUnspent(ns, &input.PreviousOutPoint)
|
||||
if credKey == nil {
|
||||
// Debits for unmined transactions are not explicitly
|
||||
// tracked. Instead, all previous outputs spent by any
|
||||
// unmined transaction are added to a map for quick
|
||||
// lookups when it must be checked whether a mined
|
||||
// output is unspent or not.
|
||||
//
|
||||
// Tracking individual debits for unmined transactions
|
||||
// could be added later to simplify (and increase
|
||||
// performance of) determining some details that need
|
||||
// the previous outputs (e.g. determining a fee), but at
|
||||
// the moment that is not done (and a db lookup is used
|
||||
// for those cases instead). There is also a good
|
||||
// chance that all unmined transaction handling will
|
||||
// move entirely to the db rather than being handled in
|
||||
// memory for atomicity reasons, so the simplist
|
||||
// implementation is currently used.
|
||||
continue
|
||||
}
|
||||
|
||||
// If this output is relevant to us, we'll mark the it as spent
|
||||
// and remove its amount from the store.
|
||||
spender.index = uint32(i)
|
||||
amt, err := spendCredit(ns, credKey, &spender)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = putDebit(
|
||||
ns, &rec.Hash, uint32(i), amt, &block.Block, credKey,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := deleteRawUnspent(ns, unspentKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newMinedBalance -= amt
|
||||
}
|
||||
|
||||
// For each output of the record that is marked as a credit, if the
|
||||
// output is marked as a credit by the unconfirmed store, remove the
|
||||
// marker and mark the output as a credit in the db.
|
||||
//
|
||||
// Moved credits are added as unspents, even if there is another
|
||||
// unconfirmed transaction which spends them.
|
||||
cred := credit{
|
||||
outPoint: wire.OutPoint{Hash: rec.Hash},
|
||||
block: block.Block,
|
||||
spentBy: indexedIncidence{index: ^uint32(0)},
|
||||
}
|
||||
|
||||
it := makeUnminedCreditIterator(ns, &rec.Hash)
|
||||
for it.next() {
|
||||
// TODO: This should use the raw apis. The credit value (it.cv)
|
||||
// can be moved from unmined directly to the credits bucket.
|
||||
// The key needs a modification to include the block
|
||||
// height/hash.
|
||||
index, err := fetchRawUnminedCreditIndex(it.ck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount, change, err := fetchRawUnminedCreditAmountChange(it.cv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cred.outPoint.Index = index
|
||||
cred.amount = amount
|
||||
cred.change = change
|
||||
|
||||
if err := putUnspentCredit(ns, &cred); err != nil {
|
||||
return err
|
||||
}
|
||||
err = putUnspent(ns, &cred.outPoint, &block.Block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newMinedBalance += amount
|
||||
}
|
||||
if it.err != nil {
|
||||
return it.err
|
||||
}
|
||||
|
||||
// Update the balance if it has changed.
|
||||
if newMinedBalance != minedBalance {
|
||||
return putMinedBalance(ns, newMinedBalance)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteUnminedTx deletes an unmined transaction from the store.
|
||||
//
|
||||
// NOTE: This should only be used once the transaction has been mined.
|
||||
func (s *Store) deleteUnminedTx(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
|
||||
for i := range rec.MsgTx.TxOut {
|
||||
k := canonicalOutPoint(&rec.Hash, uint32(i))
|
||||
if err := deleteRawUnminedCredit(ns, k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return deleteRawUnmined(ns, rec.Hash[:])
|
||||
}
|
||||
|
||||
// InsertTx records a transaction as belonging to a wallet's transaction
|
||||
// history. If block is nil, the transaction is considered unspent, and the
|
||||
// transaction's index must be unset.
|
||||
func (s *Store) InsertTx(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) error {
|
||||
if block == nil {
|
||||
return s.insertMemPoolTx(ns, rec)
|
||||
}
|
||||
return s.insertMinedTx(ns, rec, block)
|
||||
}
|
||||
|
||||
// RemoveUnminedTx attempts to remove an unmined transaction from the
|
||||
// transaction store. This is to be used in the scenario that a transaction
|
||||
// that we attempt to rebroadcast, turns out to double spend one of our
|
||||
// existing inputs. This function we remove the conflicting transaction
|
||||
// identified by the tx record, and also recursively remove all transactions
|
||||
// that depend on it.
|
||||
func (s *Store) RemoveUnminedTx(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
|
||||
// As we already have a tx record, we can directly call the
|
||||
// removeConflict method. This will do the job of recursively removing
|
||||
// this unmined transaction, and any transactions that depend on it.
|
||||
return s.removeConflict(ns, rec)
|
||||
}
|
||||
|
||||
// insertMinedTx inserts a new transaction record for a mined transaction into
|
||||
// the database under the confirmed bucket. It guarantees that, if the
|
||||
// tranasction was previously unconfirmed, then it will take care of cleaning up
|
||||
// the unconfirmed state. All other unconfirmed double spend attempts will be
|
||||
// removed as well.
|
||||
func (s *Store) insertMinedTx(ns walletdb.ReadWriteBucket, rec *TxRecord,
|
||||
block *BlockMeta) error {
|
||||
|
||||
// If a transaction record for this hash and block already exists, we
|
||||
// can exit early.
|
||||
if _, v := existsTxRecord(ns, &rec.Hash, &block.Block); v != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If a block record does not yet exist for any transactions from this
|
||||
// block, insert a block record first. Otherwise, update it by adding
|
||||
// the transaction hash to the set of transactions from this block.
|
||||
var err error
|
||||
blockKey, blockValue := existsBlockRecord(ns, block.Height)
|
||||
if blockValue == nil {
|
||||
err = putBlockRecord(ns, block, &rec.Hash)
|
||||
} else {
|
||||
blockValue, err = appendRawBlockRecord(blockValue, &rec.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = putRawBlockRecord(ns, blockKey, blockValue)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := putTxRecord(ns, rec, &block.Block); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine if this transaction has affected our balance, and if so,
|
||||
// update it.
|
||||
if err := s.updateMinedBalance(ns, rec, block); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If this transaction previously existed within the store as unmined,
|
||||
// we'll need to remove it from the unmined bucket.
|
||||
if v := existsRawUnmined(ns, rec.Hash[:]); v != nil {
|
||||
log.Infof("Marking unconfirmed transaction %v mined in block %d",
|
||||
&rec.Hash, block.Height)
|
||||
|
||||
if err := s.deleteUnminedTx(ns, rec); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// As there may be unconfirmed transactions that are invalidated by this
|
||||
// transaction (either being duplicates, or double spends), remove them
|
||||
// from the unconfirmed set. This also handles removing unconfirmed
|
||||
// transaction spend chains if any other unconfirmed transactions spend
|
||||
// outputs of the removed double spend.
|
||||
return s.removeDoubleSpends(ns, rec)
|
||||
}
|
||||
|
||||
// AddCredit marks a transaction record as containing a transaction output
|
||||
// spendable by wallet. The output is added unspent, and is marked spent
|
||||
// when a new transaction spending the output is inserted into the store.
|
||||
//
|
||||
// TODO(jrick): This should not be necessary. Instead, pass the indexes
|
||||
// that are known to contain credits when a transaction or merkleblock is
|
||||
// inserted into the store.
|
||||
func (s *Store) AddCredit(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) error {
|
||||
if int(index) >= len(rec.MsgTx.TxOut) {
|
||||
str := "transaction output does not exist"
|
||||
return storeError(ErrInput, str, nil)
|
||||
}
|
||||
|
||||
isNew, err := s.addCredit(ns, rec, block, index, change)
|
||||
if err == nil && isNew && s.NotifyUnspent != nil {
|
||||
s.NotifyUnspent(&rec.Hash, index)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// addCredit is an AddCredit helper that runs in an update transaction. The
|
||||
// bool return specifies whether the unspent output is newly added (true) or a
|
||||
// duplicate (false).
|
||||
func (s *Store) addCredit(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) (bool, error) {
|
||||
if block == nil {
|
||||
// If the outpoint that we should mark as credit already exists
|
||||
// within the store, either as unconfirmed or confirmed, then we
|
||||
// have nothing left to do and can exit.
|
||||
k := canonicalOutPoint(&rec.Hash, index)
|
||||
if existsRawUnminedCredit(ns, k) != nil {
|
||||
return false, nil
|
||||
}
|
||||
if existsRawUnspent(ns, k) != nil {
|
||||
return false, nil
|
||||
}
|
||||
v := valueUnminedCredit(btcutil.Amount(rec.MsgTx.TxOut[index].Value), change)
|
||||
return true, putRawUnminedCredit(ns, k, v)
|
||||
}
|
||||
|
||||
k, v := existsCredit(ns, &rec.Hash, index, &block.Block)
|
||||
if v != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
txOutAmt := btcutil.Amount(rec.MsgTx.TxOut[index].Value)
|
||||
log.Debugf("Marking transaction %v output %d (%v) spendable",
|
||||
rec.Hash, index, txOutAmt)
|
||||
|
||||
cred := credit{
|
||||
outPoint: wire.OutPoint{
|
||||
Hash: rec.Hash,
|
||||
Index: index,
|
||||
},
|
||||
block: block.Block,
|
||||
amount: txOutAmt,
|
||||
change: change,
|
||||
spentBy: indexedIncidence{index: ^uint32(0)},
|
||||
}
|
||||
v = valueUnspentCredit(&cred)
|
||||
err := putRawCredit(ns, k, v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
minedBalance, err := fetchMinedBalance(ns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
err = putMinedBalance(ns, minedBalance+txOutAmt)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, putUnspent(ns, &cred.outPoint, &block.Block)
|
||||
}
|
||||
|
||||
// Rollback removes all blocks at height onwards, moving any transactions within
|
||||
// each block to the unconfirmed pool.
|
||||
func (s *Store) Rollback(ns walletdb.ReadWriteBucket, height int32) error {
|
||||
return s.rollback(ns, height)
|
||||
}
|
||||
|
||||
func (s *Store) rollback(ns walletdb.ReadWriteBucket, height int32) error {
|
||||
minedBalance, err := fetchMinedBalance(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Keep track of all credits that were removed from coinbase
|
||||
// transactions. After detaching all blocks, if any transaction record
|
||||
// exists in unmined that spends these outputs, remove them and their
|
||||
// spend chains.
|
||||
//
|
||||
// It is necessary to keep these in memory and fix the unmined
|
||||
// transactions later since blocks are removed in increasing order.
|
||||
var coinBaseCredits []wire.OutPoint
|
||||
var heightsToRemove []int32
|
||||
|
||||
it := makeReverseBlockIterator(ns)
|
||||
for it.prev() {
|
||||
b := &it.elem
|
||||
if it.elem.Height < height {
|
||||
break
|
||||
}
|
||||
|
||||
heightsToRemove = append(heightsToRemove, it.elem.Height)
|
||||
|
||||
log.Infof("Rolling back %d transactions from block %v height %d",
|
||||
len(b.transactions), b.Hash, b.Height)
|
||||
|
||||
for i := range b.transactions {
|
||||
txHash := &b.transactions[i]
|
||||
|
||||
recKey := keyTxRecord(txHash, &b.Block)
|
||||
recVal := existsRawTxRecord(ns, recKey)
|
||||
var rec TxRecord
|
||||
err = readRawTxRecord(txHash, recVal, &rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = deleteTxRecord(ns, txHash, &b.Block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle coinbase transactions specially since they are
|
||||
// not moved to the unconfirmed store. A coinbase cannot
|
||||
// contain any debits, but all credits should be removed
|
||||
// and the mined balance decremented.
|
||||
if blockchain.IsCoinBaseTx(&rec.MsgTx) {
|
||||
op := wire.OutPoint{Hash: rec.Hash}
|
||||
for i, output := range rec.MsgTx.TxOut {
|
||||
k, v := existsCredit(ns, &rec.Hash,
|
||||
uint32(i), &b.Block)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
op.Index = uint32(i)
|
||||
|
||||
coinBaseCredits = append(coinBaseCredits, op)
|
||||
|
||||
unspentKey, credKey := existsUnspent(ns, &op)
|
||||
if credKey != nil {
|
||||
minedBalance -= btcutil.Amount(output.Value)
|
||||
err = deleteRawUnspent(ns, unspentKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = deleteRawCredit(ns, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
err = putRawUnmined(ns, txHash[:], recVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For each debit recorded for this transaction, mark
|
||||
// the credit it spends as unspent (as long as it still
|
||||
// exists) and delete the debit. The previous output is
|
||||
// recorded in the unconfirmed store for every previous
|
||||
// output, not just debits.
|
||||
for i, input := range rec.MsgTx.TxIn {
|
||||
prevOut := &input.PreviousOutPoint
|
||||
prevOutKey := canonicalOutPoint(&prevOut.Hash,
|
||||
prevOut.Index)
|
||||
err = putRawUnminedInput(ns, prevOutKey, rec.Hash[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If this input is a debit, remove the debit
|
||||
// record and mark the credit that it spent as
|
||||
// unspent, incrementing the mined balance.
|
||||
debKey, credKey, err := existsDebit(ns,
|
||||
&rec.Hash, uint32(i), &b.Block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if debKey == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// unspendRawCredit does not error in case the
|
||||
// no credit exists for this key, but this
|
||||
// behavior is correct. Since blocks are
|
||||
// removed in increasing order, this credit
|
||||
// may have already been removed from a
|
||||
// previously removed transaction record in
|
||||
// this rollback.
|
||||
var amt btcutil.Amount
|
||||
amt, err = unspendRawCredit(ns, credKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = deleteRawDebit(ns, debKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the credit was previously removed in the
|
||||
// rollback, the credit amount is zero. Only
|
||||
// mark the previously spent credit as unspent
|
||||
// if it still exists.
|
||||
if amt == 0 {
|
||||
continue
|
||||
}
|
||||
unspentVal, err := fetchRawCreditUnspentValue(credKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
minedBalance += amt
|
||||
err = putRawUnspent(ns, prevOutKey, unspentVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// For each detached non-coinbase credit, move the
|
||||
// credit output to unmined. If the credit is marked
|
||||
// unspent, it is removed from the utxo set and the
|
||||
// mined balance is decremented.
|
||||
//
|
||||
// TODO: use a credit iterator
|
||||
for i, output := range rec.MsgTx.TxOut {
|
||||
k, v := existsCredit(ns, &rec.Hash, uint32(i),
|
||||
&b.Block)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
amt, change, err := fetchRawCreditAmountChange(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outPointKey := canonicalOutPoint(&rec.Hash, uint32(i))
|
||||
unminedCredVal := valueUnminedCredit(amt, change)
|
||||
err = putRawUnminedCredit(ns, outPointKey, unminedCredVal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = deleteRawCredit(ns, k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
credKey := existsRawUnspent(ns, outPointKey)
|
||||
if credKey != nil {
|
||||
minedBalance -= btcutil.Amount(output.Value)
|
||||
err = deleteRawUnspent(ns, outPointKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reposition cursor before deleting this k/v pair and advancing to the
|
||||
// previous.
|
||||
it.reposition(it.elem.Height)
|
||||
|
||||
// Avoid cursor deletion until bolt issue #620 is resolved.
|
||||
// err = it.delete()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
}
|
||||
if it.err != nil {
|
||||
return it.err
|
||||
}
|
||||
|
||||
// Delete the block records outside of the iteration since cursor deletion
|
||||
// is broken.
|
||||
for _, h := range heightsToRemove {
|
||||
err = deleteBlockRecord(ns, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, op := range coinBaseCredits {
|
||||
opKey := canonicalOutPoint(&op.Hash, op.Index)
|
||||
unminedSpendTxHashKeys := fetchUnminedInputSpendTxHashes(ns, opKey)
|
||||
for _, unminedSpendTxHashKey := range unminedSpendTxHashKeys {
|
||||
unminedVal := existsRawUnmined(ns, unminedSpendTxHashKey[:])
|
||||
|
||||
// If the spending transaction spends multiple outputs
|
||||
// from the same transaction, we'll find duplicate
|
||||
// entries within the store, so it's possible we're
|
||||
// unable to find it if the conflicts have already been
|
||||
// removed in a previous iteration.
|
||||
if unminedVal == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var unminedRec TxRecord
|
||||
unminedRec.Hash = unminedSpendTxHashKey
|
||||
err = readRawTxRecord(&unminedRec.Hash, unminedVal, &unminedRec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Transaction %v spends a removed coinbase "+
|
||||
"output -- removing as well", unminedRec.Hash)
|
||||
err = s.removeConflict(ns, &unminedRec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return putMinedBalance(ns, minedBalance)
|
||||
}
|
||||
|
||||
// UnspentOutputs returns all unspent received transaction outputs.
|
||||
// The order is undefined.
|
||||
func (s *Store) UnspentOutputs(ns walletdb.ReadBucket) ([]Credit, error) {
|
||||
var unspent []Credit
|
||||
|
||||
var op wire.OutPoint
|
||||
var block Block
|
||||
err := ns.NestedReadBucket(bucketUnspent).ForEach(func(k, v []byte) error {
|
||||
err := readCanonicalOutPoint(k, &op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existsRawUnminedInput(ns, k) != nil {
|
||||
// Output is spent by an unmined transaction.
|
||||
// Skip this k/v pair.
|
||||
return nil
|
||||
}
|
||||
err = readUnspentBlock(v, &block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blockTime, err := fetchBlockTime(ns, block.Height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(jrick): reading the entire transaction should
|
||||
// be avoidable. Creating the credit only requires the
|
||||
// output amount and pkScript.
|
||||
rec, err := fetchTxRecord(ns, &op.Hash, &block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txOut := rec.MsgTx.TxOut[op.Index]
|
||||
cred := Credit{
|
||||
OutPoint: op,
|
||||
BlockMeta: BlockMeta{
|
||||
Block: block,
|
||||
Time: blockTime,
|
||||
},
|
||||
Amount: btcutil.Amount(txOut.Value),
|
||||
PkScript: txOut.PkScript,
|
||||
Received: rec.Received,
|
||||
FromCoinBase: blockchain.IsCoinBaseTx(&rec.MsgTx),
|
||||
}
|
||||
unspent = append(unspent, cred)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if _, ok := err.(Error); ok {
|
||||
return nil, err
|
||||
}
|
||||
str := "failed iterating unspent bucket"
|
||||
return nil, storeError(ErrDatabase, str, err)
|
||||
}
|
||||
|
||||
err = ns.NestedReadBucket(bucketUnminedCredits).ForEach(func(k, v []byte) error {
|
||||
if existsRawUnminedInput(ns, k) != nil {
|
||||
// Output is spent by an unmined transaction.
|
||||
// Skip to next unmined credit.
|
||||
return nil
|
||||
}
|
||||
|
||||
err := readCanonicalOutPoint(k, &op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(jrick): Reading/parsing the entire transaction record
|
||||
// just for the output amount and script can be avoided.
|
||||
recVal := existsRawUnmined(ns, op.Hash[:])
|
||||
var rec TxRecord
|
||||
err = readRawTxRecord(&op.Hash, recVal, &rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txOut := rec.MsgTx.TxOut[op.Index]
|
||||
cred := Credit{
|
||||
OutPoint: op,
|
||||
BlockMeta: BlockMeta{
|
||||
Block: Block{Height: -1},
|
||||
},
|
||||
Amount: btcutil.Amount(txOut.Value),
|
||||
PkScript: txOut.PkScript,
|
||||
Received: rec.Received,
|
||||
FromCoinBase: blockchain.IsCoinBaseTx(&rec.MsgTx),
|
||||
}
|
||||
unspent = append(unspent, cred)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if _, ok := err.(Error); ok {
|
||||
return nil, err
|
||||
}
|
||||
str := "failed iterating unmined credits bucket"
|
||||
return nil, storeError(ErrDatabase, str, err)
|
||||
}
|
||||
|
||||
return unspent, nil
|
||||
}
|
||||
|
||||
// Balance returns the spendable wallet balance (total value of all unspent
|
||||
// transaction outputs) given a minimum of minConf confirmations, calculated
|
||||
// at a current chain height of curHeight. Coinbase outputs are only included
|
||||
// in the balance if maturity has been reached.
|
||||
//
|
||||
// Balance may return unexpected results if syncHeight is lower than the block
|
||||
// height of the most recent mined transaction in the store.
|
||||
func (s *Store) Balance(ns walletdb.ReadBucket, minConf int32, syncHeight int32) (btcutil.Amount, error) {
|
||||
bal, err := fetchMinedBalance(ns)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Subtract the balance for each credit that is spent by an unmined
|
||||
// transaction.
|
||||
var op wire.OutPoint
|
||||
var block Block
|
||||
err = ns.NestedReadBucket(bucketUnspent).ForEach(func(k, v []byte) error {
|
||||
err := readCanonicalOutPoint(k, &op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = readUnspentBlock(v, &block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existsRawUnminedInput(ns, k) != nil {
|
||||
_, v := existsCredit(ns, &op.Hash, op.Index, &block)
|
||||
amt, err := fetchRawCreditAmount(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bal -= amt
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if _, ok := err.(Error); ok {
|
||||
return 0, err
|
||||
}
|
||||
str := "failed iterating unspent outputs"
|
||||
return 0, storeError(ErrDatabase, str, err)
|
||||
}
|
||||
|
||||
// Decrement the balance for any unspent credit with less than
|
||||
// minConf confirmations and any (unspent) immature coinbase credit.
|
||||
coinbaseMaturity := int32(s.chainParams.CoinbaseMaturity)
|
||||
stopConf := minConf
|
||||
if coinbaseMaturity > stopConf {
|
||||
stopConf = coinbaseMaturity
|
||||
}
|
||||
lastHeight := syncHeight - stopConf
|
||||
blockIt := makeReadReverseBlockIterator(ns)
|
||||
for blockIt.prev() {
|
||||
block := &blockIt.elem
|
||||
|
||||
if block.Height < lastHeight {
|
||||
break
|
||||
}
|
||||
|
||||
for i := range block.transactions {
|
||||
txHash := &block.transactions[i]
|
||||
rec, err := fetchTxRecord(ns, txHash, &block.Block)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
numOuts := uint32(len(rec.MsgTx.TxOut))
|
||||
for i := uint32(0); i < numOuts; i++ {
|
||||
// Avoid double decrementing the credit amount
|
||||
// if it was already removed for being spent by
|
||||
// an unmined tx.
|
||||
opKey := canonicalOutPoint(txHash, i)
|
||||
if existsRawUnminedInput(ns, opKey) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, v := existsCredit(ns, txHash, i, &block.Block)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
amt, spent, err := fetchRawCreditAmountSpent(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if spent {
|
||||
continue
|
||||
}
|
||||
confs := syncHeight - block.Height + 1
|
||||
if confs < minConf || (blockchain.IsCoinBaseTx(&rec.MsgTx) &&
|
||||
confs < coinbaseMaturity) {
|
||||
bal -= amt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if blockIt.err != nil {
|
||||
return 0, blockIt.err
|
||||
}
|
||||
|
||||
// If unmined outputs are included, increment the balance for each
|
||||
// output that is unspent.
|
||||
if minConf == 0 {
|
||||
err = ns.NestedReadBucket(bucketUnminedCredits).ForEach(func(k, v []byte) error {
|
||||
if existsRawUnminedInput(ns, k) != nil {
|
||||
// Output is spent by an unmined transaction.
|
||||
// Skip to next unmined credit.
|
||||
return nil
|
||||
}
|
||||
|
||||
amount, err := fetchRawUnminedCreditAmount(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bal += amount
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
if _, ok := err.(Error); ok {
|
||||
return 0, err
|
||||
}
|
||||
str := "failed to iterate over unmined credits bucket"
|
||||
return 0, storeError(ErrDatabase, str, err)
|
||||
}
|
||||
}
|
||||
|
||||
return bal, nil
|
||||
}
|
||||
222
vendor/github.com/btcsuite/btcwallet/wtxmgr/unconfirmed.go
generated
vendored
Normal file
222
vendor/github.com/btcsuite/btcwallet/wtxmgr/unconfirmed.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2016 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wtxmgr
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
// insertMemPoolTx inserts the unmined transaction record. It also marks
|
||||
// previous outputs referenced by the inputs as spent.
|
||||
func (s *Store) insertMemPoolTx(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
|
||||
// Check whether the transaction has already been added to the store,
|
||||
// regardless of whether is has confirmed or not. This ensures that we
|
||||
// don't add it to the unconfirmed bucket again if it has already
|
||||
// confirmed.
|
||||
//
|
||||
// TODO: compare serialized txs to ensure this isn't a hash
|
||||
// collision?
|
||||
if txDetails, _ := s.TxDetails(ns, &rec.Hash); txDetails != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Since transaction records within the store are keyed by their
|
||||
// transaction _and_ block confirmation, we'll iterate through the
|
||||
// transaction's outputs to determine if we've already seen them to
|
||||
// prevent from adding this transaction to the unconfirmed bucket.
|
||||
for i := range rec.MsgTx.TxOut {
|
||||
k := canonicalOutPoint(&rec.Hash, uint32(i))
|
||||
if existsRawUnspent(ns, k) != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Inserting unconfirmed transaction %v", rec.Hash)
|
||||
v, err := valueTxRecord(rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = putRawUnmined(ns, rec.Hash[:], v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, input := range rec.MsgTx.TxIn {
|
||||
prevOut := &input.PreviousOutPoint
|
||||
k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
|
||||
err = putRawUnminedInput(ns, k, rec.Hash[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: increment credit amount for each credit (but those are unknown
|
||||
// here currently).
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeDoubleSpends checks for any unmined transactions which would introduce
|
||||
// a double spend if tx was added to the store (either as a confirmed or unmined
|
||||
// transaction). Each conflicting transaction and all transactions which spend
|
||||
// it are recursively removed.
|
||||
func (s *Store) removeDoubleSpends(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
|
||||
for _, input := range rec.MsgTx.TxIn {
|
||||
prevOut := &input.PreviousOutPoint
|
||||
prevOutKey := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
|
||||
|
||||
doubleSpendHashes := fetchUnminedInputSpendTxHashes(ns, prevOutKey)
|
||||
for _, doubleSpendHash := range doubleSpendHashes {
|
||||
// We'll make sure not to remove ourselves.
|
||||
if rec.Hash == doubleSpendHash {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the spending transaction spends multiple outputs
|
||||
// from the same transaction, we'll find duplicate
|
||||
// entries within the store, so it's possible we're
|
||||
// unable to find it if the conflicts have already been
|
||||
// removed in a previous iteration.
|
||||
doubleSpendVal := existsRawUnmined(
|
||||
ns, doubleSpendHash[:],
|
||||
)
|
||||
if doubleSpendVal == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var doubleSpend TxRecord
|
||||
doubleSpend.Hash = doubleSpendHash
|
||||
err := readRawTxRecord(
|
||||
&doubleSpend.Hash, doubleSpendVal, &doubleSpend,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Removing double spending transaction %v",
|
||||
doubleSpend.Hash)
|
||||
|
||||
if err := s.removeConflict(ns, &doubleSpend); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeConflict removes an unmined transaction record and all spend chains
|
||||
// deriving from it from the store. This is designed to remove transactions
|
||||
// that would otherwise result in double spend conflicts if left in the store,
|
||||
// and to remove transactions that spend coinbase transactions on reorgs.
|
||||
func (s *Store) removeConflict(ns walletdb.ReadWriteBucket, rec *TxRecord) error {
|
||||
// For each potential credit for this record, each spender (if any) must
|
||||
// be recursively removed as well. Once the spenders are removed, the
|
||||
// credit is deleted.
|
||||
for i := range rec.MsgTx.TxOut {
|
||||
k := canonicalOutPoint(&rec.Hash, uint32(i))
|
||||
spenderHashes := fetchUnminedInputSpendTxHashes(ns, k)
|
||||
for _, spenderHash := range spenderHashes {
|
||||
// If the spending transaction spends multiple outputs
|
||||
// from the same transaction, we'll find duplicate
|
||||
// entries within the store, so it's possible we're
|
||||
// unable to find it if the conflicts have already been
|
||||
// removed in a previous iteration.
|
||||
spenderVal := existsRawUnmined(ns, spenderHash[:])
|
||||
if spenderVal == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var spender TxRecord
|
||||
spender.Hash = spenderHash
|
||||
err := readRawTxRecord(&spender.Hash, spenderVal, &spender)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Transaction %v is part of a removed conflict "+
|
||||
"chain -- removing as well", spender.Hash)
|
||||
if err := s.removeConflict(ns, &spender); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := deleteRawUnminedCredit(ns, k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If this tx spends any previous credits (either mined or unmined), set
|
||||
// each unspent. Mined transactions are only marked spent by having the
|
||||
// output in the unmined inputs bucket.
|
||||
for _, input := range rec.MsgTx.TxIn {
|
||||
prevOut := &input.PreviousOutPoint
|
||||
k := canonicalOutPoint(&prevOut.Hash, prevOut.Index)
|
||||
err := deleteRawUnminedInput(ns, k, rec.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return deleteRawUnmined(ns, rec.Hash[:])
|
||||
}
|
||||
|
||||
// UnminedTxs returns the underlying transactions for all unmined transactions
|
||||
// which are not known to have been mined in a block. Transactions are
|
||||
// guaranteed to be sorted by their dependency order.
|
||||
func (s *Store) UnminedTxs(ns walletdb.ReadBucket) ([]*wire.MsgTx, error) {
|
||||
recSet, err := s.unminedTxRecords(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txSet := make(map[chainhash.Hash]*wire.MsgTx, len(recSet))
|
||||
for txHash, txRec := range recSet {
|
||||
txSet[txHash] = &txRec.MsgTx
|
||||
}
|
||||
|
||||
return DependencySort(txSet), nil
|
||||
}
|
||||
|
||||
func (s *Store) unminedTxRecords(ns walletdb.ReadBucket) (map[chainhash.Hash]*TxRecord, error) {
|
||||
unmined := make(map[chainhash.Hash]*TxRecord)
|
||||
err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error {
|
||||
var txHash chainhash.Hash
|
||||
err := readRawUnminedHash(k, &txHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rec := new(TxRecord)
|
||||
err = readRawTxRecord(&txHash, v, rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unmined[rec.Hash] = rec
|
||||
return nil
|
||||
})
|
||||
return unmined, err
|
||||
}
|
||||
|
||||
// UnminedTxHashes returns the hashes of all transactions not known to have been
|
||||
// mined in a block.
|
||||
func (s *Store) UnminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) {
|
||||
return s.unminedTxHashes(ns)
|
||||
}
|
||||
|
||||
func (s *Store) unminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, error) {
|
||||
var hashes []*chainhash.Hash
|
||||
err := ns.NestedReadBucket(bucketUnmined).ForEach(func(k, v []byte) error {
|
||||
hash := new(chainhash.Hash)
|
||||
err := readRawUnminedHash(k, hash)
|
||||
if err == nil {
|
||||
hashes = append(hashes, hash)
|
||||
}
|
||||
return err
|
||||
})
|
||||
return hashes, err
|
||||
}
|
||||
Reference in New Issue
Block a user