/* * 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 txstore import ( "errors" "fmt" "path/filepath" "sort" "sync" "time" "github.com/btcsuite/btcutil" "github.com/conformal/btcchain" "github.com/conformal/btcnet" "github.com/conformal/btcscript" "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 { // TODO: Use atomic operations for dirty so the reader lock // doesn't need to be grabbed. dirty bool path string dir string file string mtx sync.RWMutex // blocks holds wallet transaction records for each block they appear // in. This is sorted by block height in increasing order. A separate // map is included to lookup indexes for blocks at some height. blocks []*blockTxCollection blockIndexes map[int32]uint32 unspent map[btcwire.OutPoint]BlockTxKey // unconfirmed holds a collection of wallet transactions that have not // been mined into a block yet. unconfirmed unconfirmedStore // Channels to notify callers of changes to the transaction store. // These are only created when a caller calls the appropiate // registration method. newCredit chan Credit newDebits chan Debits minedCredit chan Credit minedDebits chan Debits notificationLock sync.Locker } // blockTxCollection holds a collection of wallet transactions from exactly one // block. type blockTxCollection struct { // Block holds the hash, time, and height of the block. Block // amountDeltas is the net increase or decrease of BTC controllable by // wallet addresses due to transactions in this block. This value only // tracks the total amount, not amounts for individual addresses (which // this txstore implementation is not aware of). amountDeltas blockAmounts // txs holds all transaction records for a block, sorted by block index. // A separate map is included to lookup indexes for txs at some block // index. txs []*txRecord txIndexes map[int]uint32 } // unconfirmedStore stores all unconfirmed transactions managed by the Store. type unconfirmedStore struct { // txs contains all unconfirmed transactions, mapping from their // transaction hash to the records. txs map[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 spentBy *BlockTxKey // nil if unspent } // New allocates and initializes a new transaction store. func New(dir string) *Store { return &Store{ path: filepath.Join(dir, filename), dir: dir, file: filename, blockIndexes: map[int32]uint32{}, unspent: map[btcwire.OutPoint]BlockTxKey{}, 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{}, }, notificationLock: new(sync.Mutex), } } func (s *Store) lookupBlock(height int32) (*blockTxCollection, error) { if i, ok := s.blockIndexes[height]; ok { return s.blocks[i], nil } return nil, MissingBlockError(height) } func (s *Store) lookupBlockTx(key BlockTxKey) (*txRecord, error) { coll, err := s.lookupBlock(key.BlockHeight) if err != nil { return nil, err } if i, ok := coll.txIndexes[key.BlockIndex]; ok { return coll.txs[i], nil } return nil, MissingBlockTxError{key.BlockIndex, key.BlockHeight} } func (s *Store) lookupBlockDebits(key BlockTxKey) (*debits, error) { r, err := s.lookupBlockTx(key) if err != nil { return nil, err } if r.debits == nil { return nil, MissingDebitsError(key) } return r.debits, nil } func (r *txRecord) lookupBlockCredit(key BlockOutputKey) (*credit, error) { switch { case len(r.credits) <= int(key.OutputIndex): fallthrough case r.credits[key.OutputIndex] == nil: return nil, MissingCreditError(key) } return r.credits[key.OutputIndex], nil } func (s *Store) lookupBlockCredit(key BlockOutputKey) (*credit, error) { txRecord, err := s.lookupBlockTx(key.BlockTxKey) if err != nil { return nil, err } return txRecord.lookupBlockCredit(key) } func (s *Store) blockCollectionForInserts(block *Block) *blockTxCollection { b, err := s.lookupBlock(block.Height) switch e := err.(type) { case MissingBlockError: b = &blockTxCollection{ Block: *block, txIndexes: map[int]uint32{}, } // If this new block cannot be appended to the end of the blocks // slice (which would disobey ordering blocks by their height), // reslice and update the store's map of block heights to block // slice indexes. if len(s.blocks) > 0 && s.blocks[len(s.blocks)-1].Height > block.Height { i := uint32(len(s.blocks)) for i != 0 && s.blocks[i-1].Height >= block.Height { i-- } detached := s.blocks[i:] s.blocks = append(s.blocks[:i:i], b) s.blockIndexes[b.Height] = i for i, b := range detached { newIndex := uint32(i + len(s.blocks)) s.blockIndexes[b.Height] = newIndex } s.blocks = append(s.blocks, detached...) } else { s.blockIndexes[int32(e)] = uint32(len(s.blocks)) s.blocks = append(s.blocks, b) } } return b } func (c *blockTxCollection) lookupTxRecord(blockIndex int) (*txRecord, uint32, error) { if i, ok := c.txIndexes[blockIndex]; ok { return c.txs[i], i, nil } return nil, 0, MissingBlockTxError{blockIndex, c.Block.Height} } func (c *blockTxCollection) txRecordForInserts(tx *btcutil.Tx) *txRecord { if i, ok := c.txIndexes[tx.Index()]; ok { return c.txs[i] } log.Infof("Inserting transaction %v from block %d", tx.Sha(), c.Height) record := &txRecord{tx: tx} // If this new transaction record cannot be appended to the end of the // txs slice (which would disobey ordering transactions by their block // index), reslice and update the block's map of block indexes to txs // slice indexes. if len(c.txs) > 0 && c.txs[len(c.txs)-1].Tx().Index() > tx.Index() { i := uint32(len(c.txs)) for i != 0 && c.txs[i-1].Tx().Index() >= tx.Index() { i-- } detached := c.txs[i:] c.txs = append(c.txs[:i:i], record) c.txIndexes[tx.Index()] = i for i, r := range detached { newIndex := uint32(i + len(c.txs)) c.txIndexes[r.Tx().Index()] = newIndex } c.txs = append(c.txs, detached...) } else { c.txIndexes[tx.Index()] = uint32(len(c.txs)) c.txs = append(c.txs, record) } return record } func (s *Store) blockTxRecordForInserts(tx *btcutil.Tx, block *Block) *txRecord { return s.blockCollectionForInserts(block).txRecordForInserts(tx) } func (u *unconfirmedStore) txRecordForInserts(tx *btcutil.Tx) *txRecord { r, ok := u.txs[*tx.Sha()] if !ok { log.Infof("Inserting unconfirmed transaction %v", tx.Sha()) r = &txRecord{tx: tx} u.txs[*tx.Sha()] = r for _, input := range r.Tx().MsgTx().TxIn { u.previousOutpoints[input.PreviousOutPoint] = r } } return r } func (s *Store) moveMinedTx(r *txRecord, block *Block) error { log.Infof("Marking unconfirmed transaction %v mined in block %d", r.tx.Sha(), block.Height) delete(s.unconfirmed.txs, *r.Tx().Sha()) // Find collection and insert records. Error out if there are records // saved for this block and index. key := BlockTxKey{r.Tx().Index(), block.Height} b := s.blockCollectionForInserts(block) txIndex := uint32(len(b.txs)) b.txIndexes[key.BlockIndex] = txIndex b.txs = append(b.txs, r) for _, input := range r.Tx().MsgTx().TxIn { delete(s.unconfirmed.previousOutpoints, input.PreviousOutPoint) // For all mined transactions with credits spent by this // transaction, remove them from the spentBlockOutPoints map // (signifying that there is no longer an unconfirmed // transaction which spending that credit), and update the // credit's spent by tracking with the block key of the // newly-mined transaction. prev, ok := s.unconfirmed.spentBlockOutPointKeys[input.PreviousOutPoint] if !ok { continue } delete(s.unconfirmed.spentBlockOutPointKeys, input.PreviousOutPoint) delete(s.unconfirmed.spentBlockOutPoints, prev) rr, err := s.lookupBlockTx(prev.BlockTxKey) if err != nil { return err } rr.credits[prev.OutputIndex].spentBy = &key // debits should already be non-nil r.debits.spends = append(r.debits.spends, prev) } if r.debits != nil { d := Debits{&TxRecord{key, r, s}} s.notifyMinedDebits(d) } // For each credit in r, if the credit is spent by another unconfirmed // transaction, move the spending transaction from spentUnconfirmed // (which signifies a transaction spending another unconfirmed tx) to // spentBlockTxs (which signifies an unconfirmed transaction spending a // confirmed tx) and modify the mined transaction's record to refer to // the credit being spent by an unconfirmed transaction. // // If the credit is not spent, modify the store's unspent bookkeeping // maps to include the credit and increment the amount deltas by the // credit's value. op := btcwire.OutPoint{Hash: *r.Tx().Sha()} for i, credit := range r.credits { if credit == nil { continue } op.Index = uint32(i) outputKey := BlockOutputKey{key, op.Index} if rr, ok := s.unconfirmed.spentUnconfirmed[op]; ok { delete(s.unconfirmed.spentUnconfirmed, op) s.unconfirmed.spentBlockOutPointKeys[op] = outputKey s.unconfirmed.spentBlockOutPoints[outputKey] = rr credit.spentBy = &BlockTxKey{BlockHeight: -1} } else if credit.spentBy == nil { // Mark outpoint unspent. s.unspent[op] = key // Increment spendable amount delta as a result of // moving this credit to this block. value := r.Tx().MsgTx().TxOut[i].Value b.amountDeltas.Spendable += btcutil.Amount(value) } c := Credit{&TxRecord{key, r, s}, op.Index} s.notifyMinedCredit(c) } // If this moved transaction debits from any previous credits, decrement // the amount deltas by the total input amount. Because this // transaction was moved from the unconfirmed transaction set, this can // never be a coinbase. if r.debits != nil { b.amountDeltas.Spendable -= r.debits.amount } return nil } // InsertTx records a transaction as belonging to a wallet's transaction // history. If block is nil, the transaction is considered unspent, and the // transaction's index must be unset. Otherwise, the transaction index must be // set if a non-nil block is set. // // The transaction record is returned. Credits and debits may be added to the // transaction by calling methods on the TxRecord. func (s *Store) InsertTx(tx *btcutil.Tx, block *Block) (*TxRecord, error) { s.mtx.Lock() defer s.mtx.Unlock() // The receive time will be the earlier of now and the block time // (if any). received := time.Now() // Verify that the index of the transaction within the block is // set if a block is set, and unset if there is no block. index := tx.Index() switch { case index == btcutil.TxIndexUnknown && block != nil: return nil, errors.New("transaction block index unset") case index != btcutil.TxIndexUnknown && block == nil: return nil, errors.New("transaction block index set") } // Simply create or return the transaction record if this transaction // is unconfirmed. if block == nil { r := s.unconfirmed.txRecordForInserts(tx) r.received = received return &TxRecord{BlockTxKey{BlockHeight: -1}, r, s}, nil } // Check if block records already exist for this tx. If so, // we're done. key := BlockTxKey{index, block.Height} record, err := s.lookupBlockTx(key) switch err.(type) { case MissingValueError: // handle it later case nil: // Verify that the txs actually match. if *record.tx.Sha() != *tx.Sha() { return nil, ErrInconsistentStore } return &TxRecord{key, record, s}, nil } // If the exact tx (not a double spend) is already included but // unconfirmed, move it to a block. if r, ok := s.unconfirmed.txs[*tx.Sha()]; ok { r.Tx().SetIndex(tx.Index()) if err := s.moveMinedTx(r, block); err != nil { return nil, err } return &TxRecord{key, r, s}, nil } // If this transaction is not already saved unconfirmed, remove all // unconfirmed transactions that are now invalidated due to being a // double spend. This also handles killing unconfirmed transaction // spend chains if any other unconfirmed transactions spend outputs // of the removed double spend. if err := s.removeDoubleSpends(tx); err != nil { return nil, err } r := s.blockTxRecordForInserts(tx, block) if r.received.IsZero() { if !block.Time.IsZero() && block.Time.Before(received) { received = block.Time } r.received = received } return &TxRecord{key, r, s}, nil } // Received returns the earliest known time the transaction was received by. func (t *TxRecord) Received() time.Time { t.s.mtx.RLock() defer t.s.mtx.RUnlock() return t.received } // Block returns the block details for a transaction. If the transaction is // unmined, both the block and returned error are nil. func (t *TxRecord) Block() (*Block, error) { t.s.mtx.RLock() defer t.s.mtx.RUnlock() coll, err := t.s.lookupBlock(t.BlockHeight) if err != nil { if err == MissingBlockError(-1) { return nil, nil } return nil, err } return &coll.Block, nil } // AddDebits marks a transaction record as having debited from previous wallet // credits. func (t *TxRecord) AddDebits() (Debits, error) { t.s.mtx.Lock() defer t.s.mtx.Unlock() if t.debits == nil { spent, err := t.s.findPreviousCredits(t.Tx()) if err != nil { return Debits{}, err } debitAmount, err := t.s.markOutputsSpent(spent, t) if err != nil { return Debits{}, err } prevOutputKeys := make([]BlockOutputKey, len(spent)) for i, c := range spent { prevOutputKeys[i] = c.outputKey() } t.debits = &debits{amount: debitAmount, spends: prevOutputKeys} log.Debugf("Transaction %v spends %d previously-unspent "+ "%s totaling %v", t.tx.Sha(), len(spent), pickNoun(len(spent), "output", "outputs"), debitAmount) } d := Debits{t} t.s.notifyNewDebits(d) return d, nil } // findPreviousCredits searches for all unspent credits that make up the inputs // for tx. func (s *Store) findPreviousCredits(tx *btcutil.Tx) ([]Credit, error) { type createdCredit struct { credit Credit err error } inputs := tx.MsgTx().TxIn creditChans := make([]chan createdCredit, len(inputs)) for i, txIn := range inputs { creditChans[i] = make(chan createdCredit, 1) go func(i int, op btcwire.OutPoint) { key, ok := s.unspent[op] if !ok { // Does this input spend an unconfirmed output? r, ok := s.unconfirmed.txs[op.Hash] switch { // Not an unconfirmed tx. case !ok: fallthrough // Output isn't a credit. case len(r.credits) <= int(op.Index): fallthrough // Output isn't a credit. case r.credits[op.Index] == nil: fallthrough // Credit already spent. case s.unconfirmed.spentUnconfirmed[op] != nil: close(creditChans[i]) return } t := &TxRecord{BlockTxKey{BlockHeight: -1}, r, s} c := Credit{t, op.Index} creditChans[i] <- createdCredit{credit: c} return } r, err := s.lookupBlockTx(key) if err != nil { creditChans[i] <- createdCredit{err: err} return } t := &TxRecord{key, r, s} c := Credit{t, op.Index} creditChans[i] <- createdCredit{credit: c} }(i, txIn.PreviousOutPoint) } spent := make([]Credit, 0, len(inputs)) for _, c := range creditChans { cc, ok := <-c if !ok { continue } if cc.err != nil { return nil, cc.err } spent = append(spent, cc.credit) } return spent, nil } // markOutputsSpent marks each previous credit spent by t as spent. The total // input of all spent previous outputs is returned. func (s *Store) markOutputsSpent(spent []Credit, t *TxRecord) (btcutil.Amount, error) { var a btcutil.Amount for _, prev := range spent { op := prev.outPoint() switch prev.BlockHeight { case -1: // unconfirmed if t.BlockHeight != -1 { // a confirmed tx cannot spend a previous output from an unconfirmed tx return 0, ErrInconsistentStore } op := prev.outPoint() s.unconfirmed.spentUnconfirmed[*op] = t.txRecord default: // Update spent info. credit := prev.txRecord.credits[prev.OutputIndex] if credit.spentBy != nil { if *credit.spentBy == t.BlockTxKey { continue } return 0, ErrInconsistentStore } credit.spentBy = &t.BlockTxKey delete(s.unspent, *op) if t.BlockHeight == -1 { // unconfirmed key := prev.outputKey() s.unconfirmed.spentBlockOutPointKeys[*op] = key s.unconfirmed.spentBlockOutPoints[key] = t.txRecord } // Increment total debited amount. a += prev.amount() } } // If t refers to a mined transaction, update its block's amount deltas // by the total debited amount. if t.BlockHeight != -1 { b, err := s.lookupBlock(t.BlockHeight) if err != nil { return 0, err } b.amountDeltas.Spendable -= a } return a, nil } func (r *txRecord) setCredit(index uint32, change bool, tx *btcutil.Tx) error { if r.credits == nil { r.credits = make([]*credit, 0, len(tx.MsgTx().TxOut)) } for i := uint32(len(r.credits)); i <= index; i++ { r.credits = append(r.credits, nil) } if r.credits[index] != nil { if *r.tx.Sha() == *tx.Sha() { return ErrDuplicateInsert } return ErrInconsistentStore } r.credits[index] = &credit{change: change} return nil } // AddCredit marks the transaction record as containing a transaction output // spendable by wallet. The output is added unspent, and is marked spent // when a new transaction spending the output is inserted into the store. func (t *TxRecord) AddCredit(index uint32, change bool) (Credit, error) { t.s.mtx.Lock() defer t.s.mtx.Unlock() if len(t.tx.MsgTx().TxOut) <= int(index) { return Credit{}, errors.New("transaction output does not exist") } if err := t.txRecord.setCredit(index, change, t.tx); err != nil { if err == ErrDuplicateInsert { return Credit{t, index}, nil } return Credit{}, err } txOutAmt := btcutil.Amount(t.tx.MsgTx().TxOut[index].Value) log.Debugf("Marking transaction %v output %d (%v) spendable", t.tx.Sha(), index, txOutAmt) switch t.BlockHeight { case -1: // unconfirmed default: b, err := t.s.lookupBlock(t.BlockHeight) if err != nil { return Credit{}, err } // New outputs are added unspent. op := btcwire.OutPoint{Hash: *t.tx.Sha(), Index: index} t.s.unspent[op] = t.BlockTxKey switch t.tx.Index() { case 0: // Coinbase b.amountDeltas.Reward += txOutAmt default: b.amountDeltas.Spendable += txOutAmt } } c := Credit{t, index} t.s.notifyNewCredit(c) return c, nil } // Rollback removes all blocks at height onwards, moving any transactions within // each block to the unconfirmed pool. func (s *Store) Rollback(height int32) error { s.mtx.Lock() defer s.mtx.Unlock() i := len(s.blocks) for i != 0 && s.blocks[i-1].Height >= height { i-- } detached := s.blocks[i:] s.blocks = s.blocks[:i] for _, b := range detached { movedTxs := len(b.txs) // Don't include coinbase transaction with number of moved txs. // There should always be at least one tx in a block collection, // and if there is a coinbase, it would be at index 0. if b.txs[0].tx.Index() == 0 { movedTxs-- } log.Infof("Rolling back block %d (%d transactions marked "+ "unconfirmed)", b.Height, movedTxs) delete(s.blockIndexes, b.Block.Height) for _, r := range b.txs { oldTxIndex := r.Tx().Index() // If the removed transaction is a coinbase, do not move // it to unconfirmed. if oldTxIndex == 0 { continue } r.Tx().SetIndex(btcutil.TxIndexUnknown) s.unconfirmed.txs[*r.Tx().Sha()] = r for _, input := range r.Tx().MsgTx().TxIn { op := input.PreviousOutPoint s.unconfirmed.previousOutpoints[op] = r } // For each detached spent credit, remove from the // store's unspent map, and lookup the spender and // modify its debit record to reference spending an // unconfirmed transaction. for outIdx, credit := range r.credits { if credit == nil { continue } op := btcwire.OutPoint{ Hash: *r.Tx().Sha(), Index: uint32(outIdx), } delete(s.unspent, op) spenderKey := credit.spentBy if spenderKey == nil { continue } prev := BlockOutputKey{ BlockTxKey: BlockTxKey{ BlockIndex: oldTxIndex, BlockHeight: b.Height, }, OutputIndex: uint32(outIdx), } // Lookup txRecord of the spending transaction. Spent // tracking differs slightly depending on whether the // spender is confirmed or not. switch spenderKey.BlockHeight { case -1: // unconfirmed spender, ok := s.unconfirmed.spentBlockOutPoints[prev] if !ok { return ErrInconsistentStore } // Swap the maps the spender is saved in. delete(s.unconfirmed.spentBlockOutPointKeys, op) delete(s.unconfirmed.spentBlockOutPoints, prev) s.unconfirmed.spentUnconfirmed[op] = spender default: spender, err := s.lookupBlockTx(*spenderKey) if err != nil { return err } if spender.debits == nil { return MissingDebitsError(*spenderKey) } current := BlockOutputKey{ BlockTxKey: BlockTxKey{BlockHeight: -1}, } err = spender.swapDebits(prev, current) if err != nil { return err } } } // If this transaction debits any previous credits, // modify each previous credit to mark it as spent // by an unconfirmed tx. if r.debits != nil { for _, prev := range r.debits.spends { rr, err := s.lookupBlockTx(prev.BlockTxKey) if err != nil { return err } c, err := rr.lookupBlockCredit(prev) if err != nil { return err } op := btcwire.OutPoint{ Hash: *rr.Tx().Sha(), Index: prev.OutputIndex, } s.unconfirmed.spentBlockOutPointKeys[op] = prev s.unconfirmed.spentBlockOutPoints[prev] = r c.spentBy = &BlockTxKey{BlockHeight: -1} } // Debit tracking for unconfirmed transactions is // done by the unconfirmed store's maps, and not // in the txRecord itself. r.debits.spends = nil } } } return nil } func (r *txRecord) swapDebits(previous, current BlockOutputKey) error { for i, outputKey := range r.debits.spends { if outputKey == previous { r.debits.spends[i] = current return nil } } return MissingCreditError(previous) } // UnminedDebitTxs returns the underlying transactions for all wallet // transactions which debit from previous outputs and are not known to have // been mined in a block. func (s *Store) UnminedDebitTxs() []*btcutil.Tx { s.mtx.RLock() defer s.mtx.RUnlock() unmined := make([]*btcutil.Tx, 0, len(s.unconfirmed.txs)) for _, r := range s.unconfirmed.spentBlockOutPoints { unmined = append(unmined, r.Tx()) } for _, r := range s.unconfirmed.spentUnconfirmed { unmined = append(unmined, r.Tx()) } return unmined } // removeDoubleSpends checks for any unconfirmed transactions which would // introduce a double spend if tx was added to the store (either as a confirmed // or unconfirmed transaction). If one is found, it and all transactions which // spends its outputs (if any) are removed, and all previous inputs for any // removed transactions are set to unspent. func (s *Store) removeDoubleSpends(tx *btcutil.Tx) error { if ds := s.unconfirmed.findDoubleSpend(tx); ds != nil { log.Debugf("Removing double spending transaction %v", ds.tx.Sha()) return s.removeConflict(ds) } return nil } func (u *unconfirmedStore) findDoubleSpend(tx *btcutil.Tx) *txRecord { for _, input := range tx.MsgTx().TxIn { if r, ok := u.previousOutpoints[input.PreviousOutPoint]; ok { return r } } return nil } // removeConflict removes an unconfirmed transaction record and all spend chains // deriving from it from the store. This is designed to remove transactions // that would otherwise result in double spend conflicts if left in the store. // All not-removed credits spent by removed transactions are set unspent. func (s *Store) removeConflict(r *txRecord) error { u := &s.unconfirmed // If this transaction contains any spent credits (which must be spent by // other unconfirmed transactions), recursively remove each spender. for i, credit := range r.credits { if credit == nil || credit.spentBy == nil { continue } op := btcwire.NewOutPoint(r.Tx().Sha(), uint32(i)) nextSpender, ok := u.spentUnconfirmed[*op] if !ok { return ErrInconsistentStore } log.Debugf("Transaction %v is part of a removed double spend "+ "chain -- removing as well", nextSpender.tx.Sha()) if err := s.removeConflict(nextSpender); err != nil { return err } } // If this tx spends any previous credits, set each unspent. for _, input := range r.Tx().MsgTx().TxIn { delete(u.previousOutpoints, input.PreviousOutPoint) // For all mined transactions with credits spent by this // conflicting transaction, remove from the bookkeeping maps // and set each previous record's credit as unspent. prevKey, ok := u.spentBlockOutPointKeys[input.PreviousOutPoint] if ok { delete(u.spentBlockOutPointKeys, input.PreviousOutPoint) delete(u.spentBlockOutPoints, prevKey) prev, err := s.lookupBlockTx(prevKey.BlockTxKey) if err != nil { return err } prev.credits[prevKey.OutputIndex].spentBy = nil continue } // For all unmined transactions with credits spent by this // conflicting transaction, remove from the unspent store's // spent tracking. // // Spend tracking is only handled by these maps, so there is // no need to modify the record and unset a spent-by pointer. if _, ok := u.spentUnconfirmed[input.PreviousOutPoint]; ok { delete(u.spentUnconfirmed, input.PreviousOutPoint) } } delete(u.txs, *r.Tx().Sha()) for _, input := range r.Tx().MsgTx().TxIn { delete(u.previousOutpoints, input.PreviousOutPoint) } return nil } // UnspentOutputs returns all unspent received transaction outputs. // The order is undefined. func (s *Store) UnspentOutputs() ([]Credit, error) { s.mtx.RLock() defer s.mtx.RUnlock() return s.unspentOutputs() } func (s *Store) unspentOutputs() ([]Credit, error) { type createdCredit struct { credit Credit err error } creditChans := make([]chan createdCredit, len(s.unspent)) i := 0 for op, key := range s.unspent { creditChans[i] = make(chan createdCredit, 1) go func(i int, key BlockTxKey, opIndex uint32) { r, err := s.lookupBlockTx(key) if err != nil { creditChans[i] <- createdCredit{err: err} return } opKey := BlockOutputKey{key, opIndex} _, spent := s.unconfirmed.spentBlockOutPoints[opKey] if spent { close(creditChans[i]) return } t := &TxRecord{key, r, s} c := Credit{t, opIndex} creditChans[i] <- createdCredit{credit: c} }(i, key, op.Index) i++ } unspent := make([]Credit, 0, len(s.unspent)) for _, c := range creditChans { cc, ok := <-c if !ok { continue } if cc.err != nil { return nil, cc.err } unspent = append(unspent, cc.credit) } for _, r := range s.unconfirmed.txs { for outputIndex, credit := range r.credits { if credit == nil || credit.spentBy != nil { continue } key := BlockTxKey{BlockHeight: -1} txRecord := &TxRecord{key, r, s} c := Credit{txRecord, uint32(outputIndex)} op := c.outPoint() _, spent := s.unconfirmed.spentUnconfirmed[*op] if !spent { unspent = append(unspent, c) } } } return unspent, nil } // unspentTx is a type defined here so it can be sorted with the sort // package. It is used to provide a sorted range over all transactions // in a block with unspent outputs. type unspentTx struct { blockIndex int sliceIndex uint32 } type creditSlice []Credit func (s creditSlice) Len() int { return len(s) } func (s creditSlice) Less(i, j int) bool { switch { // If both credits are from the same tx, sort by output index. case s[i].Tx().Sha() == s[j].Tx().Sha(): return s[i].OutputIndex < s[j].OutputIndex // If both transactions are unmined, sort by their received date. case s[i].BlockIndex == -1 && s[j].BlockIndex == -1: return s[i].received.Before(s[j].received) // Unmined (newer) txs always come last. case s[i].BlockIndex == -1: return false case s[j].BlockIndex == -1: return true // If both txs are mined in different blocks, sort by block height. case s[i].BlockHeight != s[j].BlockHeight: return s[i].BlockHeight < s[j].BlockHeight // Both txs share the same block, sort by block index. default: return s[i].BlockIndex < s[j].BlockIndex } } func (s creditSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // SortedUnspentOutputs returns all unspent recevied transaction outputs. // The order is first unmined transactions (sorted by receive date), then // mined transactions in increasing number of confirmations. Transactions // in the same block (same number of confirmations) are sorted by block // index in increasing order. Credits (outputs) from the same transaction // are sorted by output index in increasing order. func (s *Store) SortedUnspentOutputs() ([]Credit, error) { s.mtx.RLock() defer s.mtx.RUnlock() unspent, err := s.unspentOutputs() if err != nil { return []Credit{}, err } sort.Sort(sort.Reverse(creditSlice(unspent))) return unspent, nil } // confirmed checks whether a transaction at height txHeight has met // minconf confirmations for a blockchain at height curHeight. func confirmed(minconf int, txHeight, curHeight int32) bool { return confirms(txHeight, curHeight) >= int32(minconf) } // confirms returns the number of confirmations for a transaction in a // block at height txHeight (or -1 for an unconfirmed tx) given the chain // height curHeight. func confirms(txHeight, curHeight int32) int32 { switch { case txHeight == -1, txHeight > curHeight: return 0 default: return curHeight - txHeight + 1 } } // Balance returns the spendable wallet balance (total value of all unspent // transaction outputs) given a minimum of minConf confirmations, calculated // at a current chain height of curHeight. Coinbase outputs are only included // in the balance if maturity has been reached. func (s *Store) Balance(minConf int, chainHeight int32) (btcutil.Amount, error) { s.mtx.RLock() defer s.mtx.RUnlock() return s.balance(minConf, chainHeight) } func (s *Store) balance(minConf int, chainHeight int32) (btcutil.Amount, error) { var bal btcutil.Amount // Shadow these functions to avoid repeating arguments unnecesarily. confirms := func(height int32) int32 { return confirms(height, chainHeight) } confirmed := func(height int32) bool { return confirmed(minConf, height, chainHeight) } for _, b := range s.blocks { if confirmed(b.Height) { bal += b.amountDeltas.Spendable if confirms(b.Height) >= 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) { s.mtx.RLock() defer s.mtx.RUnlock() for _, b := range s.blocks { for _, r := range b.txs { key := BlockTxKey{r.tx.Index(), b.Block.Height} records = append(records, &TxRecord{key, r, s}) } } // Unconfirmed records are saved unsorted, and must be sorted by // received date on the fly. unconfirmed := make([]*TxRecord, 0, len(s.unconfirmed.txs)) for _, r := range s.unconfirmed.txs { key := BlockTxKey{BlockHeight: -1} unconfirmed = append(unconfirmed, &TxRecord{key, r, s}) } sort.Sort(byReceiveDate(unconfirmed)) records = append(records, unconfirmed...) return } // Implementation of sort.Interface to sort transaction records by their // receive date. type byReceiveDate []*TxRecord func (r byReceiveDate) Len() int { return len(r) } func (r byReceiveDate) Less(i, j int) bool { return r[i].received.Before(r[j].received) } func (r byReceiveDate) Swap(i, j int) { r[i], r[j] = r[j], r[i] } // Debits returns the debit record for the transaction, or a non-nil error if // the transaction does not debit from any previous transaction credits. func (t *TxRecord) Debits() (Debits, error) { t.s.mtx.RLock() defer t.s.mtx.RUnlock() if t.debits == nil { return Debits{}, errors.New("no debits") } return Debits{t}, nil } // Credits returns all credit records for this transaction's outputs that are or // were spendable by wallet. func (t *TxRecord) Credits() []Credit { t.s.mtx.RLock() defer t.s.mtx.RUnlock() credits := make([]Credit, 0, len(t.credits)) for i, c := range t.credits { if c != nil { credits = append(credits, Credit{t, uint32(i)}) } } return credits } // HasCredit returns whether the transaction output at the passed index is // a wallet credit. func (t *TxRecord) HasCredit(i int) bool { t.s.mtx.RLock() defer t.s.mtx.RUnlock() if len(t.credits) <= i { return false } return t.credits[i] != nil } // InputAmount returns the total amount debited from previous credits. func (d Debits) InputAmount() btcutil.Amount { d.s.mtx.RLock() defer d.s.mtx.RUnlock() return d.txRecord.debits.amount } // OutputAmount returns the total amount of all outputs for a transaction. func (t *TxRecord) OutputAmount(ignoreChange bool) btcutil.Amount { t.s.mtx.RLock() defer t.s.mtx.RUnlock() return t.outputAmount(ignoreChange) } func (t *TxRecord) outputAmount(ignoreChange bool) btcutil.Amount { a := btcutil.Amount(0) for i, txOut := range t.Tx().MsgTx().TxOut { if ignoreChange { switch cs := t.credits; { case i < len(cs) && cs[i] != nil && cs[i].change: continue } } a += btcutil.Amount(txOut.Value) } return a } // Fee returns the difference between the debited amount and the total // transaction output. func (d Debits) Fee() btcutil.Amount { return d.txRecord.debits.amount - d.outputAmount(false) } // Addresses parses the pubkey script, extracting all addresses for a // standard script. func (c Credit) Addresses(net *btcnet.Params) (btcscript.ScriptClass, []btcutil.Address, int, error) { c.s.mtx.RLock() defer c.s.mtx.RUnlock() msgTx := c.Tx().MsgTx() pkScript := msgTx.TxOut[c.OutputIndex].PkScript return btcscript.ExtractPkScriptAddrs(pkScript, net) } // Change returns whether the credit is the result of a change output. func (c Credit) Change() bool { c.s.mtx.RLock() defer c.s.mtx.RUnlock() return c.txRecord.credits[c.OutputIndex].change } // Confirmed returns whether a transaction has reached some target number of // confirmations, given the current best chain height. func (t *TxRecord) Confirmed(target int, chainHeight int32) bool { t.s.mtx.RLock() defer t.s.mtx.RUnlock() return confirmed(target, t.BlockHeight, chainHeight) } // Confirmations returns the total number of confirmations a transaction has // reached, given the current best chain height. func (t *TxRecord) Confirmations(chainHeight int32) int32 { t.s.mtx.RLock() defer t.s.mtx.RUnlock() return confirms(t.BlockHeight, chainHeight) } // IsCoinbase returns whether the transaction is a coinbase. func (t *TxRecord) IsCoinbase() bool { t.s.mtx.RLock() defer t.s.mtx.RUnlock() return t.isCoinbase() } func (t *TxRecord) isCoinbase() bool { return t.BlockHeight != -1 && t.BlockIndex == 0 } // Amount returns the amount credited to the account from a transaction output. func (c Credit) Amount() btcutil.Amount { c.s.mtx.RLock() defer c.s.mtx.RUnlock() return c.amount() } func (c Credit) amount() btcutil.Amount { msgTx := c.Tx().MsgTx() return btcutil.Amount(msgTx.TxOut[c.OutputIndex].Value) } // OutPoint returns the outpoint needed to include in a transaction input // to spend this output. func (c Credit) OutPoint() *btcwire.OutPoint { c.s.mtx.RLock() defer c.s.mtx.RUnlock() return c.outPoint() } 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 { c.s.mtx.RLock() defer c.s.mtx.RUnlock() return c.txRecord.credits[c.OutputIndex].spentBy != nil } // TxOut returns the transaction output which this credit references. func (c Credit) TxOut() *btcwire.TxOut { c.s.mtx.RLock() defer c.s.mtx.RUnlock() return c.Tx().MsgTx().TxOut[c.OutputIndex] } // Tx returns the underlying transaction. func (r *txRecord) Tx() *btcutil.Tx { return r.tx }