Add support for listtransactions RPC request.

This change adds support for the listtransactions RPC command.  To
properly reply to this command, additonal information about received
transactions was added, and is now saved in an account's tx.bin file.
Additionally, when sending a transaction, a *tx.SendTx is now saved to
the Tx store, and is included in listtransactions replies under the
"send" category.

WARNING: All account's tx.bin and utxo.bin files should be removed
before running with this change, or else the files may not be read
correctly.  Removing tx.bin is not an issue as it was not being used
before, and was being saved with incorrect data.  Removing utxo.bin is
not an issue as it will just trigger a rescan on next start.  File
format versions are now included in both files, so automatic updates
from previous file formats will be possible with future changes.

Fixes #12.
This commit is contained in:
Josh Rickmar 2013-11-22 13:42:25 -05:00
parent a246fc91d6
commit 413f23ea18
7 changed files with 794 additions and 273 deletions

View file

@ -27,6 +27,7 @@ import (
"github.com/conformal/btcwire"
"github.com/conformal/btcws"
"sync"
"time"
)
var accounts = NewAccountStore()
@ -451,36 +452,44 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo
}
return false
}
sender, ok := v["sender"].(string)
if !ok {
log.Error("Tx Handler: Unspecified sender.")
return false
}
receiver, ok := v["receiver"].(string)
if !ok {
log.Error("Tx Handler: Unspecified receiver.")
return false
}
blockhashBE, ok := v["blockhash"].(string)
if !ok {
log.Error("Tx Handler: Unspecified block hash.")
return false
}
height, ok := v["height"].(float64)
if !ok {
log.Error("Tx Handler: Unspecified height.")
return false
}
txhashBE, ok := v["txhash"].(string)
blockHashBE, ok := v["blockhash"].(string)
if !ok {
log.Error("Tx Handler: Unspecified block hash.")
return false
}
fblockIndex, ok := v["blockindex"].(float64)
if !ok {
log.Error("Tx Handler: Unspecified block index.")
return false
}
blockIndex := int32(fblockIndex)
fblockTime, ok := v["blocktime"].(float64)
if !ok {
log.Error("Tx Handler: Unspecified block time.")
return false
}
blockTime := int64(fblockTime)
txhashBE, ok := v["txid"].(string)
if !ok {
log.Error("Tx Handler: Unspecified transaction hash.")
return false
}
index, ok := v["index"].(float64)
ftxOutIndex, ok := v["txoutindex"].(float64)
if !ok {
log.Error("Tx Handler: Unspecified transaction index.")
log.Error("Tx Handler: Unspecified transaction output index.")
return false
}
txOutIndex := int32(ftxOutIndex)
amt, ok := v["amount"].(float64)
if !ok {
log.Error("Tx Handler: Unspecified amount.")
@ -499,18 +508,16 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo
// btcd sends the block and tx hashes as BE strings. Convert both
// to a LE ShaHash.
blockhash, err := btcwire.NewShaHashFromStr(blockhashBE)
blockHash, err := btcwire.NewShaHashFromStr(blockHashBE)
if err != nil {
log.Errorf("Tx Handler: Block hash string cannot be parsed: %v", err)
return false
}
txhash, err := btcwire.NewShaHashFromStr(txhashBE)
txID, err := btcwire.NewShaHashFromStr(txhashBE)
if err != nil {
log.Errorf("Tx Handler: Tx hash string cannot be parsed: %v", err)
return false
}
// TODO(jrick): btcd does not find the sender yet.
senderHash, _, _ := btcutil.DecodeAddress(sender)
receiverHash, _, err := btcutil.DecodeAddress(receiver)
if err != nil {
log.Errorf("Tx Handler: receiver address can not be decoded: %v", err)
@ -519,12 +526,15 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo
// Add to TxStore.
t := &tx.RecvTx{
Amt: uint64(amt),
TxID: *txID,
TimeReceived: time.Now().Unix(),
BlockHeight: int32(height),
BlockHash: *blockHash,
BlockIndex: blockIndex,
BlockTime: blockTime,
Amount: int64(amt),
ReceiverHash: receiverHash,
}
copy(t.TxHash[:], txhash[:])
copy(t.BlockHash[:], blockhash[:])
copy(t.SenderAddr[:], senderHash)
copy(t.ReceiverAddr[:], receiverHash)
a.TxStore.Lock()
txs := a.TxStore.s
@ -532,14 +542,13 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo
a.TxStore.dirty = true
a.TxStore.Unlock()
// Add to UtxoStore if unspent.
if !spent {
// First, iterate through all stored utxos. If an unconfirmed utxo
// (not present in a block) has the same outpoint as this utxo,
// update the block height and hash.
a.UtxoStore.RLock()
for _, u := range a.UtxoStore.s {
if bytes.Equal(u.Out.Hash[:], txhash[:]) && u.Out.Index == uint32(index) {
if bytes.Equal(u.Out.Hash[:], txID[:]) && u.Out.Index == uint32(txOutIndex) {
// Found a either a duplicate, or a change UTXO. If not change,
// ignore it.
a.UtxoStore.RUnlock()
@ -548,7 +557,7 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo
}
a.UtxoStore.Lock()
copy(u.BlockHash[:], blockhash[:])
copy(u.BlockHash[:], blockHash[:])
u.Height = int32(height)
a.UtxoStore.dirty = true
a.UtxoStore.Unlock()
@ -566,10 +575,10 @@ func (a *Account) newBlockTxOutHandler(result interface{}, e *btcjson.Error) boo
Height: int32(height),
Subscript: pkscript,
}
copy(u.Out.Hash[:], txhash[:])
u.Out.Index = uint32(index)
copy(u.Out.Hash[:], txID[:])
u.Out.Index = uint32(txOutIndex)
copy(u.AddrHash[:], receiverHash)
copy(u.BlockHash[:], blockhash[:])
copy(u.BlockHash[:], blockHash[:])
a.UtxoStore.Lock()
a.UtxoStore.s = append(a.UtxoStore.s, u)
a.UtxoStore.dirty = true

42
cmd.go
View file

@ -103,6 +103,8 @@ func checkCreateAccountDir(path string) error {
// Wallets opened from this function are not set to track against a
// btcd connection.
func OpenAccount(cfg *config, account string) (*Account, error) {
var finalErr error
adir := accountdir(cfg, account)
if err := checkCreateAccountDir(adir); err != nil {
return nil, err
@ -134,6 +136,25 @@ func OpenAccount(cfg *config, account string) (*Account, error) {
name: account,
}
// Read tx file. If this fails, return a ErrNoTxs error and let
// the caller decide if a rescan is necessary.
if txfile, err = os.Open(txfilepath); err != nil {
log.Errorf("cannot open tx file: %s", err)
// This is not a error we should immediately return with,
// but other errors can be more important, so only return
// this if none of the others are hit.
finalErr = ErrNoTxs
} else {
defer txfile.Close()
var txs tx.TxStore
if _, err = txs.ReadFrom(txfile); err != nil {
log.Errorf("cannot read tx file: %s", err)
finalErr = ErrNoTxs
} else {
a.TxStore.s = txs
}
}
// Read utxo file. If this fails, return a ErrNoUtxos error so a
// rescan can be done since the wallet creation block.
var utxos tx.UtxoStore
@ -144,25 +165,12 @@ func OpenAccount(cfg *config, account string) (*Account, error) {
defer utxofile.Close()
if _, err = utxos.ReadFrom(utxofile); err != nil {
log.Errorf("cannot read utxo file: %s", err)
return a, ErrNoUtxos
finalErr = ErrNoUtxos
} else {
a.UtxoStore.s = utxos
}
a.UtxoStore.s = utxos
// Read tx file. If this fails, return a ErrNoTxs error and let
// the caller decide if a rescan is necessary.
if txfile, err = os.Open(txfilepath); err != nil {
log.Errorf("cannot open tx file: %s", err)
return a, ErrNoTxs
}
defer txfile.Close()
var txs tx.TxStore
if _, err = txs.ReadFrom(txfile); err != nil {
log.Errorf("cannot read tx file: %s", err)
return a, ErrNoTxs
}
a.TxStore.s = txs
return a, nil
return a, finalErr
}
// GetCurBlock returns the blockchain height and SHA hash of the most

126
cmdmgr.go
View file

@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"github.com/conformal/btcjson"
"github.com/conformal/btcwallet/tx"
"github.com/conformal/btcwallet/wallet"
"github.com/conformal/btcwire"
"github.com/conformal/btcws"
@ -45,6 +46,7 @@ var rpcHandlers = map[string]cmdHandler{
"getnewaddress": GetNewAddress,
"importprivkey": ImportPrivKey,
"listaccounts": ListAccounts,
"listtransactions": ListTransactions,
"sendfrom": SendFrom,
"sendmany": SendMany,
"settxfee": SetTxFee,
@ -458,6 +460,59 @@ func ListAccounts(frontend chan []byte, icmd btcjson.Cmd) {
ReplySuccess(frontend, cmd.Id(), pairs)
}
// ListTransactions replies to a listtransactions request by returning a
// JSON object with details of sent and recevied wallet transactions.
func ListTransactions(frontend chan []byte, icmd btcjson.Cmd) {
// Type assert icmd to access parameters.
cmd, ok := icmd.(*btcjson.ListTransactionsCmd)
if !ok {
ReplyError(frontend, icmd.Id(), &btcjson.ErrInternal)
return
}
// Check that the account specified in the request exists.
a, ok := accounts.m[cmd.Account]
if !ok {
ReplyError(frontend, cmd.Id(),
&btcjson.ErrWalletInvalidAccountName)
return
}
// Get current block. The block height used for calculating
// the number of tx confirmations.
bs, err := GetCurBlock()
if err != nil {
e := &btcjson.Error{
Code: btcjson.ErrInternal.Code,
Message: err.Error(),
}
ReplyError(frontend, cmd.Id(), e)
return
}
a.mtx.RLock()
a.TxStore.RLock()
var txInfoList []map[string]interface{}
lastLookupIdx := len(a.TxStore.s) - cmd.Count
// Search in reverse order: lookup most recently-added first.
for i := len(a.TxStore.s) - 1; i >= cmd.From && i >= lastLookupIdx; i-- {
switch e := a.TxStore.s[i].(type) {
case *tx.SendTx:
infos := e.TxInfo(a.Name(), bs.Height, a.Net())
txInfoList = append(txInfoList, infos...)
case *tx.RecvTx:
info := e.TxInfo(a.Name(), bs.Height, a.Net())
txInfoList = append(txInfoList, info)
}
}
a.mtx.RUnlock()
a.TxStore.RUnlock()
// Reply with the list of tx information.
ReplySuccess(frontend, cmd.Id(), txInfoList)
}
// SendFrom creates a new transaction spending unspent transaction
// outputs for a wallet to another payment address. Leftover inputs
// not sent to the payment address or a fee for the miner are sent
@ -668,41 +723,70 @@ func SendMany(frontend chan []byte, icmd btcjson.Cmd) {
}
func handleSendRawTxReply(frontend chan []byte, icmd btcjson.Cmd,
result interface{}, err *btcjson.Error, a *Account,
result interface{}, e *btcjson.Error, a *Account,
txInfo *CreatedTx) bool {
if err != nil {
ReplyError(frontend, icmd.Id(), err)
if e != nil {
ReplyError(frontend, icmd.Id(), e)
return true
}
txIDStr, ok := result.(string)
if !ok {
e := &btcjson.Error{
Code: btcjson.ErrInternal.Code,
Message: "Unexpected type from btcd reply",
}
ReplyError(frontend, icmd.Id(), e)
return true
}
txID, err := btcwire.NewShaHashFromStr(txIDStr)
if err != nil {
e := &btcjson.Error{
Code: btcjson.ErrInternal.Code,
Message: "Invalid hash string from btcd reply",
}
ReplyError(frontend, icmd.Id(), e)
return true
}
// Add to transaction store.
sendtx := &tx.SendTx{
TxID: *txID,
Time: txInfo.time.Unix(),
BlockHeight: -1,
Fee: txInfo.fee,
Receivers: txInfo.outputs,
}
a.TxStore.Lock()
a.TxStore.s = append(a.TxStore.s, sendtx)
a.TxStore.dirty = true
a.TxStore.Unlock()
// Remove previous unspent outputs now spent by the tx.
a.UtxoStore.Lock()
modified := a.UtxoStore.s.Remove(txInfo.inputs)
a.UtxoStore.dirty = a.UtxoStore.dirty || modified
// Add unconfirmed change utxo (if any) to UtxoStore.
if txInfo.changeUtxo != nil {
a.UtxoStore.s = append(a.UtxoStore.s, txInfo.changeUtxo)
a.ReqSpentUtxoNtfn(txInfo.changeUtxo)
modified = true
}
if modified {
a.UtxoStore.dirty = true
a.UtxoStore.Unlock()
if err := a.writeDirtyToDisk(); err != nil {
log.Errorf("cannot sync dirty wallet: %v", err)
}
// Notify all frontends of account's new unconfirmed and
// confirmed balance.
confirmed := a.CalculateBalance(1)
unconfirmed := a.CalculateBalance(0) - confirmed
NotifyWalletBalance(frontendNotificationMaster, a.name, confirmed)
NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, a.name, unconfirmed)
} else {
a.UtxoStore.Unlock()
}
a.UtxoStore.Unlock()
// Disk sync tx and utxo stores.
if err := a.writeDirtyToDisk(); err != nil {
log.Errorf("cannot sync dirty wallet: %v", err)
}
// Notify all frontends of account's new unconfirmed and
// confirmed balance.
confirmed := a.CalculateBalance(1)
unconfirmed := a.CalculateBalance(0) - confirmed
NotifyWalletBalance(frontendNotificationMaster, a.name, confirmed)
NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, a.name, unconfirmed)
// btcd cannot be trusted to successfully relay the tx to the
// Bitcoin network. Even if this succeeds, the rawtx must be
@ -713,7 +797,7 @@ func handleSendRawTxReply(frontend chan []byte, icmd btcjson.Cmd,
// Add hex string of raw tx to sent tx pool. If btcd disconnects
// and is reconnected, these txs are resent.
UnminedTxs.Lock()
UnminedTxs.m[TXID(result.(string))] = txInfo
UnminedTxs.m[TXID(*txID)] = txInfo
UnminedTxs.Unlock()
log.Debugf("successfully sent transaction %v", result)

View file

@ -61,19 +61,22 @@ var TxFee = struct {
// for change (if any).
type CreatedTx struct {
rawTx []byte
time time.Time
inputs []*tx.Utxo
outputs []tx.Pair
btcout int64
fee int64
changeAddr string
changeUtxo *tx.Utxo
}
// TXID is a transaction hash identifying a transaction.
type TXID string
type TXID btcwire.ShaHash
// UnminedTXs holds a map of transaction IDs as keys mapping to a
// hex string of a raw transaction. If sending a raw transaction
// succeeds, the tx is added to this map and checked again after each
// new block. If the new block contains a tx, it is removed from
// this map. Otherwise, btcwallet will resend the tx to btcd.
// CreatedTx structure. If sending a raw transaction succeeds, the
// tx is added to this map and checked again after each new block.
// If the new block contains a tx, it is removed from this map.
var UnminedTxs = struct {
sync.Mutex
m map[TXID]*CreatedTx
@ -179,6 +182,10 @@ func (w *Account) txToPairs(pairs map[string]int64, fee int64, minconf int) (*Cr
return nil, err
}
// outputs is a tx.Pair slice representing each output that is created
// by the transaction.
outputs := make([]tx.Pair, 0, len(pairs)+1)
// Add outputs to new tx.
for addr, amt := range pairs {
addr160, _, err := btcutil.DecodeAddress(addr)
@ -193,6 +200,13 @@ func (w *Account) txToPairs(pairs map[string]int64, fee int64, minconf int) (*Cr
}
txout := btcwire.NewTxOut(int64(amt), pkScript)
msgtx.AddTxOut(txout)
// Create amount, address pair and add to outputs.
out := tx.Pair{
Amount: amt,
PubkeyHash: addr160,
}
outputs = append(outputs, out)
}
// Check if there are leftover unspent outputs, and return coins back to
@ -238,6 +252,13 @@ func (w *Account) txToPairs(pairs map[string]int64, fee int64, minconf int) (*Cr
Subscript: pkScript,
}
copy(changeUtxo.AddrHash[:], changeAddrHash)
// Add change to outputs.
out := tx.Pair{
Amount: int64(change),
PubkeyHash: changeAddrHash,
}
outputs = append(outputs, out)
}
// Selected unspent outputs become new transaction's inputs.
@ -296,5 +317,15 @@ func (w *Account) txToPairs(pairs map[string]int64, fee int64, minconf int) (*Cr
buf := new(bytes.Buffer)
msgtx.BtcEncode(buf, btcwire.ProtocolVersion)
return &CreatedTx{buf.Bytes(), inputs, changeAddr, changeUtxo}, nil
info := &CreatedTx{
rawTx: buf.Bytes(),
time: time.Now(),
inputs: inputs,
outputs: outputs,
btcout: int64(btcout),
fee: fee,
changeAddr: changeAddr,
changeUtxo: changeUtxo,
}
return info, nil
}

View file

@ -17,6 +17,7 @@
package main
import (
"bytes"
"code.google.com/p/go.net/websocket"
"crypto/tls"
"crypto/x509"
@ -25,6 +26,7 @@ import (
"errors"
"fmt"
"github.com/conformal/btcjson"
"github.com/conformal/btcwallet/tx"
"github.com/conformal/btcwallet/wallet"
"github.com/conformal/btcwire"
"github.com/conformal/btcws"
@ -527,15 +529,47 @@ func NtfnTxMined(n btcws.Notification) {
log.Errorf("%v handler: unexpected type", n.Id())
return
}
txid, err := btcwire.NewShaHashFromStr(tmn.TxID)
if err != nil {
log.Errorf("%v handler: invalid hash string", n.Id())
return
}
blockhash, err := btcwire.NewShaHashFromStr(tmn.BlockHash)
if err != nil {
log.Errorf("%v handler: invalid block hash string", n.Id())
return
}
// Lookup tx in store and add block information.
accounts.Lock()
out:
for _, a := range accounts.m {
a.TxStore.Lock()
// Search in reverse order, more likely to find it
// sooner that way.
for i := len(a.TxStore.s) - 1; i >= 0; i-- {
sendtx, ok := a.TxStore.s[i].(*tx.SendTx)
if ok {
if bytes.Equal(txid.Bytes(), sendtx.TxID[:]) {
copy(sendtx.BlockHash[:], blockhash.Bytes())
sendtx.BlockHeight = tmn.BlockHeight
sendtx.BlockIndex = int32(tmn.Index)
sendtx.BlockTime = tmn.BlockTime
a.TxStore.Unlock()
break out
}
}
}
a.TxStore.Unlock()
}
accounts.Unlock()
// Remove mined transaction from pool.
UnminedTxs.Lock()
delete(UnminedTxs.m, TXID(txid[:]))
delete(UnminedTxs.m, TXID(*txid))
UnminedTxs.Unlock()
}

667
tx/tx.go
View file

@ -22,14 +22,31 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
"io"
)
var (
// ErrInvalidFormat represents an error where the expected
// format of serialized data was not matched.
ErrInvalidFormat = errors.New("invalid format")
// ErrBadLength represents an error when writing a slice
// where the length does not match the expected.
ErrBadLength = errors.New("bad length")
)
// Byte headers prepending received and sent serialized transactions.
const (
RecvTxHeader byte = iota
SendTxHeader
recvTxHeader byte = iota
sendTxHeader
)
// File format versions.
const (
utxoFileVersion uint32 = 0
txFileVersion uint32 = 0
)
// UtxoStore is a type used for holding all Utxo structures for all
@ -61,29 +78,224 @@ type PkScript []byte
// TxStore is a slice holding RecvTx and SendTx pointers.
type TxStore []interface{}
const (
addressUnknown byte = iota
addressKnown
)
// pubkeyHash is a slice holding 20 bytes (for a known pubkey hash
// of a Bitcoin address), or nil (for an unknown address).
type pubkeyHash []byte
// Enforce that pubkeyHash satisifies the io.ReaderFrom and
// io.WriterTo interfaces.
var pubkeyHashVar = pubkeyHash([]byte{})
var _ io.ReaderFrom = &pubkeyHashVar
var _ io.WriterTo = &pubkeyHashVar
// ReadFrom satisifies the io.ReaderFrom interface.
func (p *pubkeyHash) ReadFrom(r io.Reader) (int64, error) {
var read int64
// Read header byte.
header := make([]byte, 1)
n, err := r.Read(header)
if err != nil {
return int64(n), err
}
read += int64(n)
switch header[0] {
case addressUnknown:
*p = nil
return read, nil
case addressKnown:
addrHash := make([]byte, ripemd160.Size)
n, err := binaryRead(r, binary.LittleEndian, &addrHash)
if err != nil {
return read + int64(n), err
}
read += int64(n)
*p = addrHash
return read, nil
default:
return read, ErrInvalidFormat
}
}
// WriteTo satisifies the io.WriterTo interface.
func (p *pubkeyHash) WriteTo(w io.Writer) (int64, error) {
var written int64
switch {
case *p == nil:
n, err := w.Write([]byte{addressUnknown})
return int64(n), err
case len(*p) == ripemd160.Size:
// Write header.
n, err := w.Write([]byte{addressKnown})
if err != nil {
return int64(n), err
}
written += int64(n)
// Write hash160.
n, err = w.Write(*p)
if err != nil {
return written + int64(n), err
}
written += int64(n)
return written, err
default: // bad!
return 0, ErrBadLength
}
}
// RecvTx is a type storing information about a transaction that was
// received by an address in a wallet.
type RecvTx struct {
TxHash btcwire.ShaHash
TxID btcwire.ShaHash
TimeReceived int64
BlockHeight int32
BlockHash btcwire.ShaHash
Height int32
Amt uint64 // Measured in Satoshis
SenderAddr [ripemd160.Size]byte
ReceiverAddr [ripemd160.Size]byte
BlockIndex int32
BlockTime int64
Amount int64 // Measured in Satoshis
ReceiverHash pubkeyHash
}
// Pairs is a Pair slice with custom serialization and unserialization
// functions.
type Pairs []Pair
// Enforce that Pairs satisifies the io.ReaderFrom and io.WriterTo
// interfaces.
var pairsVar = Pairs([]Pair{})
var _ io.ReaderFrom = &pairsVar
var _ io.WriterTo = &pairsVar
// ReadFrom reades a Pair slice from r. Part of the io.ReaderFrom interface.
func (p *Pairs) ReadFrom(r io.Reader) (int64, error) {
var read int64
nPairsBytes := make([]byte, 4) // Raw bytes for a uint32.
n, err := r.Read(nPairsBytes)
if err != nil {
return int64(n), err
}
read += int64(n)
nPairs := binary.LittleEndian.Uint32(nPairsBytes)
s := make([]Pair, nPairs)
for i := range s {
n, err := s[i].ReadFrom(r)
if err != nil {
return read + n, err
}
read += n
}
*p = s
return read, nil
}
// WriteTo writes a Pair slice to w. Part of the io.WriterTo interface.
func (p *Pairs) WriteTo(w io.Writer) (int64, error) {
var written int64
nPairs := uint32(len(*p))
nPairsBytes := make([]byte, 4) // Raw bytes for a uint32
binary.LittleEndian.PutUint32(nPairsBytes, nPairs)
n, err := w.Write(nPairsBytes)
if err != nil {
return int64(n), err
}
written += int64(n)
s := *p
for i := range s {
n, err := s[i].WriteTo(w)
if err != nil {
return written + n, err
}
written += n
}
return written, nil
}
// Pair represents an amount paid to a single pubkey hash. Pair includes
// custom serialization and unserialization functions by implementing the
// io.ReaderFromt and io.WriterTo interfaces.
type Pair struct {
PubkeyHash pubkeyHash
Amount int64 // Measured in Satoshis
}
// Enforce that Pair satisifies the io.ReaderFrom and io.WriterTo
// interfaces.
var _ io.ReaderFrom = &Pair{}
var _ io.WriterTo = &Pair{}
// ReadFrom reads a serialized Pair from r. Part of the io.ReaderFrom
// interface.
func (p *Pair) ReadFrom(r io.Reader) (int64, error) {
var read int64
n, err := p.PubkeyHash.ReadFrom(r)
if err != nil {
return n, err
}
read += n
amountBytes := make([]byte, 8) // raw bytes for a uint64
nr, err := r.Read(amountBytes)
if err != nil {
return read + int64(nr), err
}
read += int64(nr)
p.Amount = int64(binary.LittleEndian.Uint64(amountBytes))
return read, nil
}
// WriteTo serializes a Pair, writing it to w. Part of the
// io.WriterTo interface.
func (p *Pair) WriteTo(w io.Writer) (int64, error) {
var written int64
n, err := p.PubkeyHash.WriteTo(w)
if err != nil {
return n, err
}
written += n
amountBytes := make([]byte, 8) // raw bytes for a uint64
binary.LittleEndian.PutUint64(amountBytes, uint64(p.Amount))
nw, err := w.Write(amountBytes)
if err != nil {
return written + int64(nw), err
}
written += int64(nw)
return written, nil
}
// SendTx is a type storing information about a transaction that was
// sent by an address in a wallet.
type SendTx struct {
TxHash btcwire.ShaHash
BlockHash btcwire.ShaHash
Height int32
Fee uint64 // Measured in Satoshis
SenderAddr [ripemd160.Size]byte
ReceiverAddrs []struct {
Addr [ripemd160.Size]byte
Amt uint64 // Measured in Satoshis
}
TxID btcwire.ShaHash
Time int64
BlockHeight int32
BlockHash btcwire.ShaHash
BlockIndex int32
BlockTime int64
Fee int64 // Measured in Satoshis
Receivers Pairs
}
// We want to use binaryRead and binaryWrite instead of binary.Read
@ -117,19 +329,28 @@ func binaryWrite(w io.Writer, order binary.ByteOrder, data interface{}) (n int64
// ReadFrom satisifies the io.ReaderFrom interface. Utxo structs are
// read in from r until an io.EOF is reached. If an io.EOF is reached
// before a Utxo is finished being read, err will be non-nil.
func (u *UtxoStore) ReadFrom(r io.Reader) (n int64, err error) {
func (u *UtxoStore) ReadFrom(r io.Reader) (int64, error) {
var read int64
// Read the file version. This is currently not used.
versionBytes := make([]byte, 4) // bytes for a uint32
n, err := r.Read(versionBytes)
if err != nil {
return int64(n), err
}
read = int64(n)
for {
// Read Utxo
utxo := new(Utxo)
read, err = utxo.ReadFrom(r)
n, err := utxo.ReadFrom(r)
if err != nil {
if read == 0 && err == io.EOF {
return n, nil
if n == 0 && err == io.EOF {
err = nil
}
return n + read, err
return read + n, err
}
n += read
read += n
*u = append(*u, utxo)
}
}
@ -137,18 +358,29 @@ func (u *UtxoStore) ReadFrom(r io.Reader) (n int64, err error) {
// WriteTo satisifies the io.WriterTo interface. Each Utxo is written
// to w, prepended by a single byte header to distinguish between
// confirmed and unconfirmed outputs.
func (u *UtxoStore) WriteTo(w io.Writer) (n int64, err error) {
func (u *UtxoStore) WriteTo(w io.Writer) (int64, error) {
var written int64
// Write file version. This is currently not used.
versionBytes := make([]byte, 4) // bytes for a uint32
binary.LittleEndian.PutUint32(versionBytes, utxoFileVersion)
n, err := w.Write(versionBytes)
if err != nil {
return int64(n), err
}
written = int64(n)
// Write each utxo in the store.
for _, utxo := range *u {
// Write Utxo
written, err = utxo.WriteTo(w)
n, err := utxo.WriteTo(w)
if err != nil {
return n + written, err
return written + n, err
}
n += written
written += n
}
return n, nil
return written, nil
}
// Rollback removes all utxos from and after the block specified
@ -219,9 +451,12 @@ func (u *UtxoStore) Remove(toRemove []*Utxo) (modified bool) {
// ReadFrom satisifies the io.ReaderFrom interface. A Utxo is read
// from r with the format:
//
// [AddrHash (20 bytes), Out (36 bytes), Subscript (varies), Amt (8 bytes), Height (4 bytes), BlockHash (32 bytes)]
//
// Each field is read little endian.
// AddrHash (20 bytes)
// Out (36 bytes)
// Subscript (varies)
// Amt (8 bytes, little endian)
// Height (4 bytes, little endian)
// BlockHash (32 bytes)
func (u *Utxo) ReadFrom(r io.Reader) (n int64, err error) {
datas := []interface{}{
&u.AddrHash,
@ -249,9 +484,12 @@ func (u *Utxo) ReadFrom(r io.Reader) (n int64, err error) {
// WriteTo satisifies the io.WriterTo interface. A Utxo is written to
// w in the format:
//
// [AddrHash (20 bytes), Out (36 bytes), Subscript (varies), Amt (8 bytes), Height (4 bytes), BlockHash (32 bytes)]
//
// Each field is written little endian.
// AddrHash (20 bytes)
// Out (36 bytes)
// Subscript (varies)
// Amt (8 bytes, little endian)
// Height (4 bytes, little endian)
// BlockHash (32 bytes)
func (u *Utxo) WriteTo(w io.Writer) (n int64, err error) {
datas := []interface{}{
&u.AddrHash,
@ -323,9 +561,8 @@ func (o *OutPoint) WriteTo(w io.Writer) (n int64, err error) {
// ReadFrom satisifies the io.ReaderFrom interface. A PkScript is read
// from r with the format:
//
// [Length (4 byte unsigned integer), ScriptBytes (Length bytes)]
//
// Length is read little endian.
// Length (4 byte, little endian)
// ScriptBytes (Length bytes)
func (s *PkScript) ReadFrom(r io.Reader) (n int64, err error) {
var scriptlen uint32
var read int64
@ -349,9 +586,8 @@ func (s *PkScript) ReadFrom(r io.Reader) (n int64, err error) {
// WriteTo satisifies the io.WriterTo interface. A PkScript is written
// to w in the format:
//
// [Length (4 byte unsigned integer), ScriptBytes (Length bytes)]
//
// Length is written little endian.
// Length (4 byte, little endian)
// ScriptBytes (Length bytes)
func (s *PkScript) WriteTo(w io.Writer) (n int64, err error) {
var written int64
written, err = binaryWrite(w, binary.LittleEndian, uint32(len(*s)))
@ -372,42 +608,54 @@ func (s *PkScript) WriteTo(w io.Writer) (n int64, err error) {
// ReadFrom satisifies the io.ReaderFrom interface. A TxStore is read
// in from r with the format:
//
// [[TxHeader (1 byte), Tx (varies in size)]...]
func (txs *TxStore) ReadFrom(r io.Reader) (n int64, err error) {
// Version (4 bytes, little endian)
// [(TxHeader (1 byte), Tx (varies in size))...]
func (txs *TxStore) ReadFrom(r io.Reader) (int64, error) {
var read int64
// Read the file version. This is currently not used.
versionBytes := make([]byte, 4) // bytes for a uint32
n, err := r.Read(versionBytes)
if err != nil {
return int64(n), err
}
read += int64(n)
store := []interface{}{}
defer func() {
*txs = store
}()
var read int64
for {
// Read header
var header byte
read, err = binaryRead(r, binary.LittleEndian, &header)
n, err := binaryRead(r, binary.LittleEndian, &header)
if err != nil {
// io.EOF is not an error here.
if err == io.EOF {
return n + read, nil
err = nil
}
return n + read, err
return read + n, err
}
n += read
read += n
var tx io.ReaderFrom
switch header {
case RecvTxHeader:
case recvTxHeader:
tx = new(RecvTx)
case SendTxHeader:
case sendTxHeader:
tx = new(SendTx)
default:
return n, fmt.Errorf("unknown Tx header")
}
// Read tx
read, err = tx.ReadFrom(r)
n, err = tx.ReadFrom(r)
if err != nil {
return n + read, err
return read + n, err
}
n += read
read += n
store = append(store, tx)
}
@ -416,35 +664,48 @@ func (txs *TxStore) ReadFrom(r io.Reader) (n int64, err error) {
// WriteTo satisifies the io.WriterTo interface. A TxStore is written
// to w in the format:
//
// [[TxHeader (1 byte), Tx (varies in size)]...]
func (txs *TxStore) WriteTo(w io.Writer) (n int64, err error) {
store := ([]interface{})(*txs)
// Version (4 bytes, little endian)
// [(TxHeader (1 byte), Tx (varies in size))...]
func (txs *TxStore) WriteTo(w io.Writer) (int64, error) {
var written int64
// Write file version. This is currently not used.
versionBytes := make([]byte, 4) // bytes for a uint32
binary.LittleEndian.PutUint32(versionBytes, utxoFileVersion)
n, err := w.Write(versionBytes)
if err != nil {
return int64(n), err
}
written = int64(n)
store := ([]interface{})(*txs)
for _, tx := range store {
switch tx.(type) {
case *RecvTx:
written, err = binaryWrite(w, binary.LittleEndian, RecvTxHeader)
n, err := binaryWrite(w, binary.LittleEndian, recvTxHeader)
if err != nil {
return n + written, err
return written + n, err
}
n += written
written += n
case *SendTx:
written, err = binaryWrite(w, binary.LittleEndian, SendTxHeader)
n, err := binaryWrite(w, binary.LittleEndian, sendTxHeader)
if err != nil {
return n + written, err
return written + n, err
}
n += written
written += n
default:
return n, fmt.Errorf("unknown type in TxStore")
return written, fmt.Errorf("unknown type in TxStore")
}
wt := tx.(io.WriterTo)
written, err = wt.WriteTo(w)
n, err := wt.WriteTo(w)
if err != nil {
return n + written, err
return written + n, err
}
n += written
written += n
}
return n, nil
return written, nil
}
// Rollback removes all txs from and after the block specified by a
@ -470,25 +731,24 @@ func (txs *TxStore) Rollback(height int32, hash *btcwire.ShaHash) (modified bool
}()
for i := len(s) - 1; i >= 0; i-- {
var txheight int32
var txhash *btcwire.ShaHash
switch s[i].(type) {
var txBlockHeight int32
var txBlockHash *btcwire.ShaHash
switch tx := s[i].(type) {
case *RecvTx:
tx := s[i].(*RecvTx)
if height > tx.Height {
if height > tx.BlockHeight {
break
}
txheight = tx.Height
txhash = &tx.BlockHash
txBlockHeight = tx.BlockHeight
txBlockHash = &tx.BlockHash
case *SendTx:
tx := s[i].(*SendTx)
if height > tx.Height {
if height > tx.BlockHeight {
break
}
txheight = tx.Height
txhash = &tx.BlockHash
txBlockHeight = tx.BlockHeight
txBlockHash = &tx.BlockHash
}
if height == txheight && *hash == *txhash {
if height == txBlockHeight && *hash == *txBlockHash {
endlen = i
}
}
@ -498,21 +758,34 @@ func (txs *TxStore) Rollback(height int32, hash *btcwire.ShaHash) (modified bool
// ReadFrom satisifies the io.ReaderFrom interface. A RecTx is read
// in from r with the format:
//
// [TxHash (32 bytes), BlockHash (32 bytes), Height (4 bytes), Amt (8 bytes), SenderAddr (20 bytes), ReceiverAddr (20 bytes)]
//
// Each field is read little endian.
// TxID (32 bytes)
// TimeReceived (8 bytes, little endian)
// BlockHeight (4 bytes, little endian)
// BlockHash (32 bytes)
// BlockIndex (4 bytes, little endian)
// BlockTime (8 bytes, little endian)
// Amt (8 bytes, little endian)
// ReceiverAddr (varies)
func (tx *RecvTx) ReadFrom(r io.Reader) (n int64, err error) {
datas := []interface{}{
&tx.TxHash,
&tx.TxID,
&tx.TimeReceived,
&tx.BlockHeight,
&tx.BlockHash,
&tx.Height,
&tx.Amt,
&tx.SenderAddr,
&tx.ReceiverAddr,
&tx.BlockIndex,
&tx.BlockTime,
&tx.Amount,
&tx.ReceiverHash,
}
var read int64
for _, data := range datas {
read, err = binaryRead(r, binary.LittleEndian, data)
switch e := data.(type) {
case io.ReaderFrom:
read, err = e.ReadFrom(r)
default:
read, err = binaryRead(r, binary.LittleEndian, data)
}
if err != nil {
return n + read, err
}
@ -524,21 +797,34 @@ func (tx *RecvTx) ReadFrom(r io.Reader) (n int64, err error) {
// WriteTo satisifies the io.WriterTo interface. A RecvTx is written to
// w in the format:
//
// [TxHash (32 bytes), BlockHash (32 bytes), Height (4 bytes), Amt (8 bytes), SenderAddr (20 bytes), ReceiverAddr (20 bytes)]
//
// Each field is written little endian.
// TxID (32 bytes)
// TimeReceived (8 bytes, little endian)
// BlockHeight (4 bytes, little endian)
// BlockHash (32 bytes)
// BlockIndex (4 bytes, little endian)
// BlockTime (8 bytes, little endian)
// Amt (8 bytes, little endian)
// ReceiverAddr (varies)
func (tx *RecvTx) WriteTo(w io.Writer) (n int64, err error) {
datas := []interface{}{
&tx.TxHash,
&tx.TxID,
&tx.TimeReceived,
&tx.BlockHeight,
&tx.BlockHash,
&tx.Height,
&tx.Amt,
&tx.SenderAddr,
&tx.ReceiverAddr,
&tx.BlockIndex,
&tx.BlockTime,
&tx.Amount,
&tx.ReceiverHash,
}
var written int64
for _, data := range datas {
written, err = binaryWrite(w, binary.LittleEndian, data)
switch e := data.(type) {
case io.WriterTo:
written, err = e.WriteTo(w)
default:
written, err = binaryWrite(w, binary.LittleEndian, data)
}
if err != nil {
return n + written, err
}
@ -547,94 +833,161 @@ func (tx *RecvTx) WriteTo(w io.Writer) (n int64, err error) {
return n, nil
}
// TxInfo returns a slice of maps that may be marshaled as a JSON array
// of JSON objects for a listtransactions RPC reply.
func (tx *RecvTx) TxInfo(account string, curheight int32,
net btcwire.BitcoinNet) map[string]interface{} {
address, err := btcutil.EncodeAddress(tx.ReceiverHash, net)
if err != nil {
address = "Unknown"
}
txInfo := map[string]interface{}{
"category": "receive",
"account": account,
"address": address,
"amount": tx.Amount,
"txid": tx.TxID.String(),
"timereceived": tx.TimeReceived,
}
if tx.BlockHeight != -1 {
txInfo["blockhash"] = tx.BlockHash.String()
txInfo["blockindex"] = tx.BlockIndex
txInfo["blocktime"] = tx.BlockTime
txInfo["confirmations"] = curheight - tx.BlockHeight + 1
}
return txInfo
}
// ReadFrom satisifies the io.WriterTo interface. A SendTx is read
// from r with the format:
//
// [TxHash (32 bytes), Height (4 bytes), Fee (8 bytes), SenderAddr (20 bytes), len(ReceiverAddrs) (4 bytes), ReceiverAddrs[Addr (20 bytes), Amt (8 bytes)]...]
//
// Each field is read little endian.
// TxID (32 bytes)
// Time (8 bytes, little endian)
// BlockHeight (4 bytes, little endian)
// BlockHash (32 bytes)
// BlockIndex (4 bytes, little endian)
// BlockTime (8 bytes, little endian)
// Fee (8 bytes, little endian)
// Receivers (varies)
func (tx *SendTx) ReadFrom(r io.Reader) (n int64, err error) {
var nReceivers uint32
datas := []interface{}{
&tx.TxHash,
&tx.Height,
&tx.Fee,
&tx.SenderAddr,
&nReceivers,
}
var read int64
datas := []interface{}{
&tx.TxID,
&tx.Time,
&tx.BlockHeight,
&tx.BlockHash,
&tx.BlockIndex,
&tx.BlockTime,
&tx.Fee,
&tx.Receivers,
}
for _, data := range datas {
read, err = binaryRead(r, binary.LittleEndian, data)
switch e := data.(type) {
case io.ReaderFrom:
read, err = e.ReadFrom(r)
default:
read, err = binaryRead(r, binary.LittleEndian, data)
}
if err != nil {
return n + read, err
}
n += read
}
if nReceivers == 0 {
// XXX: Is this valid? Entire output is a fee for the miner?
return n, nil
}
tx.ReceiverAddrs = make([]struct {
Addr [ripemd160.Size]byte
Amt uint64
},
nReceivers)
for i := uint32(0); i < nReceivers; i++ {
datas := []interface{}{
&tx.ReceiverAddrs[i].Addr,
&tx.ReceiverAddrs[i].Amt,
}
for _, data := range datas {
read, err = binaryRead(r, binary.LittleEndian, data)
if err != nil {
return n + read, err
}
n += read
}
}
return n, nil
}
// WriteTo satisifies the io.WriterTo interface. A SendTx is written to
// w in the format:
//
// [TxHash (32 bytes), Height (4 bytes), Fee (8 bytes), SenderAddr (20 bytes), len(ReceiverAddrs) (4 bytes), ReceiverAddrs[Addr (20 bytes), Amt (8 bytes)]...]
//
// Each field is written little endian.
// TxID (32 bytes)
// Time (8 bytes, little endian)
// BlockHeight (4 bytes, little endian)
// BlockHash (32 bytes)
// BlockIndex (4 bytes, little endian)
// BlockTime (8 bytes, little endian)
// Fee (8 bytes, little endian)
// Receivers (varies)
func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) {
nReceivers := uint32(len(tx.ReceiverAddrs))
if int64(nReceivers) != int64(len(tx.ReceiverAddrs)) {
return n, errors.New("too many receiving addresses")
}
datas := []interface{}{
&tx.TxHash,
&tx.Height,
&tx.Fee,
&tx.SenderAddr,
nReceivers,
}
var written int64
datas := []interface{}{
&tx.TxID,
&tx.Time,
&tx.BlockHeight,
&tx.BlockHash,
&tx.BlockIndex,
&tx.BlockTime,
&tx.Fee,
&tx.Receivers,
}
for _, data := range datas {
written, err = binaryWrite(w, binary.LittleEndian, data)
switch e := data.(type) {
case io.WriterTo:
written, err = e.WriteTo(w)
default:
written, err = binaryWrite(w, binary.LittleEndian, data)
}
if err != nil {
return n + written, err
}
n += written
}
for i := range tx.ReceiverAddrs {
datas := []interface{}{
&tx.ReceiverAddrs[i].Addr,
&tx.ReceiverAddrs[i].Amt,
}
for _, data := range datas {
written, err = binaryWrite(w, binary.LittleEndian, data)
if err != nil {
return n + written, err
}
n += written
}
}
return n, nil
}
// TxInfo returns a slice of maps that may be marshaled as a JSON array
// of JSON objects for a listtransactions RPC reply.
func (tx *SendTx) TxInfo(account string, curheight int32,
net btcwire.BitcoinNet) []map[string]interface{} {
reply := make([]map[string]interface{}, len(tx.Receivers))
var confirmations int32
if tx.BlockHeight != -1 {
confirmations = curheight - tx.BlockHeight + 1
}
// error is ignored since the length will always be correct.
txID, _ := btcwire.NewShaHash(tx.TxID[:])
txIDStr := txID.String()
// error is ignored since the length will always be correct.
blockHash, _ := btcwire.NewShaHash(tx.BlockHash[:])
blockHashStr := blockHash.String()
for i, pair := range tx.Receivers {
// EncodeAddress cannot error with these inputs.
address, err := btcutil.EncodeAddress(pair.PubkeyHash, net)
if err != nil {
address = "Unknown"
}
info := map[string]interface{}{
"account": account,
"address": address,
"category": "send",
"amount": -pair.Amount,
"fee": float64(tx.Fee) / float64(btcutil.SatoshiPerBitcoin),
"confirmations": confirmations,
"txid": txIDStr,
"time": tx.Time,
"timereceived": tx.Time,
}
if tx.BlockHeight != -1 {
info["blockhash"] = blockHashStr
info["blockindex"] = tx.BlockIndex
info["blocktime"] = tx.BlockTime
}
reply[i] = info
}
return reply
}

View file

@ -19,7 +19,6 @@ package tx
import (
"bytes"
"code.google.com/p/go.crypto/ripemd160"
"encoding/binary"
"github.com/conformal/btcwire"
"github.com/davecgh/go-spew/spew"
"io"
@ -29,55 +28,53 @@ import (
var (
recvtx = &RecvTx{
TxHash: [btcwire.HashSize]byte{
TxID: [btcwire.HashSize]byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31,
},
Amt: 69,
SenderAddr: [ripemd160.Size]byte{
BlockHash: [btcwire.HashSize]byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31,
},
BlockHeight: 69,
Amount: 69,
ReceiverHash: []byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19,
},
ReceiverAddr: [ripemd160.Size]byte{
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 37, 38, 39,
},
}
sendtx = &SendTx{
TxHash: [btcwire.HashSize]byte{
TxID: [btcwire.HashSize]byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31,
},
SenderAddr: [ripemd160.Size]byte{
Time: 12345,
BlockHash: [btcwire.HashSize]byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31,
},
ReceiverAddrs: []struct {
Addr [ripemd160.Size]byte
Amt uint64
}{
struct {
Addr [ripemd160.Size]byte
Amt uint64
}{
Amt: 69,
Addr: [ripemd160.Size]byte{
BlockHeight: 69,
BlockTime: 54321,
BlockIndex: 3,
Receivers: []Pair{
Pair{
PubkeyHash: []byte{
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
34, 35, 36, 37, 38, 39,
},
Amount: 69,
},
struct {
Addr [ripemd160.Size]byte
Amt uint64
}{
Amt: 96,
Addr: [ripemd160.Size]byte{
Pair{
PubkeyHash: []byte{
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
54, 55, 56, 57, 58, 59,
},
Amount: 96,
},
},
}
@ -145,24 +142,36 @@ func TestUtxoStoreWriteRead(t *testing.T) {
utxo.Subscript = []byte{}
utxo.Amt = uint64(i + 3)
utxo.Height = int32(i + 4)
utxo.BlockHash = [btcwire.HashSize]byte{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31,
}
*store1 = append(*store1, utxo)
}
bufWriter := &bytes.Buffer{}
n, err := store1.WriteTo(bufWriter)
nWritten, err := store1.WriteTo(bufWriter)
if err != nil {
t.Error(err)
}
if nWritten != int64(bufWriter.Len()) {
t.Errorf("Wrote %v bytes but write buffer has %v bytes.", nWritten, bufWriter.Len())
}
storeBytes := bufWriter.Bytes()
bufReader := bytes.NewBuffer(storeBytes)
if nWritten != int64(bufReader.Len()) {
t.Errorf("Wrote %v bytes but read buffer has %v bytes.", nWritten, bufReader.Len())
}
store2 := new(UtxoStore)
n, err = store2.ReadFrom(bytes.NewBuffer(storeBytes))
nRead, err := store2.ReadFrom(bufReader)
if err != nil {
t.Error(err)
}
if int(n) != len(storeBytes) {
t.Error("Incorrect number of bytes read.")
if nWritten != nRead {
t.Errorf("Bytes written (%v) does not match bytes read (%v).", nWritten, nRead)
}
if !reflect.DeepEqual(store1, store2) {
@ -170,15 +179,15 @@ func TestUtxoStoreWriteRead(t *testing.T) {
t.Error("Stores do not match.")
}
truncatedLen := 100
truncatedLen := 101
truncatedReadBuf := bytes.NewBuffer(storeBytes[:truncatedLen])
store3 := new(UtxoStore)
n, err = store3.ReadFrom(truncatedReadBuf)
n, err := store3.ReadFrom(truncatedReadBuf)
if err != io.EOF {
t.Errorf("Expected err = io.EOF reading from truncated buffer, got: %v", err)
}
if int(n) != truncatedLen {
t.Error("Incorrect number of bytes read from truncated buffer.")
t.Errorf("Incorrect number of bytes (%v) read from truncated buffer (len %v).", n, truncatedLen)
}
}
@ -189,10 +198,6 @@ func TestRecvTxWriteRead(t *testing.T) {
t.Error(err)
return
}
if int(n) != binary.Size(recvtx) {
t.Error("Writing Tx: Size Mismatch")
return
}
txBytes := bufWriter.Bytes()
tx := new(RecvTx)
@ -201,10 +206,6 @@ func TestRecvTxWriteRead(t *testing.T) {
t.Error(err)
return
}
if int(n) != binary.Size(tx) {
t.Error("Reading Tx: Size Mismatch")
return
}
if !reflect.DeepEqual(recvtx, tx) {
t.Error("Txs do not match.")
@ -240,7 +241,8 @@ func TestSendTxWriteRead(t *testing.T) {
return
}
if n1 != n2 {
t.Error("Number of bytes written and read mismatch.")
t.Errorf("Number of bytes written and read mismatch, %d != %d",
n1, n2)
return
}