lbcwallet/tx/tx.go
Josh Rickmar e9bdf2a094 Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software.  For a variety of reasons, it was very slow at
processing transaction inserts.  Among them:

1) Every single transaction record being saved in a linked list
   (container/list), and inserting into this list would be an O(n)
   operation so that records could be ordered by receive date.

2) Every single transaction in the above mentioned list was iterated
   over in order to find double spends which must be removed.  It is
   silly to do this check for mined transactions, which already have
   been checked for this by btcd.  Worse yet, if double spends were
   found, the list would be iterated a second (or third, or fourth)
   time for each removed transaction.

3) All spend tracking for signed-by-wallet transactions was found on
   each transaction insert, even if the now spent previous transaction
   outputs were known by the caller.

This list could keep going on, but you get the idea.  It was bad.

To resolve these issues a new transaction store had to be implemented.
The new implementation:

1) Tracks mined and unmined transactions in different data structures.
   Mined transactions are cheap to track because the required double
   spend checks have already been performed by the chain server, and
   double spend checks are only required to be performed on
   newly-inserted mined transactions which may conflict with previous
   unmined transactions.

2) Saves mined transactions grouped by block first, and then by their
   transaction index.  Lookup keys for mined transactions are simply
   the block height (in the best chain, that's all we save) and index
   of the transaction in the block.  This makes looking up any
   arbitrary transaction almost an O(1) operation (almost, because
   block height and block indexes are mapped to their slice indexes
   with a Go map).

3) Saves records in each transaction for whether the outputs are
   wallet credits (spendable by wallet) and for whether inputs debit
   from previous credits.  Both structures point back to the source
   or spender (credits point to the transaction that spends them, or
   nil for unspent credits, and debits include keys to lookup the
   transaction credits they spent.  While complicated to keep track
   of, this greatly simplifies the spent tracking for transactions
   across rollbacks and transaction removals.

4) Implements double spend checking as an almost O(1) operation.  A
   Go map is used to map each previous outpoint for all unconfirmed
   transactions to the unconfirmed tx record itself.  Checking for
   double spends on confirmed transaction inserts only involves
   looking up each previous outpoint of the inserted tx in this map.
   If a double spend is found, removal is simplified by only
   removing the transaction and its spend chain from store maps,
   rather than iterating a linked list several times over to remove
   each dead transaction in the spend chain.

5) Allows the caller to specify the previous credits which are spent
   by a debiting transaction.  When a transaction is created by
   wallet, the previous outputs are already known, and by passing
   their record types to the AddDebits method, lookups for each
   previously unspent credit are omitted.

6) Bookkeeps all blocks with transactions with unspent credits, and
   bookkeeps the transaction indexes of all transactions with unspent
   outputs for a single block.  For the case where the caller adding a
   debit record does not know what credits a transaction debits from,
   these bookkeeping structures allow the store to only consider known
   unspent transactions, rather than searching through both spent and
   unspents.

7) Saves amount deltas for the entire balance as a result of each
   block, due to transactions within that block.  This improves the
   performance of calculating the full balance by not needing to
   iterate over every transaction, and then every credit, to determine
   if a credit is spent or unspent.  When transactions are moved from
   unconfirmed to a block structure, the amount deltas are incremented
   by the amount of all transaction credits (both spent and unspent)
   and debited by the total amount the transaction spends from
   previous wallet credits.  For the common case of calculating a
   balance with just one confirmation, the only involves iterating
   over each block structure and adding the (possibly negative)
   amount delta.  Coinbase rewards are saved similarly, but with a
   different amount variable so they can be seperatly included or
   excluded.

Due to all of the changes in how the store internally works, the
serialization format has changed.  To simplify the serialization
logic, support for reading the last store file version has been
removed.  Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 16:12:05 -05:00

1338 lines
39 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 tx
import (
"errors"
"fmt"
"sort"
"time"
"github.com/conformal/btcchain"
"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 is spendable
// by wallet.
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 {
// 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 is a set of block heights which contain transactions with
// unspent outputs.
unspent map[int32]struct{}
// unconfirmed holds a collection of wallet transactions that have not
// been mined into a block yet.
unconfirmed unconfirmedStore
}
// 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
// unspents maps block indexes of transactions with unspent outputs to
// their index in the txs slice.
unspent 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
locked bool
spentBy *BlockTxKey // nil if unspent
}
// NewStore allocates and initializes a new transaction store.
func NewStore() *Store {
return &Store{
blockIndexes: map[int32]uint32{},
unspent: map[int32]struct{}{},
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{},
},
}
}
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{},
unspent: 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]
}
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
if _, ok := c.unspent[r.Tx().Index()]; ok {
c.unspent[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 {
r = &txRecord{tx: tx}
u.txs[*tx.Sha()] = r
for _, input := range r.Tx().MsgTx().TxIn {
u.previousOutpoints[input.PreviousOutpoint] = r
}
}
return r
}
func (r *txRecord) setDebitsSpends(spends []*BlockOutputKey, tx *btcutil.Tx) error {
if r.debits.spends != nil {
if *r.tx.Sha() == *tx.Sha() {
return ErrDuplicateInsert
}
return ErrInconsistentStore
}
r.debits.spends = spends
return nil
}
func (r *txRecord) setCredit(c *credit, index uint32, tx *btcutil.Tx) error {
if len(r.credits) <= int(index) {
r.credits = extendCredits(r.credits, index)
}
if r.credits[index] != nil {
if *r.tx.Sha() == *tx.Sha() {
return ErrDuplicateInsert
}
return ErrInconsistentStore
}
r.credits[index] = c
return nil
}
func extendCredits(c []*credit, index uint32) []*credit {
missing := make([]*credit, int(index+1)-len(c))
return append(c, missing...)
}
func (s *Store) moveMinedTx(r *txRecord, block *Block) error {
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
}
if len(rr.credits) <= int(prev.OutputIndex) {
rr.credits = extendCredits(rr.credits, prev.OutputIndex)
}
rr.credits[prev.OutputIndex].spentBy = &key
// debits should already be non-nil
r.debits.spends = append(r.debits.spends, &prev)
}
// 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.
for i, credit := range r.credits {
if credit == nil {
continue
}
op := btcwire.NewOutPoint(r.Tx().Sha(), uint32(i))
outputKey := BlockOutputKey{key, uint32(i)}
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 entire transaction as containing at least one
// unspent credit.
s.unspent[key.BlockHeight] = struct{}{}
b.unspent[key.BlockIndex] = txIndex
// 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)
}
}
// 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) {
// 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 {
key := BlockTxKey{BlockHeight: -1}
r := s.unconfirmed.txRecordForInserts(tx)
r.received = received
return &TxRecord{key, 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 {
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) {
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 all them transaction
// credits in the spent slice. If spent is nil, the previous debits will be found,
// however this is an expensive lookup and should be avoided if possible.
func (t *TxRecord) AddDebits(spent []*Credit) (*Debits, error) {
if t.debits == nil {
// Find now-spent credits if no debits have been previously set
// and none were passed in by the caller.
if len(spent) == 0 {
foundSpent, err := t.s.findPreviousCredits(t.Tx())
if err != nil {
return nil, err
}
spent = foundSpent
}
debitAmount, err := t.s.markOutputsSpent(spent, t)
if err != nil {
return nil, err
}
t.debits = &debits{amount: debitAmount}
}
switch t.BlockHeight {
case -1: // unconfimred
for _, c := range spent {
op := c.OutPoint()
switch c.BlockHeight {
case -1: // unconfirmed
t.s.unconfirmed.spentUnconfirmed[*op] = t.txRecord
default:
key := c.outputKey()
t.s.unconfirmed.spentBlockOutPointKeys[*op] = *key
t.s.unconfirmed.spentBlockOutPoints[*key] = t.txRecord
}
}
default:
if t.debits.spends == nil {
prevOutputKeys := make([]*BlockOutputKey, 0, len(spent))
for _, c := range spent {
prevOutputKeys = append(prevOutputKeys, c.outputKey())
}
err := t.txRecord.setDebitsSpends(prevOutputKeys, t.tx)
if err != nil {
if err == ErrDuplicateInsert {
return &Debits{t}, nil
}
return nil, err
}
}
}
return &Debits{t}, nil
}
// findPreviousCredits searches for all unspent credits that make up the inputs
// for tx. This lookup is very expensive and should be avoided at all costs.
func (s *Store) findPreviousCredits(tx *btcutil.Tx) ([]*Credit, error) {
unfound := make(map[btcwire.OutPoint]struct{}, len(tx.MsgTx().TxIn))
for _, txIn := range tx.MsgTx().TxIn {
unfound[txIn.PreviousOutpoint] = struct{}{}
}
spent := make([]*Credit, 0, len(unfound))
done:
for blockHeight := range s.unspent {
b, err := s.lookupBlock(blockHeight)
if err != nil {
return nil, err
}
for blockIndex, txIdx := range b.unspent {
if uint32(len(b.txs)) <= txIdx {
return nil, MissingBlockTxError{
BlockIndex: blockIndex,
BlockHeight: blockHeight,
}
}
r := b.txs[txIdx]
op := btcwire.OutPoint{Hash: *r.Tx().Sha()}
for i, cred := range r.credits {
if cred == nil || cred.spentBy != nil {
continue
}
op.Index = uint32(i)
if _, ok := unfound[op]; ok {
key := BlockTxKey{blockIndex, b.Height}
t := &TxRecord{key, r, s}
c := &Credit{t, op.Index}
spent = append(spent, c)
delete(unfound, op)
if len(unfound) == 0 {
break done
}
}
}
}
}
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 {
switch prev.BlockHeight {
case -1: // unconfirmed
op := prev.OutPoint()
s.unconfirmed.spentUnconfirmed[*op] = t.txRecord
default:
b, err := s.lookupBlock(prev.BlockHeight)
if err != nil {
return 0, err
}
r, _, err := b.lookupTxRecord(prev.BlockIndex)
if err != nil {
return 0, err
}
// Update spent info. If this transaction (and possibly
// block) no longer contains any unspent transactions,
// remove from bookkeeping maps.
credit := prev.txRecord.credits[prev.OutputIndex]
if credit.spentBy != nil {
if *credit.spentBy == t.BlockTxKey {
continue
}
return 0, ErrInconsistentStore
}
credit.spentBy = &t.BlockTxKey
if !r.hasUnspents() {
delete(b.unspent, prev.BlockIndex)
if len(b.unspent) == 0 {
delete(s.unspent, b.Height)
}
}
if t.BlockHeight == -1 { // unconfirmed
op := prev.OutPoint()
key := prev.outputKey()
s.unconfirmed.spentBlockOutPointKeys[*op] = *key
s.unconfirmed.spentBlockOutPoints[*key] = t.txRecord
}
// Increment total debited amount.
v := r.Tx().MsgTx().TxOut[prev.OutputIndex].Value
a += btcutil.Amount(v)
}
}
// 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) hasUnspents() bool {
for _, credit := range r.credits {
if credit == nil {
continue
}
if credit.spentBy == nil {
return true
}
}
return false
}
// 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) {
if len(t.tx.MsgTx().TxOut) <= int(index) {
return nil, errors.New("transaction output does not exist")
}
c := &credit{change: change}
if err := t.txRecord.setCredit(c, index, t.tx); err != nil {
if err == ErrDuplicateInsert {
return &Credit{t, index}, nil
}
return nil, err
}
switch t.BlockHeight {
case -1: // unconfirmed
default:
b, err := t.s.lookupBlock(t.BlockHeight)
if err != nil {
return nil, err
}
_, txsIndex, err := b.lookupTxRecord(t.Tx().Index())
if err != nil {
return nil, err
}
// New outputs are added unspent.
t.s.unspent[t.BlockTxKey.BlockHeight] = struct{}{}
b.unspent[t.Tx().Index()] = txsIndex
switch a := t.tx.MsgTx().TxOut[index].Value; t.tx.Index() {
case 0: // Coinbase
b.amountDeltas.Reward += btcutil.Amount(a)
default:
b.amountDeltas.Spendable += btcutil.Amount(a)
}
}
return &Credit{t, index}, 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 {
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 {
delete(s.blockIndexes, b.Block.Height)
delete(s.unspent, 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, lookup the spender
// and modify its debit record to reference spending an
// unconfirmed transaction.
for outIdx, credit := range r.credits {
if credit == nil {
continue
}
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.
op := btcwire.OutPoint{
Hash: *r.Tx().Sha(),
Index: uint32(outIdx),
}
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: uint32(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 == nil {
continue
}
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 {
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 {
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
}
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) {
unspent := make([]*Credit, 0, len(s.unspent))
for height := range s.unspent {
b, err := s.lookupBlock(height)
if err != nil {
return nil, err
}
for blockIndex, index := range b.unspent {
r := b.txs[index]
for outputIndex, credit := range r.credits {
if credit == nil || credit.spentBy != nil {
continue
}
key := BlockTxKey{blockIndex, b.Height}
txRecord := &TxRecord{key, r, s}
c := &Credit{txRecord, uint32(outputIndex)}
unspent = append(unspent, c)
}
}
}
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
}
// 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) {
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) {
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, len(s.unconfirmed.txs))
for _, r := range s.unconfirmed.txs {
key := BlockTxKey{BlockHeight: -1}
unconfirmed = append(records, &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 nil if the
// transaction does not debit from any previous transaction credits.
func (t *TxRecord) Debits() *Debits {
if t.debits == nil {
return nil
}
return &Debits{t}
}
// Credits returns all credit records for this transaction's outputs that are or
// were spendable by wallet.
func (t *TxRecord) Credits() []*Credit {
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
}
// InputAmount returns the total amount debited from previous credits.
func (d *Debits) InputAmount() btcutil.Amount {
return d.txRecord.debits.amount
}
// OutputAmount returns the total amount of all outputs for a transaction.
func (t *TxRecord) OutputAmount(ignoreChange bool) btcutil.Amount {
a := btcutil.Amount(0)
for i, txOut := range t.Tx().MsgTx().TxOut {
switch {
case !ignoreChange:
fallthrough
case len(t.credits) <= i:
fallthrough
case t.credits[i] == nil:
fallthrough
case !t.credits[i].change:
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.InputAmount() - d.OutputAmount(false)
}
// Addresses parses the pubkey script, extracting all addresses for a
// standard script.
func (c *Credit) Addresses(net btcwire.BitcoinNet) (btcscript.ScriptClass,
[]btcutil.Address, int, error) {
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 {
return c.txRecord.credits[c.OutputIndex].change
}
// IsCoinbase returns whether the transaction is a coinbase.
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 {
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 {
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 {
return c.txRecord.credits[c.OutputIndex].spentBy != nil
}
// TxOut returns the transaction output which this credit references.
func (c *Credit) TxOut() *btcwire.TxOut {
return c.Tx().MsgTx().TxOut[c.OutputIndex]
}
// Tx returns the underlying transaction.
func (r *txRecord) Tx() *btcutil.Tx {
return r.tx
}