Remove legacy txstore.

This commit is contained in:
Josh Rickmar 2015-04-16 16:53:02 -04:00
parent c012cdbd50
commit 09c391cc38
8 changed files with 0 additions and 3895 deletions

View file

@ -1,102 +0,0 @@
/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Package txstore 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

View file

@ -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
}

View file

@ -1,198 +0,0 @@
/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package txstore
import (
"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
}

View file

@ -1,68 +0,0 @@
/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package txstore
import "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
}

View file

@ -1,145 +0,0 @@
/*
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package txstore
import (
"errors"
)
// 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()
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,596 +0,0 @@
// Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package txstore_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")
}
}