/* * Copyright (c) 2013, 2014 Conformal Systems LLC * * 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, ¤t) 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 }