b9fd527d33
This commit is the result of several big changes being made to the wallet. In particular, the "handshake" (initial sync to the chain server) was quite racy and required proper synchronization. To make fixing this race easier, several other changes were made to the internal wallet data structures and much of the RPC server ended up being rewritten. First, all account support has been removed. The previous Account struct has been replaced with a Wallet structure, which includes a keystore for saving keys, and a txstore for storing relevant transactions. This decision has been made since it is the opinion of myself and other developers that bitcoind accounts are fundamentally broken (as accounts implemented by bitcoind support both arbitrary address groupings as well as moving balances between accounts -- these are fundamentally incompatible features), and since a BIP0032 keystore is soon planned to be implemented (at which point, "accounts" can return as HD extended keys). With the keystore handling the grouping of related keys, there is no reason have many different Account structs, and the AccountManager has been removed as well. All RPC handlers that take an account option will only work with "" (the default account) or "*" if the RPC allows specifying all accounts. Second, much of the RPC server has been cleaned up. The global variables for the RPC server and chain server client have been moved to part of the rpcServer struct, and the handlers for each RPC method that are looked up change depending on which components have been set. Passthrough requests are also no longer handled specially, but when the chain server is set, a handler to perform the passthrough will be returned if the method is not otherwise a wallet RPC. The notification system for websocket clients has also been rewritten so wallet components can send notifications through channels, rather than requiring direct access to the RPC server itself, or worse still, sending directly to a websocket client's send channel. In the future, this will enable proper registration of notifications, rather than unsolicited broadcasts to every connected websocket client (see issue #84). Finally, and the main reason why much of this cleanup was necessary, the races during intial sync with the chain server have been fixed. Previously, when the 'Handshake' was run, a rescan would occur which would perform modifications to Account data structures as notifications were received. Synchronization was provided with a single binary semaphore which serialized all access to wallet and account data. However, the Handshake itself was not able to run with this lock (or else notifications would block), and many data races would occur as both notifications were being handled. If GOMAXPROCS was ever increased beyond 1, btcwallet would always immediately crash due to invalid addresses caused by the data races on startup. To fix this, the single lock for all wallet access has been replaced with mutexes for both the keystore and txstore. Handling of btcd notifications and client requests may now occur simultaneously. GOMAXPROCS has also been set to the number of logical CPUs at the beginning of main, since with the data races fixed, there's no reason to prevent the extra parallelism gained by increasing it. Closes #78. Closes #101. Closes #110.
1514 lines
43 KiB
Go
1514 lines
43 KiB
Go
/*
|
|
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
package txstore
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/conformal/btcchain"
|
|
"github.com/conformal/btcnet"
|
|
"github.com/conformal/btcscript"
|
|
"github.com/conformal/btcutil"
|
|
"github.com/conformal/btcwire"
|
|
)
|
|
|
|
var (
|
|
// ErrDuplicateInsert describes the error where an insert was ignored
|
|
// for being a duplicate.
|
|
ErrDuplicateInsert = errors.New("duplicate insert")
|
|
|
|
// ErrInconsistentStore describes an error where the transaction store
|
|
// is in an inconsistent state. This error is unrecoverable, and the
|
|
// transaction store should be regenerated by a rescan.
|
|
ErrInconsistentStore = errors.New("inconsistant transaction store")
|
|
|
|
// ErrUnsupportedVersion represents an error where a serialized
|
|
// object is marked with a version that is no longer supported
|
|
// during deserialization.
|
|
ErrUnsupportedVersion = errors.New("version no longer supported")
|
|
)
|
|
|
|
// MissingValueError is a catch-all error interface for any error due to a
|
|
// missing value in the transaction store.
|
|
type MissingValueError interface {
|
|
Error() string
|
|
MissingValueError() error
|
|
}
|
|
|
|
// MissingBlockError describes an error where a block could not be found in
|
|
// the transaction store. The value is the height of the missing block.
|
|
type MissingBlockError int32
|
|
|
|
// Error implements the error interface.
|
|
func (e MissingBlockError) Error() string {
|
|
return fmt.Sprintf("missing record for block %d", e)
|
|
}
|
|
|
|
// MissingValueError implements the MissingValueError interface.
|
|
func (e MissingBlockError) MissingValueError() error {
|
|
return e
|
|
}
|
|
|
|
// MissingBlockTxError describes an error where a mined transaction could
|
|
// not be found in the transaction store. The value is the lookup key for
|
|
// the mined transaction.
|
|
type MissingBlockTxError BlockTxKey
|
|
|
|
// Error implements the error interface.
|
|
func (e MissingBlockTxError) Error() string {
|
|
return fmt.Sprintf("missing record for transaction at block %d index "+
|
|
"%d", e.BlockHeight, e.BlockIndex)
|
|
}
|
|
|
|
// MissingValueError implements the MissingValueError interface.
|
|
func (e MissingBlockTxError) MissingValueError() error {
|
|
return e
|
|
}
|
|
|
|
// MissingDebitsError describes an error where a mined transaction does not
|
|
// contain any debiting records. The value is the lookup key for the mined
|
|
// transaction.
|
|
type MissingDebitsError BlockTxKey
|
|
|
|
// Error implements the error interface.
|
|
func (e MissingDebitsError) Error() string {
|
|
return fmt.Sprintf("missing record for debits at block %d index %d",
|
|
e.BlockHeight, e.BlockIndex)
|
|
}
|
|
|
|
// MissingValueError implements the MissingValueError interface.
|
|
func (e MissingDebitsError) MissingValueError() error {
|
|
return e
|
|
}
|
|
|
|
// MissingCreditError describes an error where a mined transaction does not
|
|
// contain a credit record at for some output. The value is the lookup key
|
|
// for the mined transaction's output.
|
|
type MissingCreditError BlockOutputKey
|
|
|
|
// Error implements the error interface.
|
|
func (e MissingCreditError) Error() string {
|
|
return fmt.Sprintf("missing record for received transaction output at "+
|
|
"block %d index %d output %d", e.BlockHeight, e.BlockIndex,
|
|
e.OutputIndex)
|
|
}
|
|
|
|
// MissingValueError implements the MissingValueError interface.
|
|
func (e MissingCreditError) MissingValueError() error {
|
|
return e
|
|
}
|
|
|
|
// TxRecord is the record type for all transactions in the store. If the
|
|
// transaction is mined, BlockTxKey will be the lookup key for the transaction.
|
|
// Otherwise, the embedded BlockHeight will be -1.
|
|
type TxRecord struct {
|
|
BlockTxKey
|
|
*txRecord
|
|
s *Store
|
|
}
|
|
|
|
// Block holds details about a block that contains wallet transactions.
|
|
type Block struct {
|
|
Hash btcwire.ShaHash
|
|
Time time.Time
|
|
Height int32
|
|
}
|
|
|
|
// Debits is the type representing any TxRecord which debits from previous
|
|
// wallet transaction outputs.
|
|
type Debits struct {
|
|
*TxRecord
|
|
}
|
|
|
|
// 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 {
|
|
*TxRecord
|
|
OutputIndex uint32
|
|
}
|
|
|
|
// blockAmounts holds the immediately spendable and reward (coinbase) amounts
|
|
// as a result of all transactions in a block.
|
|
type blockAmounts struct {
|
|
Spendable btcutil.Amount
|
|
Reward btcutil.Amount
|
|
}
|
|
|
|
// Store implements a transaction store for storing and managing wallet
|
|
// transactions.
|
|
type Store struct {
|
|
// TODO: Use atomic operations for dirty so the reader lock
|
|
// doesn't need to be grabbed.
|
|
dirty bool
|
|
path string
|
|
dir string
|
|
file string
|
|
|
|
mtx sync.RWMutex
|
|
|
|
// blocks holds wallet transaction records for each block they appear
|
|
// in. This is sorted by block height in increasing order. A separate
|
|
// map is included to lookup indexes for blocks at some height.
|
|
blocks []*blockTxCollection
|
|
blockIndexes map[int32]uint32
|
|
|
|
unspent map[btcwire.OutPoint]BlockTxKey
|
|
|
|
// unconfirmed holds a collection of wallet transactions that have not
|
|
// been mined into a block yet.
|
|
unconfirmed unconfirmedStore
|
|
|
|
// Channels to notify callers of changes to the transaction store.
|
|
// These are only created when a caller calls the appropiate
|
|
// registration method.
|
|
newCredit chan Credit
|
|
newDebits chan Debits
|
|
minedCredit chan Credit
|
|
minedDebits chan Debits
|
|
notificationLock sync.Locker
|
|
}
|
|
|
|
// blockTxCollection holds a collection of wallet transactions from exactly one
|
|
// block.
|
|
type blockTxCollection struct {
|
|
// Block holds the hash, time, and height of the block.
|
|
Block
|
|
|
|
// amountDeltas is the net increase or decrease of BTC controllable by
|
|
// wallet addresses due to transactions in this block. This value only
|
|
// tracks the total amount, not amounts for individual addresses (which
|
|
// this txstore implementation is not aware of).
|
|
amountDeltas blockAmounts
|
|
|
|
// txs holds all transaction records for a block, sorted by block index.
|
|
// A separate map is included to lookup indexes for txs at some block
|
|
// index.
|
|
txs []*txRecord
|
|
txIndexes map[int]uint32
|
|
}
|
|
|
|
// unconfirmedStore stores all unconfirmed transactions managed by the Store.
|
|
type unconfirmedStore struct {
|
|
// txs contains all unconfirmed transactions, mapping from their
|
|
// transaction hash to the records.
|
|
txs map[btcwire.ShaHash]*txRecord
|
|
|
|
// spentBlockOutPoints maps from spent outputs from mined transaction
|
|
// to the unconfirmed transaction which spends it. An additional
|
|
// map is included to lookup the output key by its outpoint.
|
|
spentBlockOutPoints map[BlockOutputKey]*txRecord
|
|
spentBlockOutPointKeys map[btcwire.OutPoint]BlockOutputKey
|
|
|
|
// spentUnconfirmed maps from an unconfirmed outpoint to the unconfirmed
|
|
// transaction which spends it.
|
|
spentUnconfirmed map[btcwire.OutPoint]*txRecord
|
|
|
|
// previousOutpoints maps all previous outputs to the transaction record
|
|
// of the unconfirmed transaction which spends it. This is primarly
|
|
// designed to assist with double spend detection without iterating
|
|
// through each value of the txs map.
|
|
previousOutpoints map[btcwire.OutPoint]*txRecord
|
|
}
|
|
|
|
// BlockTxKey is a lookup key for a single mined transaction in the store.
|
|
type BlockTxKey struct {
|
|
BlockIndex int
|
|
BlockHeight int32
|
|
}
|
|
|
|
// BlockOutputKey is a lookup key for a transaction output from a block in the
|
|
// store.
|
|
type BlockOutputKey struct {
|
|
BlockTxKey
|
|
OutputIndex uint32
|
|
}
|
|
|
|
// txRecord holds all credits and debits created by a transaction's inputs and
|
|
// outputs.
|
|
type txRecord struct {
|
|
// tx is the transaction that all records in this structure reference.
|
|
tx *btcutil.Tx
|
|
|
|
// debit records the debits this transaction creates, or nil if the
|
|
// transaction does not spend any previous credits.
|
|
debits *debits
|
|
|
|
// credits holds all records for received transaction outputs from
|
|
// this transaction.
|
|
credits []*credit
|
|
|
|
received time.Time
|
|
}
|
|
|
|
// debits records the debits a transaction record makes from previous wallet
|
|
// transaction credits.
|
|
type debits struct {
|
|
amount btcutil.Amount
|
|
spends []BlockOutputKey
|
|
}
|
|
|
|
// credit describes a transaction output which was or is spendable by wallet.
|
|
type credit struct {
|
|
change bool
|
|
spentBy *BlockTxKey // nil if unspent
|
|
}
|
|
|
|
// New allocates and initializes a new transaction store.
|
|
func New(dir string) *Store {
|
|
return &Store{
|
|
path: filepath.Join(dir, filename),
|
|
dir: dir,
|
|
file: filename,
|
|
blockIndexes: map[int32]uint32{},
|
|
unspent: map[btcwire.OutPoint]BlockTxKey{},
|
|
unconfirmed: unconfirmedStore{
|
|
txs: map[btcwire.ShaHash]*txRecord{},
|
|
spentBlockOutPoints: map[BlockOutputKey]*txRecord{},
|
|
spentBlockOutPointKeys: map[btcwire.OutPoint]BlockOutputKey{},
|
|
spentUnconfirmed: map[btcwire.OutPoint]*txRecord{},
|
|
previousOutpoints: map[btcwire.OutPoint]*txRecord{},
|
|
},
|
|
notificationLock: new(sync.Mutex),
|
|
}
|
|
}
|
|
|
|
func (s *Store) lookupBlock(height int32) (*blockTxCollection, error) {
|
|
if i, ok := s.blockIndexes[height]; ok {
|
|
return s.blocks[i], nil
|
|
}
|
|
return nil, MissingBlockError(height)
|
|
}
|
|
|
|
func (s *Store) lookupBlockTx(key BlockTxKey) (*txRecord, error) {
|
|
coll, err := s.lookupBlock(key.BlockHeight)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if i, ok := coll.txIndexes[key.BlockIndex]; ok {
|
|
return coll.txs[i], nil
|
|
}
|
|
return nil, MissingBlockTxError{key.BlockIndex, key.BlockHeight}
|
|
}
|
|
|
|
func (s *Store) lookupBlockDebits(key BlockTxKey) (*debits, error) {
|
|
r, err := s.lookupBlockTx(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if r.debits == nil {
|
|
return nil, MissingDebitsError(key)
|
|
}
|
|
return r.debits, nil
|
|
}
|
|
|
|
func (r *txRecord) lookupBlockCredit(key BlockOutputKey) (*credit, error) {
|
|
switch {
|
|
case len(r.credits) <= int(key.OutputIndex):
|
|
fallthrough
|
|
case r.credits[key.OutputIndex] == nil:
|
|
return nil, MissingCreditError(key)
|
|
}
|
|
return r.credits[key.OutputIndex], nil
|
|
}
|
|
|
|
func (s *Store) lookupBlockCredit(key BlockOutputKey) (*credit, error) {
|
|
txRecord, err := s.lookupBlockTx(key.BlockTxKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return txRecord.lookupBlockCredit(key)
|
|
}
|
|
|
|
func (s *Store) blockCollectionForInserts(block *Block) *blockTxCollection {
|
|
b, err := s.lookupBlock(block.Height)
|
|
switch e := err.(type) {
|
|
case MissingBlockError:
|
|
b = &blockTxCollection{
|
|
Block: *block,
|
|
txIndexes: map[int]uint32{},
|
|
}
|
|
|
|
// If this new block cannot be appended to the end of the blocks
|
|
// slice (which would disobey ordering blocks by their height),
|
|
// reslice and update the store's map of block heights to block
|
|
// slice indexes.
|
|
if len(s.blocks) > 0 && s.blocks[len(s.blocks)-1].Height > block.Height {
|
|
i := uint32(len(s.blocks))
|
|
for i != 0 && s.blocks[i-1].Height >= block.Height {
|
|
i--
|
|
}
|
|
detached := s.blocks[i:]
|
|
s.blocks = append(s.blocks[:i], b)
|
|
s.blockIndexes[b.Height] = i
|
|
for i, b := range detached {
|
|
newIndex := uint32(i + len(s.blocks))
|
|
s.blockIndexes[b.Height] = newIndex
|
|
}
|
|
s.blocks = append(s.blocks, detached...)
|
|
|
|
} else {
|
|
s.blockIndexes[int32(e)] = uint32(len(s.blocks))
|
|
s.blocks = append(s.blocks, b)
|
|
}
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (c *blockTxCollection) lookupTxRecord(blockIndex int) (*txRecord, uint32, error) {
|
|
if i, ok := c.txIndexes[blockIndex]; ok {
|
|
return c.txs[i], i, nil
|
|
}
|
|
return nil, 0, MissingBlockTxError{blockIndex, c.Block.Height}
|
|
}
|
|
|
|
func (c *blockTxCollection) txRecordForInserts(tx *btcutil.Tx) *txRecord {
|
|
if i, ok := c.txIndexes[tx.Index()]; ok {
|
|
return c.txs[i]
|
|
}
|
|
|
|
log.Infof("Inserting transaction %v from block %d", tx.Sha(), c.Height)
|
|
record := &txRecord{tx: tx}
|
|
|
|
// If this new transaction record cannot be appended to the end of the
|
|
// txs slice (which would disobey ordering transactions by their block
|
|
// index), reslice and update the block's map of block indexes to txs
|
|
// slice indexes.
|
|
if len(c.txs) > 0 && c.txs[len(c.txs)-1].Tx().Index() > tx.Index() {
|
|
i := uint32(len(c.txs))
|
|
for i != 0 && c.txs[i-1].Tx().Index() >= tx.Index() {
|
|
i--
|
|
}
|
|
detached := c.txs[i:]
|
|
c.txs = append(c.txs[:i], record)
|
|
c.txIndexes[tx.Index()] = i
|
|
for i, r := range detached {
|
|
newIndex := uint32(i + len(c.txs))
|
|
c.txIndexes[r.Tx().Index()] = newIndex
|
|
}
|
|
c.txs = append(c.txs, detached...)
|
|
} else {
|
|
c.txIndexes[tx.Index()] = uint32(len(c.txs))
|
|
c.txs = append(c.txs, record)
|
|
}
|
|
return record
|
|
}
|
|
|
|
func (s *Store) blockTxRecordForInserts(tx *btcutil.Tx, block *Block) *txRecord {
|
|
return s.blockCollectionForInserts(block).txRecordForInserts(tx)
|
|
}
|
|
|
|
func (u *unconfirmedStore) txRecordForInserts(tx *btcutil.Tx) *txRecord {
|
|
r, ok := u.txs[*tx.Sha()]
|
|
if !ok {
|
|
log.Infof("Inserting unconfirmed transaction %v", tx.Sha())
|
|
r = &txRecord{tx: tx}
|
|
u.txs[*tx.Sha()] = r
|
|
for _, input := range r.Tx().MsgTx().TxIn {
|
|
u.previousOutpoints[input.PreviousOutpoint] = r
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (s *Store) moveMinedTx(r *txRecord, block *Block) error {
|
|
log.Infof("Marking unconfirmed transaction %v mined in block %d",
|
|
r.tx.Sha(), block.Height)
|
|
|
|
delete(s.unconfirmed.txs, *r.Tx().Sha())
|
|
|
|
// Find collection and insert records. Error out if there are records
|
|
// saved for this block and index.
|
|
key := BlockTxKey{r.Tx().Index(), block.Height}
|
|
b := s.blockCollectionForInserts(block)
|
|
txIndex := uint32(len(b.txs))
|
|
b.txIndexes[key.BlockIndex] = txIndex
|
|
b.txs = append(b.txs, r)
|
|
|
|
for _, input := range r.Tx().MsgTx().TxIn {
|
|
delete(s.unconfirmed.previousOutpoints, input.PreviousOutpoint)
|
|
|
|
// For all mined transactions with credits spent by this
|
|
// transaction, remove them from the spentBlockOutPoints map
|
|
// (signifying that there is no longer an unconfirmed
|
|
// transaction which spending that credit), and update the
|
|
// credit's spent by tracking with the block key of the
|
|
// newly-mined transaction.
|
|
prev, ok := s.unconfirmed.spentBlockOutPointKeys[input.PreviousOutpoint]
|
|
if !ok {
|
|
continue
|
|
}
|
|
delete(s.unconfirmed.spentBlockOutPointKeys, input.PreviousOutpoint)
|
|
delete(s.unconfirmed.spentBlockOutPoints, prev)
|
|
rr, err := s.lookupBlockTx(prev.BlockTxKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rr.credits[prev.OutputIndex].spentBy = &key
|
|
// debits should already be non-nil
|
|
r.debits.spends = append(r.debits.spends, prev)
|
|
}
|
|
if r.debits != nil {
|
|
d := Debits{&TxRecord{key, r, s}}
|
|
s.notifyMinedDebits(d)
|
|
}
|
|
|
|
// For each credit in r, if the credit is spent by another unconfirmed
|
|
// transaction, move the spending transaction from spentUnconfirmed
|
|
// (which signifies a transaction spending another unconfirmed tx) to
|
|
// spentBlockTxs (which signifies an unconfirmed transaction spending a
|
|
// confirmed tx) and modify the mined transaction's record to refer to
|
|
// the credit being spent by an unconfirmed transaction.
|
|
//
|
|
// If the credit is not spent, modify the store's unspent bookkeeping
|
|
// maps to include the credit and increment the amount deltas by the
|
|
// credit's value.
|
|
op := btcwire.OutPoint{Hash: *r.Tx().Sha()}
|
|
for i, credit := range r.credits {
|
|
if credit == nil {
|
|
continue
|
|
}
|
|
op.Index = uint32(i)
|
|
outputKey := BlockOutputKey{key, op.Index}
|
|
if rr, ok := s.unconfirmed.spentUnconfirmed[op]; ok {
|
|
delete(s.unconfirmed.spentUnconfirmed, op)
|
|
s.unconfirmed.spentBlockOutPointKeys[op] = outputKey
|
|
s.unconfirmed.spentBlockOutPoints[outputKey] = rr
|
|
credit.spentBy = &BlockTxKey{BlockHeight: -1}
|
|
} else if credit.spentBy == nil {
|
|
// Mark outpoint unspent.
|
|
s.unspent[op] = key
|
|
|
|
// Increment spendable amount delta as a result of
|
|
// moving this credit to this block.
|
|
value := r.Tx().MsgTx().TxOut[i].Value
|
|
b.amountDeltas.Spendable += btcutil.Amount(value)
|
|
}
|
|
|
|
c := Credit{&TxRecord{key, r, s}, op.Index}
|
|
s.notifyMinedCredit(c)
|
|
}
|
|
|
|
// If this moved transaction debits from any previous credits, decrement
|
|
// the amount deltas by the total input amount. Because this
|
|
// transaction was moved from the unconfirmed transaction set, this can
|
|
// never be a coinbase.
|
|
if r.debits != nil {
|
|
b.amountDeltas.Spendable -= r.debits.amount
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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. Otherwise, the transaction index must be
|
|
// set if a non-nil block is set.
|
|
//
|
|
// The transaction record is returned. Credits and debits may be added to the
|
|
// transaction by calling methods on the TxRecord.
|
|
func (s *Store) InsertTx(tx *btcutil.Tx, block *Block) (*TxRecord, error) {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
|
|
// The receive time will be the earlier of now and the block time
|
|
// (if any).
|
|
received := time.Now()
|
|
|
|
// Verify that the index of the transaction within the block is
|
|
// set if a block is set, and unset if there is no block.
|
|
index := tx.Index()
|
|
switch {
|
|
case index == btcutil.TxIndexUnknown && block != nil:
|
|
return nil, errors.New("transaction block index unset")
|
|
case index != btcutil.TxIndexUnknown && block == nil:
|
|
return nil, errors.New("transaction block index set")
|
|
}
|
|
|
|
// Simply create or return the transaction record if this transaction
|
|
// is unconfirmed.
|
|
if block == nil {
|
|
r := s.unconfirmed.txRecordForInserts(tx)
|
|
r.received = received
|
|
return &TxRecord{BlockTxKey{BlockHeight: -1}, r, s}, nil
|
|
}
|
|
|
|
// Check if block records already exist for this tx. If so,
|
|
// we're done.
|
|
key := BlockTxKey{index, block.Height}
|
|
record, err := s.lookupBlockTx(key)
|
|
switch err.(type) {
|
|
case MissingValueError:
|
|
// handle it later
|
|
case nil:
|
|
// Verify that the txs actually match.
|
|
if *record.tx.Sha() != *tx.Sha() {
|
|
return nil, ErrInconsistentStore
|
|
}
|
|
|
|
return &TxRecord{key, record, s}, nil
|
|
}
|
|
|
|
// If the exact tx (not a double spend) is already included but
|
|
// unconfirmed, move it to a block.
|
|
if r, ok := s.unconfirmed.txs[*tx.Sha()]; ok {
|
|
r.Tx().SetIndex(tx.Index())
|
|
if err := s.moveMinedTx(r, block); err != nil {
|
|
return nil, err
|
|
}
|
|
return &TxRecord{key, r, s}, nil
|
|
}
|
|
|
|
// If this transaction is not already saved unconfirmed, remove all
|
|
// unconfirmed transactions that are now invalidated due to being a
|
|
// double spend. This also handles killing unconfirmed transaction
|
|
// spend chains if any other unconfirmed transactions spend outputs
|
|
// of the removed double spend.
|
|
if err := s.removeDoubleSpends(tx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r := s.blockTxRecordForInserts(tx, block)
|
|
if r.received.IsZero() {
|
|
if !block.Time.IsZero() && block.Time.Before(received) {
|
|
received = block.Time
|
|
}
|
|
r.received = received
|
|
}
|
|
return &TxRecord{key, r, s}, nil
|
|
}
|
|
|
|
// Received returns the earliest known time the transaction was received by.
|
|
func (t *TxRecord) Received() time.Time {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
return t.received
|
|
}
|
|
|
|
// Block returns the block details for a transaction. If the transaction is
|
|
// unmined, both the block and returned error are nil.
|
|
func (t *TxRecord) Block() (*Block, error) {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
coll, err := t.s.lookupBlock(t.BlockHeight)
|
|
if err != nil {
|
|
if err == MissingBlockError(-1) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return &coll.Block, nil
|
|
}
|
|
|
|
// AddDebits marks a transaction record as having debited from previous wallet
|
|
// credits.
|
|
func (t *TxRecord) AddDebits() (Debits, error) {
|
|
t.s.mtx.Lock()
|
|
defer t.s.mtx.Unlock()
|
|
|
|
if t.debits == nil {
|
|
spent, err := t.s.findPreviousCredits(t.Tx())
|
|
if err != nil {
|
|
return Debits{}, err
|
|
}
|
|
debitAmount, err := t.s.markOutputsSpent(spent, t)
|
|
if err != nil {
|
|
return Debits{}, err
|
|
}
|
|
|
|
prevOutputKeys := make([]BlockOutputKey, len(spent))
|
|
for i, c := range spent {
|
|
prevOutputKeys[i] = c.outputKey()
|
|
}
|
|
|
|
t.debits = &debits{amount: debitAmount, spends: prevOutputKeys}
|
|
|
|
log.Debugf("Transaction %v spends %d previously-unspent "+
|
|
"%s totaling %v", t.tx.Sha(), len(spent),
|
|
pickNoun(len(spent), "output", "outputs"), debitAmount)
|
|
}
|
|
|
|
d := Debits{t}
|
|
t.s.notifyNewDebits(d)
|
|
return d, nil
|
|
}
|
|
|
|
// findPreviousCredits searches for all unspent credits that make up the inputs
|
|
// for tx.
|
|
func (s *Store) findPreviousCredits(tx *btcutil.Tx) ([]Credit, error) {
|
|
type createdCredit struct {
|
|
credit Credit
|
|
err error
|
|
}
|
|
|
|
inputs := tx.MsgTx().TxIn
|
|
creditChans := make([]chan createdCredit, len(inputs))
|
|
for i, txIn := range inputs {
|
|
creditChans[i] = make(chan createdCredit)
|
|
go func(i int, op btcwire.OutPoint) {
|
|
key, ok := s.unspent[op]
|
|
if !ok {
|
|
// Does this input spend an unconfirmed output?
|
|
r, ok := s.unconfirmed.txs[op.Hash]
|
|
switch {
|
|
// Not an unconfirmed tx.
|
|
case !ok:
|
|
fallthrough
|
|
// Output isn't a credit.
|
|
case len(r.credits) <= int(op.Index):
|
|
fallthrough
|
|
// Output isn't a credit.
|
|
case r.credits[op.Index] == nil:
|
|
fallthrough
|
|
// Credit already spent.
|
|
case s.unconfirmed.spentUnconfirmed[op] != nil:
|
|
close(creditChans[i])
|
|
return
|
|
}
|
|
t := &TxRecord{BlockTxKey{BlockHeight: -1}, r, s}
|
|
c := Credit{t, op.Index}
|
|
creditChans[i] <- createdCredit{credit: c}
|
|
return
|
|
}
|
|
r, err := s.lookupBlockTx(key)
|
|
if err != nil {
|
|
creditChans[i] <- createdCredit{err: err}
|
|
return
|
|
}
|
|
t := &TxRecord{key, r, s}
|
|
c := Credit{t, op.Index}
|
|
creditChans[i] <- createdCredit{credit: c}
|
|
}(i, txIn.PreviousOutpoint)
|
|
}
|
|
spent := make([]Credit, 0, len(inputs))
|
|
for _, c := range creditChans {
|
|
cc, ok := <-c
|
|
if !ok {
|
|
continue
|
|
}
|
|
if cc.err != nil {
|
|
return nil, cc.err
|
|
}
|
|
spent = append(spent, cc.credit)
|
|
}
|
|
return spent, nil
|
|
}
|
|
|
|
// markOutputsSpent marks each previous credit spent by t as spent. The total
|
|
// input of all spent previous outputs is returned.
|
|
func (s *Store) markOutputsSpent(spent []Credit, t *TxRecord) (btcutil.Amount, error) {
|
|
var a btcutil.Amount
|
|
for _, prev := range spent {
|
|
op := prev.outPoint()
|
|
switch prev.BlockHeight {
|
|
case -1: // unconfirmed
|
|
op := prev.outPoint()
|
|
switch prev.BlockHeight {
|
|
case -1: // unconfirmed
|
|
t.s.unconfirmed.spentUnconfirmed[*op] = t.txRecord
|
|
default:
|
|
key := prev.outputKey()
|
|
t.s.unconfirmed.spentBlockOutPointKeys[*op] = key
|
|
t.s.unconfirmed.spentBlockOutPoints[key] = t.txRecord
|
|
}
|
|
|
|
default:
|
|
// Update spent info.
|
|
credit := prev.txRecord.credits[prev.OutputIndex]
|
|
if credit.spentBy != nil {
|
|
if *credit.spentBy == t.BlockTxKey {
|
|
continue
|
|
}
|
|
return 0, ErrInconsistentStore
|
|
}
|
|
credit.spentBy = &t.BlockTxKey
|
|
delete(s.unspent, *op)
|
|
if t.BlockHeight == -1 { // unconfirmed
|
|
key := prev.outputKey()
|
|
s.unconfirmed.spentBlockOutPointKeys[*op] = key
|
|
s.unconfirmed.spentBlockOutPoints[key] = t.txRecord
|
|
}
|
|
|
|
// Increment total debited amount.
|
|
a += prev.amount()
|
|
}
|
|
}
|
|
|
|
// If t refers to a mined transaction, update its block's amount deltas
|
|
// by the total debited amount.
|
|
if t.BlockHeight != -1 {
|
|
b, err := s.lookupBlock(t.BlockHeight)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
b.amountDeltas.Spendable -= a
|
|
}
|
|
|
|
return a, nil
|
|
}
|
|
|
|
func (r *txRecord) setCredit(index uint32, change bool, tx *btcutil.Tx) error {
|
|
if r.credits == nil {
|
|
r.credits = make([]*credit, 0, len(tx.MsgTx().TxOut))
|
|
}
|
|
for i := uint32(len(r.credits)); i <= index; i++ {
|
|
r.credits = append(r.credits, nil)
|
|
}
|
|
if r.credits[index] != nil {
|
|
if *r.tx.Sha() == *tx.Sha() {
|
|
return ErrDuplicateInsert
|
|
}
|
|
return ErrInconsistentStore
|
|
}
|
|
r.credits[index] = &credit{change: change}
|
|
return nil
|
|
}
|
|
|
|
// AddCredit marks the 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.
|
|
func (t *TxRecord) AddCredit(index uint32, change bool) (Credit, error) {
|
|
t.s.mtx.Lock()
|
|
defer t.s.mtx.Unlock()
|
|
|
|
if len(t.tx.MsgTx().TxOut) <= int(index) {
|
|
return Credit{}, errors.New("transaction output does not exist")
|
|
}
|
|
|
|
if err := t.txRecord.setCredit(index, change, t.tx); err != nil {
|
|
if err == ErrDuplicateInsert {
|
|
return Credit{t, index}, nil
|
|
}
|
|
return Credit{}, err
|
|
}
|
|
|
|
txOutAmt := btcutil.Amount(t.tx.MsgTx().TxOut[index].Value)
|
|
log.Debugf("Marking transaction %v output %d (%v) spendable",
|
|
t.tx.Sha(), index, txOutAmt)
|
|
|
|
switch t.BlockHeight {
|
|
case -1: // unconfirmed
|
|
default:
|
|
b, err := t.s.lookupBlock(t.BlockHeight)
|
|
if err != nil {
|
|
return Credit{}, err
|
|
}
|
|
|
|
// New outputs are added unspent.
|
|
op := btcwire.OutPoint{Hash: *t.tx.Sha(), Index: index}
|
|
t.s.unspent[op] = t.BlockTxKey
|
|
switch t.tx.Index() {
|
|
case 0: // Coinbase
|
|
b.amountDeltas.Reward += txOutAmt
|
|
default:
|
|
b.amountDeltas.Spendable += txOutAmt
|
|
}
|
|
}
|
|
|
|
c := Credit{t, index}
|
|
t.s.notifyNewCredit(c)
|
|
return c, nil
|
|
}
|
|
|
|
// Rollback removes all blocks at height onwards, moving any transactions within
|
|
// each block to the unconfirmed pool.
|
|
func (s *Store) Rollback(height int32) error {
|
|
s.mtx.Lock()
|
|
defer s.mtx.Unlock()
|
|
|
|
i := len(s.blocks)
|
|
for i != 0 && s.blocks[i-1].Height >= height {
|
|
i--
|
|
}
|
|
detached := s.blocks[i:]
|
|
s.blocks = s.blocks[:i]
|
|
for _, b := range detached {
|
|
movedTxs := len(b.txs)
|
|
// Don't include coinbase transaction with number of moved txs.
|
|
// There should always be at least one tx in a block collection,
|
|
// and if there is a coinbase, it would be at index 0.
|
|
if b.txs[0].tx.Index() == 0 {
|
|
movedTxs--
|
|
}
|
|
log.Infof("Rolling back block %d (%d transactions marked "+
|
|
"unconfirmed)", b.Height, movedTxs)
|
|
delete(s.blockIndexes, b.Block.Height)
|
|
for _, r := range b.txs {
|
|
oldTxIndex := r.Tx().Index()
|
|
|
|
// If the removed transaction is a coinbase, do not move
|
|
// it to unconfirmed.
|
|
if oldTxIndex == 0 {
|
|
continue
|
|
}
|
|
|
|
r.Tx().SetIndex(btcutil.TxIndexUnknown)
|
|
s.unconfirmed.txs[*r.Tx().Sha()] = r
|
|
for _, input := range r.Tx().MsgTx().TxIn {
|
|
op := input.PreviousOutpoint
|
|
s.unconfirmed.previousOutpoints[op] = r
|
|
}
|
|
|
|
// For each detached spent credit, remove from the
|
|
// store's unspent map, and lookup the spender and
|
|
// modify its debit record to reference spending an
|
|
// unconfirmed transaction.
|
|
for outIdx, credit := range r.credits {
|
|
if credit == nil {
|
|
continue
|
|
}
|
|
|
|
op := btcwire.OutPoint{
|
|
Hash: *r.Tx().Sha(),
|
|
Index: uint32(outIdx),
|
|
}
|
|
delete(s.unspent, op)
|
|
|
|
spenderKey := credit.spentBy
|
|
if spenderKey == nil {
|
|
continue
|
|
}
|
|
|
|
prev := BlockOutputKey{
|
|
BlockTxKey: BlockTxKey{
|
|
BlockIndex: oldTxIndex,
|
|
BlockHeight: b.Height,
|
|
},
|
|
OutputIndex: uint32(outIdx),
|
|
}
|
|
|
|
// Lookup txRecord of the spending transaction. Spent
|
|
// tracking differs slightly depending on whether the
|
|
// spender is confirmed or not.
|
|
switch spenderKey.BlockHeight {
|
|
case -1: // unconfirmed
|
|
spender, ok := s.unconfirmed.spentBlockOutPoints[prev]
|
|
if !ok {
|
|
return ErrInconsistentStore
|
|
}
|
|
|
|
// Swap the maps the spender is saved in.
|
|
delete(s.unconfirmed.spentBlockOutPointKeys, op)
|
|
delete(s.unconfirmed.spentBlockOutPoints, prev)
|
|
s.unconfirmed.spentUnconfirmed[op] = spender
|
|
|
|
default:
|
|
spender, err := s.lookupBlockTx(*spenderKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if spender.debits == nil {
|
|
return MissingDebitsError(*spenderKey)
|
|
}
|
|
|
|
current := BlockOutputKey{
|
|
BlockTxKey: BlockTxKey{BlockHeight: -1},
|
|
}
|
|
err = spender.swapDebits(prev, current)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// If this transaction debits any previous credits,
|
|
// modify each previous credit to mark it as spent
|
|
// by an unconfirmed tx.
|
|
if r.debits != nil {
|
|
for _, prev := range r.debits.spends {
|
|
rr, err := s.lookupBlockTx(prev.BlockTxKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c, err := rr.lookupBlockCredit(prev)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
op := btcwire.OutPoint{
|
|
Hash: *rr.Tx().Sha(),
|
|
Index: prev.OutputIndex,
|
|
}
|
|
s.unconfirmed.spentBlockOutPointKeys[op] = prev
|
|
s.unconfirmed.spentBlockOutPoints[prev] = r
|
|
c.spentBy = &BlockTxKey{BlockHeight: -1}
|
|
}
|
|
|
|
// Debit tracking for unconfirmed transactions is
|
|
// done by the unconfirmed store's maps, and not
|
|
// in the txRecord itself.
|
|
r.debits.spends = nil
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *txRecord) swapDebits(previous, current BlockOutputKey) error {
|
|
for i, outputKey := range r.debits.spends {
|
|
if outputKey == previous {
|
|
r.debits.spends[i] = current
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return MissingCreditError(previous)
|
|
}
|
|
|
|
// UnminedDebitTxs returns the underlying transactions for all wallet
|
|
// transactions which debit from previous outputs and are not known to have
|
|
// been mined in a block.
|
|
func (s *Store) UnminedDebitTxs() []*btcutil.Tx {
|
|
s.mtx.RLock()
|
|
defer s.mtx.RUnlock()
|
|
|
|
unmined := make([]*btcutil.Tx, 0, len(s.unconfirmed.txs))
|
|
for _, r := range s.unconfirmed.spentBlockOutPoints {
|
|
unmined = append(unmined, r.Tx())
|
|
}
|
|
for _, r := range s.unconfirmed.spentUnconfirmed {
|
|
unmined = append(unmined, r.Tx())
|
|
}
|
|
return unmined
|
|
}
|
|
|
|
// removeDoubleSpends checks for any unconfirmed transactions which would
|
|
// introduce a double spend if tx was added to the store (either as a confirmed
|
|
// or unconfirmed transaction). If one is found, it and all transactions which
|
|
// spends its outputs (if any) are removed, and all previous inputs for any
|
|
// removed transactions are set to unspent.
|
|
func (s *Store) removeDoubleSpends(tx *btcutil.Tx) error {
|
|
if ds := s.unconfirmed.findDoubleSpend(tx); ds != nil {
|
|
log.Debugf("Removing double spending transaction %v", ds.tx.Sha())
|
|
return s.removeConflict(ds)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *unconfirmedStore) findDoubleSpend(tx *btcutil.Tx) *txRecord {
|
|
for _, input := range tx.MsgTx().TxIn {
|
|
if r, ok := u.previousOutpoints[input.PreviousOutpoint]; ok {
|
|
return r
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// removeConflict removes an unconfirmed 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.
|
|
// All not-removed credits spent by removed transactions are set unspent.
|
|
func (s *Store) removeConflict(r *txRecord) error {
|
|
u := &s.unconfirmed
|
|
|
|
// If this transaction contains any spent credits (which must be spent by
|
|
// other unconfirmed transactions), recursively remove each spender.
|
|
for i, credit := range r.credits {
|
|
if credit == nil || credit.spentBy == nil {
|
|
continue
|
|
}
|
|
op := btcwire.NewOutPoint(r.Tx().Sha(), uint32(i))
|
|
nextSpender, ok := u.spentUnconfirmed[*op]
|
|
if !ok {
|
|
return ErrInconsistentStore
|
|
}
|
|
log.Debugf("Transaction %v is part of a removed double spend "+
|
|
"chain -- removing as well", nextSpender.tx.Sha())
|
|
if err := s.removeConflict(nextSpender); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If this tx spends any previous credits, set each unspent.
|
|
for _, input := range r.Tx().MsgTx().TxIn {
|
|
delete(u.previousOutpoints, input.PreviousOutpoint)
|
|
|
|
// For all mined transactions with credits spent by this
|
|
// conflicting transaction, remove from the bookkeeping maps
|
|
// and set each previous record's credit as unspent.
|
|
prevKey, ok := u.spentBlockOutPointKeys[input.PreviousOutpoint]
|
|
if ok {
|
|
delete(u.spentBlockOutPointKeys, input.PreviousOutpoint)
|
|
delete(u.spentBlockOutPoints, prevKey)
|
|
prev, err := s.lookupBlockTx(prevKey.BlockTxKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
prev.credits[prevKey.OutputIndex].spentBy = nil
|
|
continue
|
|
}
|
|
|
|
// For all unmined transactions with credits spent by this
|
|
// conflicting transaction, remove from the unspent store's
|
|
// spent tracking.
|
|
//
|
|
// Spend tracking is only handled by these maps, so there is
|
|
// no need to modify the record and unset a spent-by pointer.
|
|
if _, ok := u.spentUnconfirmed[input.PreviousOutpoint]; ok {
|
|
delete(u.spentUnconfirmed, input.PreviousOutpoint)
|
|
}
|
|
}
|
|
|
|
delete(u.txs, *r.Tx().Sha())
|
|
for _, input := range r.Tx().MsgTx().TxIn {
|
|
delete(u.previousOutpoints, input.PreviousOutpoint)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnspentOutputs returns all unspent received transaction outputs.
|
|
// The order is undefined.
|
|
func (s *Store) UnspentOutputs() ([]Credit, error) {
|
|
s.mtx.RLock()
|
|
defer s.mtx.RUnlock()
|
|
|
|
return s.unspentOutputs()
|
|
}
|
|
|
|
func (s *Store) unspentOutputs() ([]Credit, error) {
|
|
type createdCredit struct {
|
|
credit Credit
|
|
err error
|
|
}
|
|
|
|
creditChans := make([]chan createdCredit, len(s.unspent))
|
|
i := 0
|
|
for op, key := range s.unspent {
|
|
creditChans[i] = make(chan createdCredit)
|
|
go func(i int, key BlockTxKey, opIndex uint32) {
|
|
r, err := s.lookupBlockTx(key)
|
|
if err != nil {
|
|
creditChans[i] <- createdCredit{err: err}
|
|
return
|
|
}
|
|
t := &TxRecord{key, r, s}
|
|
c := Credit{t, opIndex}
|
|
creditChans[i] <- createdCredit{credit: c}
|
|
}(i, key, op.Index)
|
|
i++
|
|
}
|
|
|
|
unspent := make([]Credit, len(s.unspent))
|
|
for i, c := range creditChans {
|
|
cc := <-c
|
|
if cc.err != nil {
|
|
return nil, cc.err
|
|
}
|
|
unspent[i] = cc.credit
|
|
}
|
|
|
|
for _, r := range s.unconfirmed.txs {
|
|
for outputIndex, credit := range r.credits {
|
|
if credit == nil || credit.spentBy != nil {
|
|
continue
|
|
}
|
|
key := BlockTxKey{BlockHeight: -1}
|
|
txRecord := &TxRecord{key, r, s}
|
|
c := Credit{txRecord, uint32(outputIndex)}
|
|
unspent = append(unspent, c)
|
|
}
|
|
}
|
|
|
|
return unspent, nil
|
|
}
|
|
|
|
// unspentTx is a type defined here so it can be sorted with the sort
|
|
// package. It is used to provide a sorted range over all transactions
|
|
// in a block with unspent outputs.
|
|
type unspentTx struct {
|
|
blockIndex int
|
|
sliceIndex uint32
|
|
}
|
|
|
|
type creditSlice []Credit
|
|
|
|
func (s creditSlice) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s creditSlice) Less(i, j int) bool {
|
|
switch {
|
|
// If both credits are from the same tx, sort by output index.
|
|
case s[i].Tx().Sha() == s[j].Tx().Sha():
|
|
return s[i].OutputIndex < s[j].OutputIndex
|
|
|
|
// If both transactions are unmined, sort by their received date.
|
|
case s[i].BlockIndex == -1 && s[j].BlockIndex == -1:
|
|
return s[i].received.Before(s[j].received)
|
|
|
|
// Unmined (newer) txs always come last.
|
|
case s[i].BlockIndex == -1:
|
|
return false
|
|
case s[j].BlockIndex == -1:
|
|
return true
|
|
|
|
// If both txs are mined in different blocks, sort by block height.
|
|
case s[i].BlockHeight != s[j].BlockHeight:
|
|
return s[i].BlockHeight < s[j].BlockHeight
|
|
|
|
// Both txs share the same block, sort by block index.
|
|
default:
|
|
return s[i].BlockIndex < s[j].BlockIndex
|
|
}
|
|
}
|
|
|
|
func (s creditSlice) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
// SortedUnspentOutputs returns all unspent recevied transaction outputs.
|
|
// The order is first unmined transactions (sorted by receive date), then
|
|
// mined transactions in increasing number of confirmations. Transactions
|
|
// in the same block (same number of confirmations) are sorted by block
|
|
// index in increasing order. Credits (outputs) from the same transaction
|
|
// are sorted by output index in increasing order.
|
|
func (s *Store) SortedUnspentOutputs() ([]Credit, error) {
|
|
s.mtx.RLock()
|
|
defer s.mtx.RUnlock()
|
|
|
|
unspent, err := s.unspentOutputs()
|
|
if err != nil {
|
|
return []Credit{}, err
|
|
}
|
|
sort.Sort(sort.Reverse(creditSlice(unspent)))
|
|
return unspent, nil
|
|
}
|
|
|
|
// confirmed checks whether a transaction at height txHeight has met
|
|
// minconf confirmations for a blockchain at height curHeight.
|
|
func confirmed(minconf int, txHeight, curHeight int32) bool {
|
|
return confirms(txHeight, curHeight) >= int32(minconf)
|
|
}
|
|
|
|
// confirms returns the number of confirmations for a transaction in a
|
|
// block at height txHeight (or -1 for an unconfirmed tx) given the chain
|
|
// height curHeight.
|
|
func confirms(txHeight, curHeight int32) int32 {
|
|
switch {
|
|
case txHeight == -1, txHeight > curHeight:
|
|
return 0
|
|
default:
|
|
return curHeight - txHeight + 1
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
func (s *Store) Balance(minConf int, chainHeight int32) (btcutil.Amount, error) {
|
|
s.mtx.RLock()
|
|
defer s.mtx.RUnlock()
|
|
|
|
return s.balance(minConf, chainHeight)
|
|
}
|
|
|
|
func (s *Store) balance(minConf int, chainHeight int32) (btcutil.Amount, error) {
|
|
var bal btcutil.Amount
|
|
|
|
// Shadow these functions to avoid repeating arguments unnecesarily.
|
|
confirms := func(height int32) int32 {
|
|
return confirms(height, chainHeight)
|
|
}
|
|
confirmed := func(height int32) bool {
|
|
return confirmed(minConf, height, chainHeight)
|
|
}
|
|
|
|
for _, b := range s.blocks {
|
|
if confirmed(b.Height) {
|
|
bal += b.amountDeltas.Spendable
|
|
if confirms(b.Height) >= btcchain.CoinbaseMaturity {
|
|
bal += b.amountDeltas.Reward
|
|
}
|
|
continue
|
|
}
|
|
// If there are still blocks that contain debiting transactions,
|
|
// decrement the balance if they spend credits meeting minConf
|
|
// confirmations.
|
|
for _, r := range b.txs {
|
|
if r.debits == nil {
|
|
continue
|
|
}
|
|
for _, prev := range r.debits.spends {
|
|
if !confirmed(prev.BlockHeight) {
|
|
continue
|
|
}
|
|
r, err := s.lookupBlockTx(prev.BlockTxKey)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
v := r.Tx().MsgTx().TxOut[prev.OutputIndex].Value
|
|
bal -= btcutil.Amount(v)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unconfirmed transactions which spend previous credits debit from
|
|
// the returned balance, even with minConf > 0.
|
|
for prev := range s.unconfirmed.spentBlockOutPoints {
|
|
if confirmed(prev.BlockHeight) {
|
|
r, err := s.lookupBlockTx(prev.BlockTxKey)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
v := r.Tx().MsgTx().TxOut[prev.OutputIndex].Value
|
|
bal -= btcutil.Amount(v)
|
|
}
|
|
}
|
|
|
|
// If unconfirmed credits are included, tally them as well.
|
|
if minConf == 0 {
|
|
for _, r := range s.unconfirmed.txs {
|
|
for i, c := range r.credits {
|
|
if c == nil {
|
|
continue
|
|
}
|
|
if c.spentBy == nil {
|
|
v := r.Tx().MsgTx().TxOut[i].Value
|
|
bal += btcutil.Amount(v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bal, nil
|
|
}
|
|
|
|
// Records returns a chronologically-ordered slice of all transaction records
|
|
// saved by the store. This is sorted first by block height in increasing
|
|
// order, and then by transaction index for each tx in a block.
|
|
func (s *Store) Records() (records []*TxRecord) {
|
|
s.mtx.RLock()
|
|
defer s.mtx.RUnlock()
|
|
|
|
for _, b := range s.blocks {
|
|
for _, r := range b.txs {
|
|
key := BlockTxKey{r.tx.Index(), b.Block.Height}
|
|
records = append(records, &TxRecord{key, r, s})
|
|
}
|
|
}
|
|
|
|
// Unconfirmed records are saved unsorted, and must be sorted by
|
|
// received date on the fly.
|
|
unconfirmed := make([]*TxRecord, 0, len(s.unconfirmed.txs))
|
|
for _, r := range s.unconfirmed.txs {
|
|
key := BlockTxKey{BlockHeight: -1}
|
|
unconfirmed = append(unconfirmed, &TxRecord{key, r, s})
|
|
}
|
|
sort.Sort(byReceiveDate(unconfirmed))
|
|
records = append(records, unconfirmed...)
|
|
|
|
return
|
|
}
|
|
|
|
// Implementation of sort.Interface to sort transaction records by their
|
|
// receive date.
|
|
type byReceiveDate []*TxRecord
|
|
|
|
func (r byReceiveDate) Len() int { return len(r) }
|
|
func (r byReceiveDate) Less(i, j int) bool { return r[i].received.Before(r[j].received) }
|
|
func (r byReceiveDate) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
|
|
|
// Debits returns the debit record for the transaction, or a non-nil error if
|
|
// the transaction does not debit from any previous transaction credits.
|
|
func (t *TxRecord) Debits() (Debits, error) {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
if t.debits == nil {
|
|
return Debits{}, errors.New("no debits")
|
|
}
|
|
return Debits{t}, nil
|
|
}
|
|
|
|
// Credits returns all credit records for this transaction's outputs that are or
|
|
// were spendable by wallet.
|
|
func (t *TxRecord) Credits() []Credit {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
credits := make([]Credit, 0, len(t.credits))
|
|
for i, c := range t.credits {
|
|
if c != nil {
|
|
credits = append(credits, Credit{t, uint32(i)})
|
|
}
|
|
}
|
|
return credits
|
|
}
|
|
|
|
// HasCredit returns whether the transaction output at the passed index is
|
|
// a wallet credit.
|
|
func (t *TxRecord) HasCredit(i int) bool {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
if len(t.credits) <= i {
|
|
return false
|
|
}
|
|
return t.credits[i] != nil
|
|
}
|
|
|
|
// InputAmount returns the total amount debited from previous credits.
|
|
func (d Debits) InputAmount() btcutil.Amount {
|
|
d.s.mtx.RLock()
|
|
defer d.s.mtx.RUnlock()
|
|
|
|
return d.txRecord.debits.amount
|
|
}
|
|
|
|
// OutputAmount returns the total amount of all outputs for a transaction.
|
|
func (t *TxRecord) OutputAmount(ignoreChange bool) btcutil.Amount {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
return t.outputAmount(ignoreChange)
|
|
}
|
|
|
|
func (t *TxRecord) outputAmount(ignoreChange bool) btcutil.Amount {
|
|
a := btcutil.Amount(0)
|
|
for i, txOut := range t.Tx().MsgTx().TxOut {
|
|
if ignoreChange {
|
|
switch cs := t.credits; {
|
|
case i < len(cs) && cs[i] != nil && cs[i].change:
|
|
continue
|
|
}
|
|
}
|
|
a += btcutil.Amount(txOut.Value)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Fee returns the difference between the debited amount and the total
|
|
// transaction output.
|
|
func (d Debits) Fee() btcutil.Amount {
|
|
return d.txRecord.debits.amount - d.outputAmount(false)
|
|
}
|
|
|
|
// Addresses parses the pubkey script, extracting all addresses for a
|
|
// standard script.
|
|
func (c Credit) Addresses(net *btcnet.Params) (btcscript.ScriptClass,
|
|
[]btcutil.Address, int, error) {
|
|
|
|
c.s.mtx.RLock()
|
|
defer c.s.mtx.RUnlock()
|
|
|
|
msgTx := c.Tx().MsgTx()
|
|
pkScript := msgTx.TxOut[c.OutputIndex].PkScript
|
|
return btcscript.ExtractPkScriptAddrs(pkScript, net)
|
|
}
|
|
|
|
// Change returns whether the credit is the result of a change output.
|
|
func (c Credit) Change() bool {
|
|
c.s.mtx.RLock()
|
|
defer c.s.mtx.RUnlock()
|
|
|
|
return c.txRecord.credits[c.OutputIndex].change
|
|
}
|
|
|
|
// Confirmed returns whether a transaction has reached some target number of
|
|
// confirmations, given the current best chain height.
|
|
func (t *TxRecord) Confirmed(target int, chainHeight int32) bool {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
return confirmed(target, t.BlockHeight, chainHeight)
|
|
}
|
|
|
|
// Confirmations returns the total number of confirmations a transaction has
|
|
// reached, given the current best chain height.
|
|
func (t *TxRecord) Confirmations(chainHeight int32) int32 {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
return confirms(t.BlockHeight, chainHeight)
|
|
}
|
|
|
|
// IsCoinbase returns whether the transaction is a coinbase.
|
|
func (t *TxRecord) IsCoinbase() bool {
|
|
t.s.mtx.RLock()
|
|
defer t.s.mtx.RUnlock()
|
|
|
|
return t.isCoinbase()
|
|
}
|
|
|
|
func (t *TxRecord) isCoinbase() bool {
|
|
return t.BlockHeight != -1 && t.BlockIndex == 0
|
|
}
|
|
|
|
// Amount returns the amount credited to the account from a transaction output.
|
|
func (c Credit) Amount() btcutil.Amount {
|
|
c.s.mtx.RLock()
|
|
defer c.s.mtx.RUnlock()
|
|
|
|
return c.amount()
|
|
}
|
|
|
|
func (c Credit) amount() btcutil.Amount {
|
|
msgTx := c.Tx().MsgTx()
|
|
return btcutil.Amount(msgTx.TxOut[c.OutputIndex].Value)
|
|
}
|
|
|
|
// OutPoint returns the outpoint needed to include in a transaction input
|
|
// to spend this output.
|
|
func (c Credit) OutPoint() *btcwire.OutPoint {
|
|
c.s.mtx.RLock()
|
|
defer c.s.mtx.RUnlock()
|
|
|
|
return c.outPoint()
|
|
}
|
|
|
|
func (c Credit) outPoint() *btcwire.OutPoint {
|
|
return btcwire.NewOutPoint(c.Tx().Sha(), c.OutputIndex)
|
|
}
|
|
|
|
// outputKey creates and returns the block lookup key for this credit.
|
|
func (c Credit) outputKey() BlockOutputKey {
|
|
return BlockOutputKey{
|
|
BlockTxKey: c.BlockTxKey,
|
|
OutputIndex: c.OutputIndex,
|
|
}
|
|
}
|
|
|
|
// Spent returns whether the transaction output is currently spent or not.
|
|
func (c Credit) Spent() bool {
|
|
c.s.mtx.RLock()
|
|
defer c.s.mtx.RUnlock()
|
|
|
|
return c.txRecord.credits[c.OutputIndex].spentBy != nil
|
|
}
|
|
|
|
// TxOut returns the transaction output which this credit references.
|
|
func (c Credit) TxOut() *btcwire.TxOut {
|
|
c.s.mtx.RLock()
|
|
defer c.s.mtx.RUnlock()
|
|
|
|
return c.Tx().MsgTx().TxOut[c.OutputIndex]
|
|
}
|
|
|
|
// Tx returns the underlying transaction.
|
|
func (r *txRecord) Tx() *btcutil.Tx {
|
|
return r.tx
|
|
}
|