lbcwallet/txstore/tx.go
Josh Rickmar c3224f4fbc Begin update to use btcnet.Params.
This is an intial pass at converting the btcwallet and deps codebases
to pass a network by their parameters, rather than by a magic number
to identify the network.  The parameters in params.go have been
updated to embed a *btcnet.Params, and all previous uses of cfg.Net()
have been replaced with activeNet.{Params,Net} (where activeNet is
the global var for the active network).

Although dependancy packages have not yet been updated from using
btcwire.BitcoinNet to btcnet.Params, the parameters are now accessible
at all callsites, and individual packages can be updated to use btcnet
without requiring updates in each external btc* package at once.

While here, the exported API for btcwallet internal library packages
(txstore and wallet) have been updated to pass full network parameters
rather than the btcwire definition of a network.
2014-05-22 21:24:08 -05:00

1349 lines
40 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"
"sort"
"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 {
// 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
}
// New allocates and initializes a new transaction store.
func New() *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 {
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.InputAmount() - 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) {
msgTx := c.Tx().MsgTx()
pkScript := msgTx.TxOut[c.OutputIndex].PkScript
return btcscript.ExtractPkScriptAddrs(pkScript, net.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
}
// 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 {
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 {
return confirms(t.BlockHeight, chainHeight)
}
// 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
}