lbcwallet/txstore/tx.go
Josh Rickmar 8795534408 Typo.
2014-05-08 21:21:31 -05:00

1348 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/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 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
}
// 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
}