Remove legacy txstore.
This commit is contained in:
parent
c012cdbd50
commit
09c391cc38
8 changed files with 0 additions and 3895 deletions
|
@ -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
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
1525
legacy/txstore/tx.go
1525
legacy/txstore/tx.go
File diff suppressed because it is too large
Load diff
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue