From bcdc3c91f2bb856d269d36633c388e994482a242 Mon Sep 17 00:00:00 2001 From: Josh Rickmar Date: Thu, 16 Apr 2015 16:53:02 -0400 Subject: [PATCH] Remove legacy txstore. --- legacy/txstore/doc.go | 102 --- legacy/txstore/fixedIO_test.go | 41 - legacy/txstore/json.go | 198 ---- legacy/txstore/log.go | 68 -- legacy/txstore/notifications.go | 145 --- legacy/txstore/serialization.go | 1220 ------------------------- legacy/txstore/tx.go | 1525 ------------------------------- legacy/txstore/tx_test.go | 596 ------------ 8 files changed, 3895 deletions(-) delete mode 100644 legacy/txstore/doc.go delete mode 100644 legacy/txstore/fixedIO_test.go delete mode 100644 legacy/txstore/json.go delete mode 100644 legacy/txstore/log.go delete mode 100644 legacy/txstore/notifications.go delete mode 100644 legacy/txstore/serialization.go delete mode 100644 legacy/txstore/tx.go delete mode 100644 legacy/txstore/tx_test.go diff --git a/legacy/txstore/doc.go b/legacy/txstore/doc.go deleted file mode 100644 index fb852918b6..0000000000 --- a/legacy/txstore/doc.go +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 provides an implementation of a transaction store for a -// bitcoin wallet. Its primary purpose is to save transactions with -// outputs spendable with wallet keys and transactions that are signed by -// wallet keys in memory, handle spend tracking for newly-inserted -// transactions, report the spendable balance from each unspent -// transaction output, and finally to provide a means to serialize the -// entire data structure to an io.Writer and deserialize from an io.Reader -// (both of which are usually an os.File). -// -// Transaction outputs which are spendable by wallet keys are called -// credits (because they credit to a wallet's total spendable balance) -// and are modeled using the Credit structure. Transaction inputs which -// spend previously-inserted credits are called debits (because they debit -// from the wallet's spendable balance) and are modeled using the Debit -// structure. -// -// Besides just saving transactions, bidirectional spend tracking is also -// performed on each credit and debit. Unlike packages such as btcdb, -// which only mark whether a transaction output is spent or unspent, this -// package always records which transaction is responsible for debiting -// (spending) any credit. Each debit also points back to the transaction -// credit it spends. -// -// A significant amount of internal bookkeeping is used to improve the -// performance of inserting transactions and querying commonly-needed -// data. Most notably, all unspent credits may be iterated over without -// including (and ignoring) spent credits. Another trick is to record -// the total spendable amount delta as a result of all transactions within -// a block, which is the total value of all credits (both spent and -// unspent) minus the total value debited from previous transactions, for -// every transaction in that block. This allows for the calculation of a -// wallet's balance for any arbitrary number of confirmations without -// needing to iterate over every unspent credit. -// -// Finally, this package records transaction insertion history (such as -// the date a transaction was first received) and is able to create the -// JSON reply structure for RPC calls such as listtransactions for any -// saved transaction. -// -// To use the transaction store, a transaction must be first inserted -// with InsertTx. After an insert, credits and debits may be attached to -// the returned transaction record using the AddCredit and AddDebits -// methods. -// -// Example use: -// -// // Create a new transaction store to hold two transactions. -// s := txstore.New() -// -// // Insert a transaction belonging to some imaginary block at -// // height 123. -// b123 := &txstore.Block{Height: 123, Time: time.Now()} -// r1, err := s.InsertTx(txA, b123) -// if err != nil { -// // handle error -// } -// -// // Mark output 0 as being a non-change credit to this wallet. -// c1o0, err := r1.AddCredit(0, false) -// if err != nil { -// // handle error -// } -// -// // c1o0 (credit 1 output 0) is inserted unspent. -// fmt.Println(c1o0.Spent()) // Prints "false" -// fmt.Println(s.Balance(1, 123)) // Prints amount of txA output 0. -// -// // Insert a second transaction at some imaginary block height -// // 321. -// b321 := &txstore.Block{Height: 321, Time: time.Now()} -// r2, err := s.InsertTx(txB, b321) -// if err != nil { -// // handle error -// } -// -// // Mark r2 as debiting from record 1's 0th credit. -// d2, err := r2.AddDebits([]txstore.Credit{c1o0}) -// if err != nil { -// // handle error -// } -// -// // Spend tracking and the balances are updated accordingly. -// fmt.Println(c1o0.Spent()) // Prints "true" -// fmt.Println(s.Balance(1, 321)) // Prints "0 BTC" -// fmt.Println(d2.InputAmount()) // Prints amount of txA output 0. -package txstore diff --git a/legacy/txstore/fixedIO_test.go b/legacy/txstore/fixedIO_test.go deleted file mode 100644 index b642165d4c..0000000000 --- a/legacy/txstore/fixedIO_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// copied from wire - -// Copyright (c) 2013-2014 Conformal Systems LLC. -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package txstore_test - -import ( - "io" -) - -// fixedWriter implements the io.Writer interface and intentially allows -// testing of error paths by forcing short writes. -type fixedWriter struct { - b []byte - pos int -} - -// Write ... -func (w *fixedWriter) Write(p []byte) (n int, err error) { - lenp := len(p) - if w.pos+lenp > cap(w.b) { - return 0, io.ErrShortWrite - } - n = lenp - w.pos += copy(w.b[w.pos:], p) - return -} - -// Bytes ... -func (w *fixedWriter) Bytes() []byte { - return w.b -} - -// newFixedWriter... -func newFixedWriter(max int64) *fixedWriter { - b := make([]byte, max, max) - fw := fixedWriter{b, 0} - return &fw -} diff --git a/legacy/txstore/json.go b/legacy/txstore/json.go deleted file mode 100644 index 549ef8cf85..0000000000 --- a/legacy/txstore/json.go +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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 ( - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" -) - -// ToJSON returns a slice of btcjson listtransactions result types for all credits -// and debits of this transaction. -func (t *TxRecord) ToJSON(account string, chainHeight int32, - net *chaincfg.Params) ([]btcjson.ListTransactionsResult, error) { - - t.s.mtx.RLock() - defer t.s.mtx.RUnlock() - - results := []btcjson.ListTransactionsResult{} - if d, err := t.Debits(); err == nil { - r, err := d.toJSON(account, chainHeight, net) - if err != nil { - return nil, err - } - results = r - } - for _, c := range t.Credits() { - r, err := c.toJSON(account, chainHeight, net) - if err != nil { - return nil, err - } - results = append(results, r) - } - return results, nil -} - -// ToJSON returns a slice of objects that may be marshaled as a JSON array -// of JSON objects for a listtransactions RPC reply. -func (d Debits) ToJSON(account string, chainHeight int32, - net *chaincfg.Params) ([]btcjson.ListTransactionsResult, error) { - - d.s.mtx.RLock() - defer d.s.mtx.RUnlock() - - return d.toJSON(account, chainHeight, net) -} - -func (d Debits) toJSON(account string, chainHeight int32, - net *chaincfg.Params) ([]btcjson.ListTransactionsResult, error) { - - msgTx := d.Tx().MsgTx() - reply := make([]btcjson.ListTransactionsResult, 0, len(msgTx.TxOut)) - - for _, txOut := range msgTx.TxOut { - address := "" - _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.PkScript, net) - if len(addrs) == 1 { - address = addrs[0].EncodeAddress() - } - - result := btcjson.ListTransactionsResult{ - Account: account, - Address: address, - Category: "send", - Amount: btcutil.Amount(-txOut.Value).ToBTC(), - Fee: d.Fee().ToBTC(), - TxID: d.Tx().Sha().String(), - Time: d.txRecord.received.Unix(), - TimeReceived: d.txRecord.received.Unix(), - WalletConflicts: []string{}, - } - if d.BlockHeight != -1 { - b, err := d.s.lookupBlock(d.BlockHeight) - if err != nil { - return nil, err - } - - result.BlockHash = b.Hash.String() - result.BlockIndex = int64(d.Tx().Index()) - result.BlockTime = b.Time.Unix() - result.Confirmations = int64(confirms(d.BlockHeight, chainHeight)) - } - reply = append(reply, result) - } - - return reply, nil -} - -// CreditCategory describes the type of wallet transaction output. The category -// of "sent transactions" (debits) is always "send", and is not expressed by -// this type. -type CreditCategory int - -// These constants define the possible credit categories. -const ( - CreditReceive CreditCategory = iota - CreditGenerate - CreditImmature -) - -// Category returns the category of the credit. The passed block chain height is -// used to distinguish immature from mature coinbase outputs. -func (c *Credit) Category(chainHeight int32) CreditCategory { - c.s.mtx.RLock() - defer c.s.mtx.RUnlock() - - return c.category(chainHeight) -} - -func (c *Credit) category(chainHeight int32) CreditCategory { - if c.isCoinbase() { - if confirmed(blockchain.CoinbaseMaturity, c.BlockHeight, chainHeight) { - return CreditGenerate - } - return CreditImmature - } - return CreditReceive -} - -// String returns the category as a string. This string may be used as the -// JSON string for categories as part of listtransactions and gettransaction -// RPC responses. -func (c CreditCategory) String() string { - switch c { - case CreditReceive: - return "receive" - case CreditGenerate: - return "generate" - case CreditImmature: - return "immature" - default: - return "unknown" - } -} - -// ToJSON returns a slice of objects that may be marshaled as a JSON array -// of JSON objects for a listtransactions RPC reply. -func (c Credit) ToJSON(account string, chainHeight int32, - net *chaincfg.Params) (btcjson.ListTransactionsResult, error) { - - c.s.mtx.RLock() - defer c.s.mtx.RUnlock() - - return c.toJSON(account, chainHeight, net) -} - -func (c Credit) toJSON(account string, chainHeight int32, - net *chaincfg.Params) (btcjson.ListTransactionsResult, error) { - - msgTx := c.Tx().MsgTx() - txout := msgTx.TxOut[c.OutputIndex] - - var address string - _, addrs, _, _ := txscript.ExtractPkScriptAddrs(txout.PkScript, net) - if len(addrs) == 1 { - address = addrs[0].EncodeAddress() - } - - result := btcjson.ListTransactionsResult{ - Account: account, - Category: c.category(chainHeight).String(), - Address: address, - Amount: btcutil.Amount(txout.Value).ToBTC(), - TxID: c.Tx().Sha().String(), - Time: c.received.Unix(), - TimeReceived: c.received.Unix(), - WalletConflicts: []string{}, - } - if c.BlockHeight != -1 { - b, err := c.s.lookupBlock(c.BlockHeight) - if err != nil { - return btcjson.ListTransactionsResult{}, err - } - - result.BlockHash = b.Hash.String() - result.BlockIndex = int64(c.Tx().Index()) - result.BlockTime = b.Time.Unix() - result.Confirmations = int64(confirms(c.BlockHeight, chainHeight)) - } - - return result, nil -} diff --git a/legacy/txstore/log.go b/legacy/txstore/log.go deleted file mode 100644 index 7a523ac23a..0000000000 --- a/legacy/txstore/log.go +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 "github.com/btcsuite/btclog" - -// log is a logger that is initialized with no output filters. This -// means the package will not perform any logging by default until the caller -// requests it. -var log btclog.Logger - -// The default amount of logging is none. -func init() { - DisableLog() -} - -// DisableLog disables all library log output. Logging output is disabled -// by default until either UseLogger or SetLogWriter are called. -func DisableLog() { - log = btclog.Disabled -} - -// UseLogger uses a specified Logger to output package logging info. -// This should be used in preference to SetLogWriter if the caller is also -// using btclog. -func UseLogger(logger btclog.Logger) { - log = logger -} - -// LogClosure is a closure that can be printed with %v to be used to -// generate expensive-to-create data for a detailed log level and avoid doing -// the work if the data isn't printed. -type logClosure func() string - -// String invokes the log closure and returns the results string. -func (c logClosure) String() string { - return c() -} - -// newLogClosure returns a new closure over the passed function which allows -// it to be used as a parameter in a logging function that is only invoked when -// the logging level is such that the message will actually be logged. -func newLogClosure(c func() string) logClosure { - return logClosure(c) -} - -// pickNoun returns the singular or plural form of a noun depending -// on the count n. -func pickNoun(n int, singular, plural string) string { - if n == 1 { - return singular - } - return plural -} diff --git a/legacy/txstore/notifications.go b/legacy/txstore/notifications.go deleted file mode 100644 index 6763868197..0000000000 --- a/legacy/txstore/notifications.go +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 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" -) - -// ErrDuplicateListen is returned for any attempts to listen for the same -// notification more than once. If callers must pass along a notifiation to -// multiple places, they must broadcast it themself. -var ErrDuplicateListen = errors.New("duplicate listen") - -type noopLocker struct{} - -func (noopLocker) Lock() {} -func (noopLocker) Unlock() {} - -func (s *Store) updateNotificationLock() { - switch { - case s.newCredit == nil: - fallthrough - case s.newDebits == nil: - fallthrough - case s.minedCredit == nil: - fallthrough - case s.minedDebits == nil: - return - } - s.notificationLock = noopLocker{} -} - -// ListenNewCredits returns a channel that passes all Credits that are newly -// added to the transaction store. The channel must be read, or other -// transaction store methods will block. -// -// If this is called twice, ErrDuplicateListen is returned. -func (s *Store) ListenNewCredits() (<-chan Credit, error) { - s.notificationLock.Lock() - defer s.notificationLock.Unlock() - - if s.newCredit != nil { - return nil, ErrDuplicateListen - } - s.newCredit = make(chan Credit) - s.updateNotificationLock() - return s.newCredit, nil -} - -// ListenNewDebits returns a channel that passes all Debits that are newly -// added to the transaction store. The channel must be read, or other -// transaction store methods will block. -// -// If this is called twice, ErrDuplicateListen is returned. -func (s *Store) ListenNewDebits() (<-chan Debits, error) { - s.notificationLock.Lock() - defer s.notificationLock.Unlock() - - if s.newDebits != nil { - return nil, ErrDuplicateListen - } - s.newDebits = make(chan Debits) - s.updateNotificationLock() - return s.newDebits, nil -} - -// ListenMinedCredits returns a channel that passes all that are moved -// from unconfirmed to a newly attached block. The channel must be read, or -// other transaction store methods will block. -// -// If this is called twice, ErrDuplicateListen is returned. -func (s *Store) ListenMinedCredits() (<-chan Credit, error) { - s.notificationLock.Lock() - defer s.notificationLock.Unlock() - - if s.minedCredit != nil { - return nil, ErrDuplicateListen - } - s.minedCredit = make(chan Credit) - s.updateNotificationLock() - return s.minedCredit, nil -} - -// ListenMinedDebits returns a channel that passes all Debits that are moved -// from unconfirmed to a newly attached block. The channel must be read, or -// other transaction store methods will block. -// -// If this is called twice, ErrDuplicateListen is returned. -func (s *Store) ListenMinedDebits() (<-chan Debits, error) { - s.notificationLock.Lock() - defer s.notificationLock.Unlock() - - if s.minedDebits != nil { - return nil, ErrDuplicateListen - } - s.minedDebits = make(chan Debits) - s.updateNotificationLock() - return s.minedDebits, nil -} - -func (s *Store) notifyNewCredit(c Credit) { - s.notificationLock.Lock() - if s.newCredit != nil { - s.newCredit <- c - } - s.notificationLock.Unlock() -} - -func (s *Store) notifyNewDebits(d Debits) { - s.notificationLock.Lock() - if s.newDebits != nil { - s.newDebits <- d - } - s.notificationLock.Unlock() -} - -func (s *Store) notifyMinedCredit(c Credit) { - s.notificationLock.Lock() - if s.minedCredit != nil { - s.minedCredit <- c - } - s.notificationLock.Unlock() -} - -func (s *Store) notifyMinedDebits(d Debits) { - s.notificationLock.Lock() - if s.minedDebits != nil { - s.minedDebits <- d - } - s.notificationLock.Unlock() -} diff --git a/legacy/txstore/serialization.go b/legacy/txstore/serialization.go deleted file mode 100644 index 1a55f70183..0000000000 --- a/legacy/txstore/serialization.go +++ /dev/null @@ -1,1220 +0,0 @@ -/* - * 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 ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "time" - - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcwallet/legacy/rename" -) - -// filename is the name of the file typically used to save a transaction -// store on disk. -const filename = "tx.bin" - -// All Store versions (both old and current). -const ( - versFirst uint32 = iota - - // versRecvTxIndex is the version where the txout index - // was added to the RecvTx struct. - versRecvTxIndex - - // versMarkSentChange is the version where serialized SentTx - // added a flags field, used for marking a sent transaction - // as change. - versMarkSentChange - - // versCombined is the version where the old utxo and tx stores - // were combined into a single data structure. - versCombined - - // versFastRewrite is the version where the combined store was - // rewritten with a focus on insertion and lookup speed. - versFastRewrite - - // versCurrent is the current tx file version. - versCurrent = versFastRewrite -) - -// byteOrder is the byte order used to read and write txstore binary data. -var byteOrder = binary.LittleEndian - -// ReadFrom satisifies the io.ReaderFrom interface by deserializing a -// transaction store from an io.Reader. -func (s *Store) ReadFrom(r io.Reader) (int64, error) { - // Don't bother locking this. The mutex gets overwritten anyways. - - var buf [4]byte - uint32Bytes := buf[:4] - - // Read current file version. - n, err := io.ReadFull(r, uint32Bytes) - n64 := int64(n) - if err != nil { - return n64, err - } - vers := byteOrder.Uint32(uint32Bytes) - - // Reading files with versions before versFastRewrite is unsupported. - if vers < versFastRewrite { - return n64, ErrUnsupportedVersion - } - - // Reset store. - *s = *New(s.path) - - // Read block structures. Begin by reading the total number of block - // structures to be read, and then iterate that many times to read - // each block. - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - blockCount := byteOrder.Uint32(uint32Bytes) - // The blocks slice is *not* preallocated to blockCount size to prevent - // accidentally allocating so much memory that the process dies. - for i := uint32(0); i < blockCount; i++ { - b := &blockTxCollection{ - txIndexes: map[int]uint32{}, - } - tmpn64, err := b.ReadFrom(r) - n64 += tmpn64 - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - s.blocks = append(s.blocks, b) - s.blockIndexes[b.Height] = i - - // Recreate store unspent map. - for blockIndex, i := range b.txIndexes { - tx := b.txs[i] - for outputIdx, cred := range tx.credits { - if cred == nil { - continue - } - if cred.spentBy == nil { - op := wire.OutPoint{ - Hash: *tx.tx.Sha(), - Index: uint32(outputIdx), - } - s.unspent[op] = BlockTxKey{ - BlockIndex: blockIndex, - BlockHeight: b.Height, - } - } - } - } - } - - // Read unconfirmed transactions and their spend tracking. - tmpn64, err := s.unconfirmed.ReadFrom(r) - n64 += tmpn64 - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - - return n64, nil -} - -// WriteTo satisifies the io.WriterTo interface by serializing a transaction -// store to an io.Writer. -func (s *Store) WriteTo(w io.Writer) (int64, error) { - s.mtx.RLock() - defer s.mtx.RUnlock() - - return s.writeTo(w) -} - -func (s *Store) writeTo(w io.Writer) (int64, error) { - var buf [4]byte - uint32Bytes := buf[:4] - - // Write current file version. - byteOrder.PutUint32(uint32Bytes, versCurrent) - n, err := w.Write(uint32Bytes) - n64 := int64(n) - if err != nil { - return n64, err - } - - // Write block structures. This begins with a uint32 specifying that - // some N blocks have been written, followed by N serialized transaction - // store blocks. - // - // The store's blockIndexes map is intentionally not written. Instead, - // it is recreated on reads after reading each block. - byteOrder.PutUint32(uint32Bytes, uint32(len(s.blocks))) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - for _, b := range s.blocks { - n, err := b.WriteTo(w) - n64 += n - if err != nil { - return n64, err - } - } - - // Write unconfirmed transactions and their spend tracking. - tmpn64, err := s.unconfirmed.WriteTo(w) - n64 += tmpn64 - if err != nil { - return n64, err - } - - // The store's unspent map is intentionally not written. Instead, it - // is recreated on reads after each block transaction collection has - // been read. This makes reads more expensive, but writing faster, and - // as writes are far more common in application use, this was deemed to - // be an acceptable tradeoff. - - return n64, nil -} - -func (b *blockTxCollection) ReadFrom(r io.Reader) (int64, error) { - var buf [8]byte - uint64Bytes := buf[:8] - uint32Bytes := buf[:4] - - // Read block hash, unix time (int64), and height (int32). - n, err := io.ReadFull(r, b.Hash[:]) - n64 := int64(n) - if err != nil { - return n64, err - } - n, err = io.ReadFull(r, uint64Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - b.Time = time.Unix(int64(byteOrder.Uint64(uint64Bytes)), 0) - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - b.Height = int32(byteOrder.Uint32(uint32Bytes)) - - // Read amount deltas as a result of transactions in this block. This - // is the net total spendable balance as a result of transaction debits - // and credits, and the block reward (not immediately spendable) for - // coinbase outputs. Both are int64s. - n, err = io.ReadFull(r, uint64Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - b.amountDeltas.Spendable = btcutil.Amount(byteOrder.Uint64(uint64Bytes)) - n, err = io.ReadFull(r, uint64Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - b.amountDeltas.Reward = btcutil.Amount(byteOrder.Uint64(uint64Bytes)) - - // Read number of transaction records (as a uint32) followed by a read - // for each expected record. - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - txCount := byteOrder.Uint32(uint32Bytes) - // The txs slice is *not* preallocated to txcount size to prevent - // accidentally allocating so much memory that the process dies. - for i := uint32(0); i < txCount; i++ { - t := &txRecord{} - tmpn64, err := t.ReadFrom(r) - n64 += tmpn64 - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - b.txs = append(b.txs, t) - - // Recreate txIndexes map. For each transaction record, map the - // block index of the underlying transaction to the slice index - // of the record. - b.txIndexes[t.tx.Index()] = i - } - - return n64, nil -} - -func (b *blockTxCollection) WriteTo(w io.Writer) (int64, error) { - var buf [8]byte - uint64Bytes := buf[:8] - uint32Bytes := buf[:4] - - // Write block hash, unix time (int64), and height (int32). - n, err := w.Write(b.Hash[:]) - n64 := int64(n) - if err != nil { - return n64, err - } - byteOrder.PutUint64(uint64Bytes, uint64(b.Time.Unix())) - n, err = w.Write(uint64Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - byteOrder.PutUint32(uint32Bytes, uint32(b.Height)) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write amount deltas as a result of transactions in this block. - // This is the net total spendable balance as a result of transaction - // debits and credits, and the block reward (not immediately spendable) - // for coinbase outputs. Both are int64s. - byteOrder.PutUint64(uint64Bytes, uint64(b.amountDeltas.Spendable)) - n, err = w.Write(uint64Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - byteOrder.PutUint64(uint64Bytes, uint64(b.amountDeltas.Reward)) - n, err = w.Write(uint64Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write number of transaction records (as a uint32) followed by each - // transaction record. - byteOrder.PutUint32(uint32Bytes, uint32(len(b.txs))) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - for _, t := range b.txs { - n, err := t.WriteTo(w) - n64 += n - if err != nil { - return n64, err - } - } - - // The block's txIndexes and unspent bookkeeping maps are intentionally - // not written. They are instead recreated on reads. This makes reads - // more expensive, but writing faster, and as writes are far more common - // in application use, this was deemed to be an acceptable tradeoff. - - return n64, nil -} - -const ( - nilPointer byte = iota - validPointer -) - -func byteMarksValidPointer(b byte) (bool, error) { - switch b { - case nilPointer: - return false, nil - case validPointer: - return true, nil - default: - s := "invalid byte representation of valid pointer" - return false, errors.New(s) - } -} - -const ( - falseByte byte = iota - trueByte -) - -func byteAsBool(b byte) (bool, error) { - switch b { - case falseByte: - return false, nil - case trueByte: - return true, nil - default: - return false, errors.New("invalid byte representation of bool") - } -} - -func (t *txRecord) ReadFrom(r io.Reader) (int64, error) { - var buf [8]byte - uint64Bytes := buf[:8] - uint32Bytes := buf[:4] - singleByte := buf[:1] - - // Read transaction index (as a uint32). - n, err := io.ReadFull(r, uint32Bytes) - n64 := int64(n) - if err != nil { - return n64, err - } - txIndex := int(byteOrder.Uint32(uint32Bytes)) - - // Deserialize transaction. - msgTx := new(msgTx) - tmpn64, err := msgTx.ReadFrom(r) - n64 += tmpn64 - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - - // Create and save the btcutil.Tx of the read MsgTx and set its index. - tx := btcutil.NewTx((*wire.MsgTx)(msgTx)) - tx.SetIndex(txIndex) - t.tx = tx - - // Read identifier for existance of debits. - n, err = io.ReadFull(r, singleByte) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - hasDebits, err := byteMarksValidPointer(singleByte[0]) - if err != nil { - return n64, err - } - - // If debits have been set, read them. Otherwise, set to nil. - if hasDebits { - // Read debited amount (int64). - n, err := io.ReadFull(r, uint64Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - amount := btcutil.Amount(byteOrder.Uint64(uint64Bytes)) - - // Read number of written outputs (as a uint32) this record - // debits from. - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - spendsCount := byteOrder.Uint32(uint32Bytes) - - // For each expected output key, allocate and read the key, - // appending the result to the spends slice. This slice is - // originally set empty (*not* preallocated to spendsCount - // size) to prevent accidentally allocating so much memory that - // the process dies. - var spends []BlockOutputKey - for i := uint32(0); i < spendsCount; i++ { - k := BlockOutputKey{} - tmpn64, err := k.ReadFrom(r) - n64 += tmpn64 - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - spends = append(spends, k) - } - - t.debits = &debits{amount, spends} - } else { - t.debits = nil - } - - // Read number of pointers (as a uint32) written to be read into the - // credits slice (although some may be nil). - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - creditsCount := byteOrder.Uint32(uint32Bytes) - - // For each expected credits slice element, check whether the credit - // exists or the pointer is nil. If nil, append nil to credits and - // continue with the next. If non-nil, allocated and read the full - // credit structure. This slice is originally set to nil (*not* - // preallocated to creditsCount size) to prevent accidentally allocating - // so much memory that the process dies. - var credits []*credit - for i := uint32(0); i < creditsCount; i++ { - // Read identifer for a valid pointer. - n, err := io.ReadFull(r, singleByte) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - validCredit, err := byteMarksValidPointer(singleByte[0]) - if err != nil { - return n64, err - } - - if !validCredit { - credits = append(credits, nil) - } else { - // Read single byte that specifies whether this credit - // was added as change. - n, err = io.ReadFull(r, singleByte) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - change, err := byteAsBool(singleByte[0]) - if err != nil { - return n64, err - } - - // Read single byte. This was previously used to - // specify whether an unspent credit was locked or not, - // but this was removed as lockedness is volatile and - // should not be saved. - // - // This space can be used for additional flags in the - // future. - n, err = io.ReadFull(r, singleByte) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - - // Read identifier for a valid pointer. - n, err = io.ReadFull(r, singleByte) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - validSpentBy, err := byteMarksValidPointer(singleByte[0]) - if err != nil { - return n64, err - } - - // If spentBy pointer is valid, allocate and read a - // transaction lookup key. - var spentBy *BlockTxKey - if validSpentBy { - spentBy = &BlockTxKey{} - tmpn64, err := spentBy.ReadFrom(r) - n64 += tmpn64 - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - } - - c := &credit{change, spentBy} - credits = append(credits, c) - } - - } - t.credits = credits - - // Read received unix time (int64). - n, err = io.ReadFull(r, uint64Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - received := int64(byteOrder.Uint64(uint64Bytes)) - t.received = time.Unix(received, 0) - - return n64, nil -} - -func (t *txRecord) WriteTo(w io.Writer) (int64, error) { - var buf [8]byte - uint64Bytes := buf[:8] - uint32Bytes := buf[:4] - - // Write transaction index (as a uint32). - byteOrder.PutUint32(uint32Bytes, uint32(t.tx.Index())) - n, err := w.Write(uint32Bytes) - n64 := int64(n) - if err != nil { - return n64, err - } - - // Serialize and write transaction. - tmpn64, err := (*msgTx)(t.tx.MsgTx()).WriteTo(w) - n64 += tmpn64 - if err != nil { - return n64, err - } - - // Write debit records, if any. This begins with a single byte to - // identify whether the record contains any debits or not. - if t.debits == nil { - // Write identifier for nil debits. - n, err = w.Write([]byte{nilPointer}) - n64 += int64(n) - if err != nil { - return n64, err - } - } else { - // Write identifier for valid debits. - n, err = w.Write([]byte{validPointer}) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write debited amount (int64). - byteOrder.PutUint64(uint64Bytes, uint64(t.debits.amount)) - n, err := w.Write(uint64Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write number of outputs (as a uint32) this record debits - // from. - byteOrder.PutUint32(uint32Bytes, uint32(len(t.debits.spends))) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write each lookup key for a spent transaction output. - for _, k := range t.debits.spends { - tmpn64, err := k.WriteTo(w) - n64 += tmpn64 - if err != nil { - return n64, err - } - } - } - - // Write number of pointers (as a uint32) in the credits slice (although - // some may be nil). Then, for each element in the credits slice, write - // an identifier whether the element is nil or valid, and if valid, - // write the credit structure. - byteOrder.PutUint32(uint32Bytes, uint32(len(t.credits))) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - for _, c := range t.credits { - if c == nil { - // Write identifier for nil credit. - n, err := w.Write([]byte{nilPointer}) - n64 += int64(n) - if err != nil { - return n64, err - } - } else { - // Write identifier for valid credit. - n, err := w.Write([]byte{validPointer}) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write a single byte to specify whether this credit - // was added as change, plus an extra empty byte which - // used to specify whether the credit was locked. This - // extra byte is currently unused and may be used for - // other flags in the future. - changeByte := falseByte - if c.change { - changeByte = trueByte - } - n, err = w.Write([]byte{changeByte, 0}) - n64 += int64(n) - if err != nil { - return n64, err - } - - // If this credit is unspent, write an identifier for - // an invalid pointer. Otherwise, write the identifier - // for a valid pointer and write the spending tx key. - if c.spentBy == nil { - // Write identifier for an unspent credit. - n, err := w.Write([]byte{nilPointer}) - n64 += int64(n) - if err != nil { - return n64, err - } - } else { - // Write identifier for an unspent credit. - n, err := w.Write([]byte{validPointer}) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write transaction lookup key. - tmpn64, err := c.spentBy.WriteTo(w) - n64 += tmpn64 - if err != nil { - return n64, err - } - } - } - } - - // Write received unix time (int64). - byteOrder.PutUint64(uint64Bytes, uint64(t.received.Unix())) - n, err = w.Write(uint64Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - return n64, nil -} - -type msgTx wire.MsgTx - -func (tx *msgTx) ReadFrom(r io.Reader) (int64, error) { - // Read from a TeeReader to return the number of read bytes. - buf := bytes.Buffer{} - tr := io.TeeReader(r, &buf) - if err := (*wire.MsgTx)(tx).Deserialize(tr); err != nil { - if buf.Len() != 0 && err == io.EOF { - err = io.ErrUnexpectedEOF - } - return int64(buf.Len()), err - } - - return int64((*wire.MsgTx)(tx).SerializeSize()), nil -} - -func (tx *msgTx) WriteTo(w io.Writer) (int64, error) { - // Write to a buffer and then copy to w so the total number of bytes - // written can be returned to the caller. Writing to a to a - // bytes.Buffer never fails except for OOM panics, so check and panic - // on any unexpected non-nil returned errors. - buf := bytes.Buffer{} - if err := (*wire.MsgTx)(tx).Serialize(&buf); err != nil { - panic(err) - } - return io.Copy(w, &buf) -} - -// ReadFrom reads a mined transaction output lookup key from r. The total -// number of bytes read is returned. -func (k *BlockOutputKey) ReadFrom(r io.Reader) (int64, error) { - var buf [4]byte - uint32Bytes := buf[:4] - - // Read embedded BlockTxKey. - n64, err := k.BlockTxKey.ReadFrom(r) - if err != nil { - return n64, err - } - - // Read output index (uint32). - n, err := io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - k.OutputIndex = byteOrder.Uint32(uint32Bytes) - - return n64, nil -} - -// WriteTo writes a mined transaction output lookup key to w. The total number -// of bytes written is returned. -func (k *BlockOutputKey) WriteTo(w io.Writer) (int64, error) { - var buf [4]byte - uint32Bytes := buf[:4] - - // Write embedded BlockTxKey. - n64, err := k.BlockTxKey.WriteTo(w) - if err != nil { - return n64, err - } - - // Write output index (uint32). - byteOrder.PutUint32(uint32Bytes, k.OutputIndex) - n, err := w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - return n64, nil -} - -// ReadFrom reads a mined transaction lookup key from r. The total number of -// bytes read is returned. -func (k *BlockTxKey) ReadFrom(r io.Reader) (int64, error) { - var buf [4]byte - uint32Bytes := buf[:4] - - // Read block index (as a uint32). - n, err := io.ReadFull(r, uint32Bytes) - n64 := int64(n) - if err != nil { - return n64, err - } - k.BlockIndex = int(byteOrder.Uint32(uint32Bytes)) - - // Read block height (int32). - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - k.BlockHeight = int32(byteOrder.Uint32(uint32Bytes)) - - return n64, nil -} - -// WriteTo writes a mined transaction lookup key to w. The total number of -// bytes written is returned. -func (k *BlockTxKey) WriteTo(w io.Writer) (int64, error) { - var buf [4]byte - uint32Bytes := buf[:4] - - // Write block index (as a uint32). - byteOrder.PutUint32(uint32Bytes, uint32(k.BlockIndex)) - n, err := w.Write(uint32Bytes) - n64 := int64(n) - if err != nil { - return n64, err - } - - // Write block height (int32). - byteOrder.PutUint32(uint32Bytes, uint32(k.BlockHeight)) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - return n64, nil -} - -func (u *unconfirmedStore) ReadFrom(r io.Reader) (int64, error) { - var buf [4]byte - uint32Bytes := buf[:4] - - // Read length (as a uint32) of transaction record key/value pairs, - // followed by each transaction record. - n, err := io.ReadFull(r, uint32Bytes) - n64 := int64(n) - if err != nil { - return n64, err - } - txCount := byteOrder.Uint32(uint32Bytes) - for i := uint32(0); i < txCount; i++ { - t := &txRecord{} - tmpn64, err := t.ReadFrom(r) - n64 += tmpn64 - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - - u.txs[*t.tx.Sha()] = t - } - - // Read length (as a uint32) of key/value pairs in the - // spentBlockOutPoints and spentBlockOutPointKeys maps, followed by the - // outpoint, the block transaction lookup key, and the transaction hash - // of the spending transaction record. - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - spentBlockOutPointCount := byteOrder.Uint32(uint32Bytes) - for i := uint32(0); i < spentBlockOutPointCount; i++ { - // Read outpoint hash and index (uint32). - op := wire.OutPoint{} - n, err := io.ReadFull(r, op.Hash[:]) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - op.Index = byteOrder.Uint32(uint32Bytes) - - // Read block transaction lookup key, and create the full block - // output key from it and the previously-read outpoint index. - opKey := BlockOutputKey{OutputIndex: op.Index} - tmpn64, err := opKey.BlockTxKey.ReadFrom(r) - n64 += tmpn64 - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - - // Read transaction record hash and check that it was previously - // read into the txs map. Use full record as the map value. - var txHash wire.ShaHash - n, err = io.ReadFull(r, txHash[:]) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - t, ok := u.txs[txHash] - if !ok { - return n64, fmt.Errorf("missing unconfirmed "+ - "transaction record for transaction %v", txHash) - } - - u.spentBlockOutPoints[opKey] = t - u.spentBlockOutPointKeys[op] = opKey - } - - // Read length (as a uint32) of key/value pairs in the spentUnconfirmed - // map, followed by the outpoint and hash of the transaction record. - // Use this hash as the lookup key for the full transaction record - // previously read into the txs map. - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - spentUnconfirmedCount := byteOrder.Uint32(uint32Bytes) - for i := uint32(0); i < spentUnconfirmedCount; i++ { - // Read outpoint hash and index (uint32). - op := wire.OutPoint{} - n, err := io.ReadFull(r, op.Hash[:]) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - n, err = io.ReadFull(r, uint32Bytes) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - op.Index = byteOrder.Uint32(uint32Bytes) - - // Read transaction record hash and check that it was previously - // read into the txs map. Use full record as the map value. - var txHash wire.ShaHash - n, err = io.ReadFull(r, txHash[:]) - n64 += int64(n) - if err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - return n64, err - } - t, ok := u.txs[txHash] - if !ok { - return n64, fmt.Errorf("missing unconfirmed "+ - "transaction record for transaction %v", txHash) - } - - u.spentUnconfirmed[op] = t - } - - // Recreate the previousOutpoints map. For each transaction record - // saved in the txs map, map each previous outpoint to the record - // itself. - for _, t := range u.txs { - for _, input := range t.tx.MsgTx().TxIn { - u.previousOutpoints[input.PreviousOutPoint] = t - } - } - - return n64, nil -} - -func (u *unconfirmedStore) WriteTo(w io.Writer) (int64, error) { - var buf [4]byte - uint32Bytes := buf[:4] - - // Write length of key/values pairs in txs map, followed by each - // transaction record. - byteOrder.PutUint32(uint32Bytes, uint32(len(u.txs))) - n, err := w.Write(uint32Bytes) - n64 := int64(n) - if err != nil { - return n64, err - } - for _, t := range u.txs { - tmpn64, err := t.WriteTo(w) - n64 += tmpn64 - if err != nil { - return n64, err - } - } - - // Write length (as a uint32) of key/value pairs in the - // spentBlockOutPoints and spentBlockOutPointKeys maps (these lengths - // must be equal), followed by the outpoint, the block transaction - // lookup key, and the hash of the transaction record. - if len(u.spentBlockOutPoints) != len(u.spentBlockOutPointKeys) { - return n64, errors.New("spent block tx maps lengths differ") - } - byteOrder.PutUint32(uint32Bytes, uint32(len(u.spentBlockOutPoints))) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - for op, opKey := range u.spentBlockOutPointKeys { - // Write outpoint hash and the index (uint32). - n, err := w.Write(op.Hash[:]) - n64 += int64(n) - if err != nil { - return n64, err - } - byteOrder.PutUint32(uint32Bytes, op.Index) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write the block transaction lookup key. This is not the full - // output key, as the index has already been serialized as part - // of the outpoint written above. - tmpn64, err := opKey.BlockTxKey.WriteTo(w) - n64 += tmpn64 - if err != nil { - return n64, err - } - - // Lookup transaction record and write the transaction hash. - t, ok := u.spentBlockOutPoints[opKey] - if !ok { - return n64, MissingCreditError(opKey) - } - n, err = w.Write(t.tx.Sha()[:]) - n64 += int64(n) - if err != nil { - return n64, err - } - } - - // Write length (as a uint32) of key/value pairs in the spentUnconfirmed - // map, followed by the outpoint and hash of the transaction record. - byteOrder.PutUint32(uint32Bytes, uint32(len(u.spentUnconfirmed))) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - for op, t := range u.spentUnconfirmed { - // Write outpoint hash and the index (uint32). - n, err := w.Write(op.Hash[:]) - n64 += int64(n) - if err != nil { - return n64, err - } - byteOrder.PutUint32(uint32Bytes, op.Index) - n, err = w.Write(uint32Bytes) - n64 += int64(n) - if err != nil { - return n64, err - } - - // Write transaction record hash. - n, err = w.Write(t.tx.Sha()[:]) - n64 += int64(n) - if err != nil { - return n64, err - } - } - - // The previousOutpoints map is intentionally not written, as it can - // be fully recreated by iterating each transaction record and adding - // a key/value pair for each prevous outpoint. This is performed when - // reading the unconfirmed store. This makes reads slightly more - // expensive, but writing faster, and as writes are far more common in - // application use, this was deemed to be an acceptable tradeoff. - - return n64, nil -} - -// MarkDirty marks that changes have been made to the transaction store. -// This should be run after any modifications are performed to the store -// or any of its records. -func (s *Store) MarkDirty() { - s.mtx.Lock() - defer s.mtx.Unlock() - - s.dirty = true -} - -// WriteIfDirty writes the entire transaction store to permanent storage if -// the dirty flag has been set (see MarkDirty). -func (s *Store) WriteIfDirty() error { - s.mtx.RLock() - if !s.dirty { - s.mtx.RUnlock() - return nil - } - - // TempFile creates the file 0600, so no need to chmod it. - fi, err := ioutil.TempFile(s.dir, s.file) - if err != nil { - s.mtx.RUnlock() - return err - } - fiPath := fi.Name() - - _, err = s.writeTo(fi) - if err != nil { - s.mtx.RUnlock() - fi.Close() - return err - } - err = fi.Sync() - if err != nil { - s.mtx.RUnlock() - fi.Close() - return err - } - fi.Close() - - err = rename.Atomic(fiPath, s.path) - s.mtx.RUnlock() - if err == nil { - s.mtx.Lock() - s.dirty = false - s.mtx.Unlock() - } - - return err -} - -// OpenDir opens a new transaction store from the specified directory. -// If the file does not exist, the error from the os package will be -// returned, and can be checked with os.IsNotExist to differentiate missing -// file errors from others (including deserialization). -func OpenDir(dir string) (*Store, error) { - path := filepath.Join(dir, filename) - fi, err := os.OpenFile(path, os.O_RDONLY, 0) - if err != nil { - return nil, err - } - defer fi.Close() - store := new(Store) - _, err = store.ReadFrom(fi) - if err != nil { - return nil, err - } - store.path = path - store.dir = dir - return store, nil -} diff --git a/legacy/txstore/tx.go b/legacy/txstore/tx.go deleted file mode 100644 index 344c088791..0000000000 --- a/legacy/txstore/tx.go +++ /dev/null @@ -1,1525 +0,0 @@ -/* - * 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/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 -} diff --git a/legacy/txstore/tx_test.go b/legacy/txstore/tx_test.go deleted file mode 100644 index f1fbff1860..0000000000 --- a/legacy/txstore/tx_test.go +++ /dev/null @@ -1,596 +0,0 @@ -// 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_test - -import ( - "bytes" - "encoding/hex" - "testing" - "time" - - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - . "github.com/btcsuite/btcwallet/legacy/txstore" -) - -// Received transaction output for mainnet outpoint -// 61d3696de4c888730cbe06b0ad8ecb6d72d6108e893895aa9bc067bd7eba3fad:0 -var ( - TstRecvSerializedTx, _ = hex.DecodeString("010000000114d9ff358894c486b4ae11c2a8cf7851b1df64c53d2e511278eff17c22fb7373000000008c493046022100995447baec31ee9f6d4ec0e05cb2a44f6b817a99d5f6de167d1c75354a946410022100c9ffc23b64d770b0e01e7ff4d25fbc2f1ca8091053078a247905c39fce3760b601410458b8e267add3c1e374cf40f1de02b59213a82e1d84c2b94096e22e2f09387009c96debe1d0bcb2356ffdcf65d2a83d4b34e72c62eccd8490dbf2110167783b2bffffffff0280969800000000001976a914479ed307831d0ac19ebc5f63de7d5f1a430ddb9d88ac38bfaa00000000001976a914dadf9e3484f28b385ddeaa6c575c0c0d18e9788a88ac00000000") - TstRecvTx, _ = btcutil.NewTxFromBytes(TstRecvSerializedTx) - TstRecvTxSpendingTxBlockHash, _ = wire.NewShaHashFromStr("00000000000000017188b968a371bab95aa43522665353b646e41865abae02a4") - TstRecvAmt = int64(10000000) - TstRecvIndex = 684 - TstRecvTxBlockDetails = &Block{ - Height: 276425, - Hash: *TstRecvTxSpendingTxBlockHash, - Time: time.Unix(1387737310, 0), - } - - TstRecvCurrentHeight = int32(284498) // mainnet blockchain height at time of writing - TstRecvTxOutConfirms = 8074 // hardcoded number of confirmations given the above block height - - TstSpendingSerializedTx, _ = hex.DecodeString("0100000003ad3fba7ebd67c09baa9538898e10d6726dcb8eadb006be0c7388c8e46d69d361000000006b4830450220702c4fbde5532575fed44f8d6e8c3432a2a9bd8cff2f966c3a79b2245a7c88db02210095d6505a57e350720cb52b89a9b56243c15ddfcea0596aedc1ba55d9fb7d5aa0012103cccb5c48a699d3efcca6dae277fee6b82e0229ed754b742659c3acdfed2651f9ffffffffdbd36173f5610e34de5c00ed092174603761595d90190f790e79cda3e5b45bc2010000006b483045022000fa20735e5875e64d05bed43d81b867f3bd8745008d3ff4331ef1617eac7c44022100ad82261fc57faac67fc482a37b6bf18158da0971e300abf5fe2f9fd39e107f58012102d4e1caf3e022757512c204bf09ff56a9981df483aba3c74bb60d3612077c9206ffffffff65536c9d964b6f89b8ef17e83c6666641bc495cb27bab60052f76cd4556ccd0d040000006a473044022068e3886e0299ffa69a1c3ee40f8b6700f5f6d463a9cf9dbf22c055a131fc4abc02202b58957fe19ff1be7a84c458d08016c53fbddec7184ac5e633f2b282ae3420ae012103b4e411b81d32a69fb81178a8ea1abaa12f613336923ee920ffbb1b313af1f4d2ffffffff02ab233200000000001976a91418808b2fbd8d2c6d022aed5cd61f0ce6c0a4cbb688ac4741f011000000001976a914f081088a300c80ce36b717a9914ab5ec8a7d283988ac00000000") - TstSpendingTx, _ = btcutil.NewTxFromBytes(TstSpendingSerializedTx) - TstSpendingTxBlockHeight = int32(279143) - TstSignedTxBlockHash, _ = wire.NewShaHashFromStr("00000000000000017188b968a371bab95aa43522665353b646e41865abae02a4") - TstSignedTxIndex = 123 - TstSignedTxBlockDetails = &Block{ - Height: TstSpendingTxBlockHeight, - Hash: *TstSignedTxBlockHash, - Time: time.Unix(1389114091, 0), - } -) - -func TestInsertsCreditsDebitsRollbacks(t *testing.T) { - // Create a double spend of the received blockchain transaction. - dupRecvTx, _ := btcutil.NewTxFromBytes(TstRecvSerializedTx) - // Switch txout amount to 1 BTC. Transaction store doesn't - // validate txs, so this is fine for testing a double spend - // removal. - TstDupRecvAmount := int64(1e8) - newDupMsgTx := dupRecvTx.MsgTx() - newDupMsgTx.TxOut[0].Value = TstDupRecvAmount - TstDoubleSpendTx := btcutil.NewTx(newDupMsgTx) - - // Create a "signed" (with invalid sigs) tx that spends output 0 of - // the double spend. - spendingTx := wire.NewMsgTx() - spendingTxIn := wire.NewTxIn(wire.NewOutPoint(TstDoubleSpendTx.Sha(), 0), []byte{0, 1, 2, 3, 4}) - spendingTx.AddTxIn(spendingTxIn) - spendingTxOut1 := wire.NewTxOut(1e7, []byte{5, 6, 7, 8, 9}) - spendingTxOut2 := wire.NewTxOut(9e7, []byte{10, 11, 12, 13, 14}) - spendingTx.AddTxOut(spendingTxOut1) - spendingTx.AddTxOut(spendingTxOut2) - TstSpendingTx := btcutil.NewTx(spendingTx) - var _ = TstSpendingTx - - tests := []struct { - name string - f func(*Store) (*Store, error) - bal, unc btcutil.Amount - unspents map[wire.OutPoint]struct{} - unmined map[wire.ShaHash]struct{} - }{ - { - name: "new store", - f: func(_ *Store) (*Store, error) { - return New("/tmp/tx.bin"), nil - }, - bal: 0, - unc: 0, - unspents: map[wire.OutPoint]struct{}{}, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "txout insert", - f: func(s *Store) (*Store, error) { - r, err := s.InsertTx(TstRecvTx, nil) - if err != nil { - return nil, err - } - - _, err = r.AddCredit(0, false) - if err != nil { - return nil, err - } - // Verify that we can create the JSON output without any - // errors. - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstRecvTx.Sha(), 0): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "insert duplicate unconfirmed", - f: func(s *Store) (*Store, error) { - r, err := s.InsertTx(TstRecvTx, nil) - if err != nil { - return nil, err - } - - _, err = r.AddCredit(0, false) - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstRecvTx.Sha(), 0): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "confirmed txout insert", - f: func(s *Store) (*Store, error) { - TstRecvTx.SetIndex(TstRecvIndex) - r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails) - if err != nil { - return nil, err - } - - _, err = r.AddCredit(0, false) - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), - unc: 0, - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstRecvTx.Sha(), 0): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "insert duplicate confirmed", - f: func(s *Store) (*Store, error) { - TstRecvTx.SetIndex(TstRecvIndex) - r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails) - if err != nil { - return nil, err - } - - _, err = r.AddCredit(0, false) - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), - unc: 0, - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstRecvTx.Sha(), 0): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "rollback confirmed credit", - f: func(s *Store) (*Store, error) { - err := s.Rollback(TstRecvTxBlockDetails.Height) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstRecvTx.Sha(), 0): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "insert confirmed double spend", - f: func(s *Store) (*Store, error) { - TstDoubleSpendTx.SetIndex(TstRecvIndex) - r, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails) - if err != nil { - return nil, err - } - - _, err = r.AddCredit(0, false) - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - - if err != nil { - return nil, err - } - return s, nil - }, - bal: btcutil.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value), - unc: 0, - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstDoubleSpendTx.Sha(), 0): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "insert unconfirmed debit", - f: func(s *Store) (*Store, error) { - _, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails) - if err != nil { - return nil, err - } - - r, err := s.InsertTx(TstSpendingTx, nil) - if err != nil { - return nil, err - } - - _, err = r.AddDebits() - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: 0, - unspents: map[wire.OutPoint]struct{}{}, - unmined: map[wire.ShaHash]struct{}{ - *TstSpendingTx.Sha(): {}, - }, - }, - { - name: "insert unconfirmed debit again", - f: func(s *Store) (*Store, error) { - _, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails) - if err != nil { - return nil, err - } - - r, err := s.InsertTx(TstSpendingTx, nil) - if err != nil { - return nil, err - } - - _, err = r.AddDebits() - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: 0, - unspents: map[wire.OutPoint]struct{}{}, - unmined: map[wire.ShaHash]struct{}{ - *TstSpendingTx.Sha(): {}, - }, - }, - { - name: "insert change (index 0)", - f: func(s *Store) (*Store, error) { - r, err := s.InsertTx(TstSpendingTx, nil) - if err != nil { - return nil, err - } - - _, err = r.AddCredit(0, true) - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value), - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstSpendingTx.Sha(), 0): {}, - }, - unmined: map[wire.ShaHash]struct{}{ - *TstSpendingTx.Sha(): {}, - }, - }, - { - name: "insert output back to this own wallet (index 1)", - f: func(s *Store) (*Store, error) { - r, err := s.InsertTx(TstSpendingTx, nil) - if err != nil { - return nil, err - } - - _, err = r.AddCredit(1, true) - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstSpendingTx.Sha(), 0): {}, - *wire.NewOutPoint(TstSpendingTx.Sha(), 1): {}, - }, - unmined: map[wire.ShaHash]struct{}{ - *TstSpendingTx.Sha(): {}, - }, - }, - { - name: "confirm signed tx", - f: func(s *Store) (*Store, error) { - TstSpendingTx.SetIndex(TstSignedTxIndex) - r, err := s.InsertTx(TstSpendingTx, TstSignedTxBlockDetails) - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), - unc: 0, - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstSpendingTx.Sha(), 0): {}, - *wire.NewOutPoint(TstSpendingTx.Sha(), 1): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "rollback after spending tx", - f: func(s *Store) (*Store, error) { - err := s.Rollback(TstSignedTxBlockDetails.Height + 1) - if err != nil { - return nil, err - } - return s, nil - }, - bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), - unc: 0, - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstSpendingTx.Sha(), 0): {}, - *wire.NewOutPoint(TstSpendingTx.Sha(), 1): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - { - name: "rollback spending tx block", - f: func(s *Store) (*Store, error) { - err := s.Rollback(TstSignedTxBlockDetails.Height) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstSpendingTx.Sha(), 0): {}, - *wire.NewOutPoint(TstSpendingTx.Sha(), 1): {}, - }, - unmined: map[wire.ShaHash]struct{}{ - *TstSpendingTx.Sha(): {}, - }, - }, - { - name: "rollback double spend tx block", - f: func(s *Store) (*Store, error) { - err := s.Rollback(TstRecvTxBlockDetails.Height) - if err != nil { - return nil, err - } - return s, nil - }, - bal: 0, - unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value), - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstSpendingTx.Sha(), 0): {}, - *wire.NewOutPoint(TstSpendingTx.Sha(), 1): {}, - }, - unmined: map[wire.ShaHash]struct{}{ - *TstSpendingTx.Sha(): {}, - }, - }, - { - name: "insert original recv txout", - f: func(s *Store) (*Store, error) { - TstRecvTx.SetIndex(TstRecvIndex) - r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails) - if err != nil { - return nil, err - } - - _, err = r.AddCredit(0, false) - if err != nil { - return nil, err - } - - _, err = r.ToJSON("", 100, &chaincfg.MainNetParams) - if err != nil { - return nil, err - } - return s, nil - }, - bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value), - unc: 0, - unspents: map[wire.OutPoint]struct{}{ - *wire.NewOutPoint(TstRecvTx.Sha(), 0): {}, - }, - unmined: map[wire.ShaHash]struct{}{}, - }, - } - - var s *Store - for _, test := range tests { - tmpStore, err := test.f(s) - if err != nil { - t.Fatalf("%s: got error: %v", test.name, err) - } - s = tmpStore - bal, err := s.Balance(1, TstRecvCurrentHeight) - if err != nil { - t.Fatalf("%s: Confirmed Balance() failed: %v", test.name, err) - } - if bal != test.bal { - t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal) - } - unc, err := s.Balance(0, TstRecvCurrentHeight) - if err != nil { - t.Fatalf("%s: Unconfirmed Balance() failed: %v", test.name, err) - } - unc -= bal - if unc != test.unc { - t.Errorf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc) - } - - // Check that unspent outputs match expected. - unspent, err := s.UnspentOutputs() - if err != nil { - t.Fatal(err) - } - for _, r := range unspent { - if r.Spent() { - t.Errorf("%s: unspent record marked as spent", test.name) - } - - op := *r.OutPoint() - if _, ok := test.unspents[op]; !ok { - t.Errorf("%s: unexpected unspent output: %v", test.name, op) - } - delete(test.unspents, op) - } - if len(test.unspents) != 0 { - t.Errorf("%s: missing expected unspent output(s)", test.name) - } - - // Check that unmined sent txs match expected. - for _, tx := range s.UnminedDebitTxs() { - if _, ok := test.unmined[*tx.Sha()]; !ok { - t.Fatalf("%s: unexpected unmined signed tx: %v", test.name, *tx.Sha()) - } - delete(test.unmined, *tx.Sha()) - } - if len(test.unmined) != 0 { - t.Errorf("%s: missing expected unmined signed tx(s)", test.name) - } - - // Pass a re-serialized version of the store to each next test. - buf := new(bytes.Buffer) - nWritten, err := s.WriteTo(buf) - if err != nil { - t.Fatalf("%v: serialization failed: %v (wrote %v bytes)", test.name, err, nWritten) - } - if nWritten != int64(buf.Len()) { - t.Errorf("%v: wrote %v bytes but buffer has %v", test.name, nWritten, buf.Len()) - } - nRead, err := s.ReadFrom(buf) - if err != nil { - t.Fatalf("%v: deserialization failed: %v (read %v bytes after writing %v)", - test.name, err, nRead, nWritten) - } - if nWritten != nRead { - t.Errorf("%v: number of bytes written (%v) does not match those read (%v)", - test.name, nWritten, nRead) - } - } -} - -func TestFindingSpentCredits(t *testing.T) { - s := New("/tmp/tx.bin") - - // Insert transaction and credit which will be spent. - r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails) - if err != nil { - t.Fatal(err) - } - _, err = r.AddCredit(0, false) - if err != nil { - t.Fatal(err) - } - - // Insert confirmed transaction which spends the above credit. - TstSpendingTx.SetIndex(TstSignedTxIndex) - r2, err := s.InsertTx(TstSpendingTx, TstSignedTxBlockDetails) - if err != nil { - t.Fatal(err) - } - _, err = r2.AddCredit(0, false) - if err != nil { - t.Fatal(err) - } - _, err = r2.AddDebits() - if err != nil { - t.Fatal(err) - } - - bal, err := s.Balance(1, TstSignedTxBlockDetails.Height) - if err != nil { - t.Fatal(err) - } - if bal != btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value) { - t.Fatal("bad balance") - } - unspents, err := s.UnspentOutputs() - if err != nil { - t.Fatal(err) - } - op := wire.NewOutPoint(TstSpendingTx.Sha(), 0) - if *unspents[0].OutPoint() != *op { - t.Fatal("unspent outpoint doesn't match expected") - } - if len(unspents) > 1 { - t.Fatal("has more than one unspent credit") - } -}