lbcwallet/txstore/tx.go
Guilherme Salgado 2181f4859d votingpool: implement Pool.StartWithdrawal()
<http://opentransactions.org/wiki/index.php/Voting_Pool_Withdrawal_Process>

Also includes some refactorings and other improvements, including better docs
and a new error type (votingpool.Error) used for all votingpool-specific
errors.
2015-04-01 15:55:42 +01:00

1525 lines
44 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"
"path/filepath"
"sort"
"sync"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
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 wire.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[wire.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[wire.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[wire.OutPoint]BlockOutputKey
// spentUnconfirmed maps from an unconfirmed outpoint to the unconfirmed
// transaction which spends it.
spentUnconfirmed map[wire.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[wire.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[wire.OutPoint]BlockTxKey{},
unconfirmed: unconfirmedStore{
txs: map[wire.ShaHash]*txRecord{},
spentBlockOutPoints: map[BlockOutputKey]*txRecord{},
spentBlockOutPointKeys: map[wire.OutPoint]BlockOutputKey{},
spentUnconfirmed: map[wire.OutPoint]*txRecord{},
previousOutpoints: map[wire.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 := wire.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 wire.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 := wire.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 := wire.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 := wire.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 := wire.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) >= blockchain.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 *chaincfg.Params) (txscript.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 txscript.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() *wire.OutPoint {
c.s.mtx.RLock()
defer c.s.mtx.RUnlock()
return c.outPoint()
}
func (c Credit) outPoint() *wire.OutPoint {
return wire.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() *wire.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
}