lbcwallet/tx/tx.go
2014-01-27 14:14:54 -05:00

1199 lines
28 KiB
Go

/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package tx
import (
"bytes"
"code.google.com/p/go.crypto/ripemd160"
"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
)
// ReaderFromVersion is an io.ReaderFrom and io.WriterTo that
// can specify any particular wallet file format for reading
// depending on the wallet file version.
type ReaderFromVersion interface {
ReadFromVersion(uint32, io.Reader) (int64, error)
io.WriterTo
}
// Various UTXO file versions.
const (
utxoVersFirst uint32 = iota
)
// Various Tx file versions.
const (
txVersFirst uint32 = iota
// txVersRecvTxIndex is the version where the txout index
// was added to the RecvTx struct.
txVersRecvTxIndex
// txVersMarkSentChange is the version where serialized SentTx
// added a flags field, used for marking a sent transaction
// as change.
txVersMarkSentChange
)
// Current versions.
const (
utxoVersCurrent = utxoVersFirst
txVersCurrent = txVersMarkSentChange
)
// UtxoStore is a type used for holding all Utxo structures for all
// addresses in a wallet.
type UtxoStore []*Utxo
// Utxo is a type storing information about a single unspent
// transaction output.
type Utxo struct {
AddrHash [ripemd160.Size]byte
Out OutPoint
Subscript PkScript
Amt uint64 // Measured in Satoshis
// Height is -1 if Utxo has not yet appeared in a block.
Height int32
// BlockHash is zeroed if Utxo has not yet appeared in a block.
BlockHash btcwire.ShaHash
}
// OutPoint is a btcwire.OutPoint with custom methods for serialization.
type OutPoint btcwire.OutPoint
// PkScript is a custom type with methods to serialize pubkey scripts
// of variable length.
type PkScript []byte
// Tx is a generic type that can be used in place of either of the tx types in
// a TxStore.
type Tx interface {
io.WriterTo
ReadFromVersion(uint32, io.Reader) (int64, error)
TxInfo(string, int32, btcwire.BitcoinNet) []map[string]interface{}
}
// TxStore is a slice holding RecvTx and SendTx pointers.
type TxStore []Tx
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 {
TxID btcwire.ShaHash
TxOutIdx uint32
TimeReceived int64
BlockHeight int32
BlockHash btcwire.ShaHash
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
func (p *Pairs) ReadFromVersion(vers uint32, 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].ReadFromVersion(vers, r)
if err != nil {
return read + n, err
}
read += n
}
*p = s
return read, nil
}
func (p *Pairs) ReadFrom(r io.Reader) (int64, error) {
return p.ReadFromVersion(txVersCurrent, r)
}
// 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
Change bool
}
// Enforce that Pair satisifies the io.ReaderFrom and io.WriterTo
// interfaces.
var _ io.ReaderFrom = &Pair{}
var _ io.WriterTo = &Pair{}
func (p *Pair) ReadFromVersion(vers uint32, r io.Reader) (int64, error) {
if vers >= txVersMarkSentChange {
// Use latest version
return p.ReadFrom(r)
}
// Old version did not read flags.
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
}
// 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))
// Read flags.
flags := make([]byte, 1) // raw bytes for 1 byte of flags
nr, err = r.Read(flags)
if err != nil {
return read + int64(nr), err
}
read += int64(nr)
p.Change = flags[0]&1<<0 == 1<<0
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)
// Set and write flags.
flags := byte(0)
if p.Change {
flags |= 1 << 0
}
flagBytes := []byte{flags}
nw, err = w.Write(flagBytes)
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 {
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
// and binary.Write because those from the binary package do not return
// the number of bytes actually written or read. We need to return
// this value to correctly support the io.ReaderFrom and io.WriterTo
// interfaces.
func binaryRead(r io.Reader, order binary.ByteOrder, data interface{}) (n int64, err error) {
var read int
buf := make([]byte, binary.Size(data))
if read, err = r.Read(buf); err != nil {
return int64(read), err
}
if read < binary.Size(data) {
return int64(read), io.EOF
}
return int64(read), binary.Read(bytes.NewBuffer(buf), order, data)
}
// See comment for binaryRead().
func binaryWrite(w io.Writer, order binary.ByteOrder, data interface{}) (n int64, err error) {
var buf bytes.Buffer
if err = binary.Write(&buf, order, data); err != nil {
return 0, err
}
written, err := w.Write(buf.Bytes())
return int64(written), err
}
// 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) (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)
n, err := utxo.ReadFrom(r)
if err != nil {
if n == 0 && err == io.EOF {
err = nil
}
return read + n, err
}
read += n
*u = append(*u, utxo)
}
}
// 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) (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, utxoVersCurrent)
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
n, err := utxo.WriteTo(w)
if err != nil {
return written + n, err
}
written += n
}
return written, nil
}
// Insert inserts an Utxo into the store.
func (u *UtxoStore) Insert(utxo *Utxo) {
s := *u
defer func() {
*u = s
}()
// 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.
for i := range s {
if bytes.Equal(s[i].Out.Hash[:], utxo.Out.Hash[:]) && s[i].Out.Index == utxo.Out.Index {
// Fill relevant block information.
copy(s[i].BlockHash[:], utxo.BlockHash[:])
s[i].Height = utxo.Height
return
}
}
// After iterating through all UTXOs, it was not a duplicate or
// change UTXO appearing in a block. Append a new Utxo to the end.
s = append(s, utxo)
}
// Rollback removes all utxos from and after the block specified
// by a block height and hash.
//
// Correct results rely on u being sorted by block height in
// increasing order.
func (u *UtxoStore) Rollback(height int32, hash *btcwire.ShaHash) (modified bool) {
s := *u
// endlen specifies the final length of the rolled-back UtxoStore.
// Past endlen, array elements are nilled. We do this instead of
// just reslicing with a shorter length to avoid leaving elements
// in the underlying array so they can be garbage collected.
endlen := len(s)
defer func() {
modified = endlen != len(s)
for i := endlen; i < len(s); i++ {
s[i] = nil
}
*u = s[:endlen]
return
}()
for i := len(s) - 1; i >= 0; i-- {
if height > s[i].Height {
break
}
if height == s[i].Height && *hash == s[i].BlockHash {
endlen = i
}
}
return
}
// Remove removes all utxos from toRemove from a UtxoStore. The order
// of utxos in the resulting UtxoStore is unspecified.
func (u *UtxoStore) Remove(toRemove []*Utxo) (modified bool) {
s := *u
m := make(map[*Utxo]bool)
for _, utxo := range s {
m[utxo] = true
}
for _, candidate := range toRemove {
if _, ok := m[candidate]; ok {
modified = true
}
delete(m, candidate)
}
if !modified {
return
}
s = make([]*Utxo, len(m))
i := 0
for utxo := range m {
s[i] = utxo
i++
}
*u = s
return
}
// 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, 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,
&u.Out,
&u.Subscript,
&u.Amt,
&u.Height,
&u.BlockHash,
}
var read int64
for _, data := range datas {
if rf, ok := data.(io.ReaderFrom); ok {
read, err = rf.ReadFrom(r)
} else {
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 Utxo is written to
// w in the format:
//
// 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,
&u.Out,
&u.Subscript,
&u.Amt,
&u.Height,
&u.BlockHash,
}
var written int64
for _, data := range datas {
if wt, ok := data.(io.WriterTo); ok {
written, err = wt.WriteTo(w)
} else {
written, err = binaryWrite(w, binary.LittleEndian, data)
}
if err != nil {
return n + written, err
}
n += written
}
return n, nil
}
// ReadFrom satisifies the io.ReaderFrom interface. An OutPoint is read
// from r with the format:
//
// [Hash (32 bytes), Index (4 bytes)]
//
// Each field is read little endian.
func (o *OutPoint) ReadFrom(r io.Reader) (n int64, err error) {
datas := []interface{}{
&o.Hash,
&o.Index,
}
var read int64
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. An OutPoint is written
// to w in the format:
//
// [Hash (32 bytes), Index (4 bytes)]
//
// Each field is written little endian.
func (o *OutPoint) WriteTo(w io.Writer) (n int64, err error) {
datas := []interface{}{
&o.Hash,
&o.Index,
}
var written int64
for _, data := range datas {
written, err = binaryWrite(w, binary.LittleEndian, data)
if err != nil {
return n + written, err
}
n += written
}
return n, nil
}
// ReadFrom satisifies the io.ReaderFrom interface. A PkScript is read
// from r with the format:
//
// 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
read, err = binaryRead(r, binary.LittleEndian, &scriptlen)
if err != nil {
return n + read, err
}
n += read
scriptbuf := new(bytes.Buffer)
read, err = scriptbuf.ReadFrom(io.LimitReader(r, int64(scriptlen)))
if err != nil {
return n + read, err
}
n += read
*s = scriptbuf.Bytes()
return n, nil
}
// WriteTo satisifies the io.WriterTo interface. A PkScript is written
// to w in the format:
//
// 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)))
if err != nil {
return n + written, nil
}
n += written
written, err = bytes.NewBuffer(*s).WriteTo(w)
if err != nil {
return n + written, nil
}
n += written
return n, nil
}
// ReadFrom satisifies the io.ReaderFrom interface. A TxStore is read
// in from r with the format:
//
// 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.
versionBytes := make([]byte, 4) // bytes for a uint32
n, err := r.Read(versionBytes)
if err != nil {
return int64(n), err
}
vers := binary.LittleEndian.Uint32(versionBytes)
read += int64(n)
store := []Tx{}
defer func() {
*txs = store
}()
for {
// Read header
var header byte
n, err := binaryRead(r, binary.LittleEndian, &header)
if err != nil {
// io.EOF is not an error here.
if err == io.EOF {
err = nil
}
return read + n, err
}
read += n
var tx Tx
// Read tx.
switch header {
case recvTxHeader:
t := new(RecvTx)
n, err = t.ReadFromVersion(vers, r)
if err != nil {
return read + n, err
}
read += n
tx = t
case sendTxHeader:
t := new(SendTx)
n, err = t.ReadFromVersion(vers, r)
if err != nil {
return read + n, err
}
read += n
tx = t
default:
return n, fmt.Errorf("unknown Tx header")
}
store = append(store, tx)
}
}
// WriteTo satisifies the io.WriterTo interface. A TxStore is written
// to w in the format:
//
// 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.
versionBytes := make([]byte, 4) // bytes for a uint32
binary.LittleEndian.PutUint32(versionBytes, txVersCurrent)
n, err := w.Write(versionBytes)
if err != nil {
return int64(n), err
}
written = int64(n)
store := ([]Tx)(*txs)
for _, tx := range store {
// Write header for tx.
var header byte
switch tx.(type) {
case *RecvTx:
header = recvTxHeader
case *SendTx:
header = sendTxHeader
default:
return written, fmt.Errorf("unknown type in TxStore")
}
headerBytes := []byte{header}
n, err := w.Write(headerBytes)
if err != nil {
return written + int64(n), err
}
written += int64(n)
// Write tx.
wt := tx.(io.WriterTo)
n64, err := wt.WriteTo(w)
if err != nil {
return written + n64, err
}
written += n64
}
return written, nil
}
// InsertRecvTx inserts a RecvTx, checking for duplicates, and updating
// previous entries with the latest block information in tx.
func (txs *TxStore) InsertRecvTx(tx *RecvTx) {
s := *txs
defer func() {
*txs = s
}()
// First, iterate through all stored tx history. If a received tx
// matches the one being added (equal txid and txout idx), update
// it with the new block information.
for i := range s {
recvTx, ok := s[i].(*RecvTx)
if !ok {
// Can only check for equality if the types match.
continue
}
// Found an identical received tx.
if bytes.Equal(recvTx.TxID[:], tx.TxID[:]) &&
recvTx.TxOutIdx == tx.TxOutIdx {
// Fill relevant block information.
copy(recvTx.BlockHash[:], tx.BlockHash[:])
recvTx.BlockHeight = tx.BlockHeight
recvTx.BlockIndex = tx.BlockIndex
recvTx.BlockTime = tx.BlockTime
return
}
}
// No received tx entries with the same outpoint. Append to the end.
s = append(s, tx)
}
// Rollback removes all txs from and after the block specified by a
// block height and hash.
//
// Correct results rely on txs being sorted by block height in
// increasing order.
func (txs *TxStore) Rollback(height int32, hash *btcwire.ShaHash) (modified bool) {
s := ([]Tx)(*txs)
// endlen specifies the final length of the rolled-back TxStore.
// Past endlen, array elements are nilled. We do this instead of
// just reslicing with a shorter length to avoid leaving elements
// in the underlying array so they can be garbage collected.
endlen := len(s)
defer func() {
modified = endlen != len(s)
for i := endlen; i < len(s); i++ {
s[i] = nil
}
*txs = s[:endlen]
return
}()
for i := len(s) - 1; i >= 0; i-- {
var txBlockHeight int32
var txBlockHash *btcwire.ShaHash
switch tx := s[i].(type) {
case *RecvTx:
if height > tx.BlockHeight {
break
}
txBlockHeight = tx.BlockHeight
txBlockHash = &tx.BlockHash
case *SendTx:
if height > tx.BlockHeight {
break
}
txBlockHeight = tx.BlockHeight
txBlockHash = &tx.BlockHash
}
if height == txBlockHeight && *hash == *txBlockHash {
endlen = i
}
}
return
}
func (tx *RecvTx) ReadFromVersion(vers uint32, r io.Reader) (n int64, err error) {
if vers >= txVersCurrent {
// Use current version.
return tx.ReadFrom(r)
}
// Old file version did not save the txout index.
datas := []interface{}{
&tx.TxID,
// tx index not read.
&tx.TimeReceived,
&tx.BlockHeight,
&tx.BlockHash,
&tx.BlockIndex,
&tx.BlockTime,
&tx.Amount,
&tx.ReceiverHash,
}
var read int64
for _, data := range datas {
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
}
return n, nil
}
// ReadFrom satisifies the io.ReaderFrom interface. A RecTx is read
// in from r with the format:
//
// TxID (32 bytes)
// TxOutIdx (4 bytes, little endian)
// 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.TxID,
&tx.TxOutIdx,
&tx.TimeReceived,
&tx.BlockHeight,
&tx.BlockHash,
&tx.BlockIndex,
&tx.BlockTime,
&tx.Amount,
&tx.ReceiverHash,
}
var read int64
for _, data := range datas {
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
}
return n, nil
}
// WriteTo satisifies the io.WriterTo interface. A RecvTx is written to
// w in the format:
//
// TxID (32 bytes)
// TxOutIdx (4 bytes, little endian)
// 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.TxID,
&tx.TxOutIdx,
&tx.TimeReceived,
&tx.BlockHeight,
&tx.BlockHash,
&tx.BlockIndex,
&tx.BlockTime,
&tx.Amount,
&tx.ReceiverHash,
}
var written int64
for _, data := range datas {
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
}
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 := "Unknown"
addr, err := btcutil.NewAddressPubKeyHash(tx.ReceiverHash, net)
if err == nil {
address = addr.String()
}
txInfo := map[string]interface{}{
"category": "receive",
"account": account,
"address": address,
"amount": float64(tx.Amount) / float64(btcutil.SatoshiPerBitcoin),
"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
} else {
txInfo["confirmations"] = 0
}
return []map[string]interface{}{txInfo}
}
func (tx *SendTx) ReadFromVersion(vers uint32, r io.Reader) (n int64, err error) {
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 {
switch e := data.(type) {
case ReaderFromVersion:
read, err = e.ReadFromVersion(vers, r)
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
}
return n, nil
}
// ReadFrom satisifies the io.WriterTo interface. A SendTx is read
// from r with the format:
//
// 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) {
return tx.ReadFromVersion(txVersCurrent, r)
}
// WriteTo satisifies the io.WriterTo interface. A SendTx is written to
// w in the format:
//
// 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) {
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 {
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
}
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 {
address := "Unknown"
addr, err := btcutil.NewAddressPubKeyHash(pair.PubkeyHash, net)
if err == nil {
address = addr.String()
}
info := map[string]interface{}{
"account": account,
"address": address,
"category": "send",
"amount": float64(-pair.Amount) / float64(btcutil.SatoshiPerBitcoin),
"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
}