1525 lines
44 KiB
Go
1525 lines
44 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/btcsuite/btcd/blockchain"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcnet"
|
|
"github.com/btcsuite/btcutil"
|
|
)
|
|
|
|
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 wire.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[wire.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[wire.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[wire.OutPoint]BlockOutputKey
|
|
|
|
// spentUnconfirmed maps from an unconfirmed outpoint to the unconfirmed
|
|
// transaction which spends it.
|
|
spentUnconfirmed map[wire.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[wire.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[wire.OutPoint]BlockTxKey{},
|
|
unconfirmed: unconfirmedStore{
|
|
txs: map[wire.ShaHash]*txRecord{},
|
|
spentBlockOutPoints: map[BlockOutputKey]*txRecord{},
|
|
spentBlockOutPointKeys: map[wire.OutPoint]BlockOutputKey{},
|
|
spentUnconfirmed: map[wire.OutPoint]*txRecord{},
|
|
previousOutpoints: map[wire.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: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: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 := wire.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, 1)
|
|
go func(i int, op wire.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
|
|
if t.BlockHeight != -1 {
|
|
// a confirmed tx cannot spend a previous output from an unconfirmed tx
|
|
return 0, ErrInconsistentStore
|
|
}
|
|
op := prev.outPoint()
|
|
s.unconfirmed.spentUnconfirmed[*op] = 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 := wire.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 := wire.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 := wire.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 := wire.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, 1)
|
|
go func(i int, key BlockTxKey, opIndex uint32) {
|
|
r, err := s.lookupBlockTx(key)
|
|
if err != nil {
|
|
creditChans[i] <- createdCredit{err: err}
|
|
return
|
|
}
|
|
|
|
opKey := BlockOutputKey{key, opIndex}
|
|
_, spent := s.unconfirmed.spentBlockOutPoints[opKey]
|
|
if spent {
|
|
close(creditChans[i])
|
|
return
|
|
}
|
|
|
|
t := &TxRecord{key, r, s}
|
|
c := Credit{t, opIndex}
|
|
creditChans[i] <- createdCredit{credit: c}
|
|
}(i, key, op.Index)
|
|
i++
|
|
}
|
|
|
|
unspent := make([]Credit, 0, len(s.unspent))
|
|
for _, c := range creditChans {
|
|
cc, ok := <-c
|
|
if !ok {
|
|
continue
|
|
}
|
|
if cc.err != nil {
|
|
return nil, cc.err
|
|
}
|
|
unspent = append(unspent, 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)}
|
|
op := c.outPoint()
|
|
_, spent := s.unconfirmed.spentUnconfirmed[*op]
|
|
if !spent {
|
|
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) >= blockchain.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) (txscript.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 txscript.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() *wire.OutPoint {
|
|
c.s.mtx.RLock()
|
|
defer c.s.mtx.RUnlock()
|
|
|
|
return c.outPoint()
|
|
}
|
|
|
|
func (c Credit) outPoint() *wire.OutPoint {
|
|
return wire.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() *wire.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
|
|
}
|