2013-08-22 19:14:21 +02:00
|
|
|
/*
|
2014-01-09 20:12:20 +01:00
|
|
|
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
2013-08-22 19:14:21 +02:00
|
|
|
*
|
|
|
|
* 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"
|
2013-09-09 20:14:57 +02:00
|
|
|
"errors"
|
2013-08-22 19:14:21 +02:00
|
|
|
"fmt"
|
2013-11-22 19:42:25 +01:00
|
|
|
"github.com/conformal/btcutil"
|
2013-08-22 19:14:21 +02:00
|
|
|
"github.com/conformal/btcwire"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
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")
|
|
|
|
)
|
|
|
|
|
2013-08-26 19:34:18 +02:00
|
|
|
// Byte headers prepending received and sent serialized transactions.
|
2013-08-22 19:14:21 +02:00
|
|
|
const (
|
2013-11-22 19:42:25 +01:00
|
|
|
recvTxHeader byte = iota
|
|
|
|
sendTxHeader
|
|
|
|
)
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2014-01-06 19:35:07 +01:00
|
|
|
// Various UTXO file versions.
|
|
|
|
const (
|
|
|
|
utxoVersFirst uint32 = iota
|
|
|
|
)
|
|
|
|
|
|
|
|
// Various Tx file versions.
|
|
|
|
const (
|
|
|
|
txVersFirst uint32 = iota
|
2013-12-17 19:18:09 +01:00
|
|
|
|
|
|
|
// txVersRecvTxIndex is the version where the txout index
|
|
|
|
// was added to the RecvTx struct.
|
2014-01-06 19:35:07 +01:00
|
|
|
txVersRecvTxIndex
|
2013-12-17 19:18:09 +01:00
|
|
|
|
|
|
|
// txVersMarkSentChange is the version where serialized SentTx
|
|
|
|
// added a flags field, used for marking a sent transaction
|
|
|
|
// as change.
|
2014-01-06 19:35:07 +01:00
|
|
|
txVersMarkSentChange
|
|
|
|
)
|
2013-12-17 19:18:09 +01:00
|
|
|
|
2014-01-06 19:35:07 +01:00
|
|
|
// Current versions.
|
|
|
|
const (
|
2013-12-17 19:18:09 +01:00
|
|
|
utxoVersCurrent = utxoVersFirst
|
2014-01-06 19:35:07 +01:00
|
|
|
txVersCurrent = txVersMarkSentChange
|
2013-08-22 19:14:21 +02:00
|
|
|
)
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// UtxoStore is a type used for holding all Utxo structures for all
|
|
|
|
// addresses in a wallet.
|
|
|
|
type UtxoStore []*Utxo
|
2013-08-22 19:14:21 +02:00
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// Utxo is a type storing information about a single unspent
|
|
|
|
// transaction output.
|
2013-08-22 19:14:21 +02:00
|
|
|
type Utxo struct {
|
2013-10-11 00:44:44 +02:00
|
|
|
AddrHash [ripemd160.Size]byte
|
2013-09-09 20:14:57 +02:00
|
|
|
Out OutPoint
|
|
|
|
Subscript PkScript
|
|
|
|
Amt uint64 // Measured in Satoshis
|
Perform smarter UTXO tracking.
This change fixes many issues with the tracking of unspent transaction
outputs. First, notifications for when UTXOs arse spent are now
requested from btcd, and when spent, will be removed from the
UtxoStore.
Second, when transactions are created, the unconfirmed (not yet in a
block) Utxo (with block height -1 and zeroed block hash) is added to
the wallet's UtxoStore. Notifications for when this UTXO is spent are
also requested from btcd. After the tx appears in a block, because
the UTXO has a pkScript to be spent by another owned wallet address, a
notification with the UTXO will be sent to btcwallet. We already
store the unconfirmed UTXO, so at this point the actual block height
and hash are filled in.
Finally, when calculating the balance, if confirmations is zero,
unconfirmed UTXOs (block height -1) will be included in the balance.
Otherwise, they are ignored.
2013-10-22 15:55:53 +02:00
|
|
|
|
|
|
|
// Height is -1 if Utxo has not yet appeared in a block.
|
Implement address rescanning.
When a wallet is opened, a rescan request will be sent to btcd with
all active addresses from the wallet, to rescan from the last synced
block (now saved to the wallet file) and the current best block.
As multi-account support is further explored, rescan requests should
be batched together to send a single request for all addresses from
all wallets.
This change introduces several changes to the wallet, tx, and utxo
files. Wallet files are still compatible, however, a rescan will try
to start at the genesis block since no correct "last synced to" or
"created at block X" was saved. The tx and utxo files, however, are
not compatible and should be deleted (or an error will occur on read).
If any errors occur opening the utxo file, a rescan will start
beginning at the creation block saved in the wallet.
2013-10-30 02:22:14 +01:00
|
|
|
Height int32
|
Perform smarter UTXO tracking.
This change fixes many issues with the tracking of unspent transaction
outputs. First, notifications for when UTXOs arse spent are now
requested from btcd, and when spent, will be removed from the
UtxoStore.
Second, when transactions are created, the unconfirmed (not yet in a
block) Utxo (with block height -1 and zeroed block hash) is added to
the wallet's UtxoStore. Notifications for when this UTXO is spent are
also requested from btcd. After the tx appears in a block, because
the UTXO has a pkScript to be spent by another owned wallet address, a
notification with the UTXO will be sent to btcwallet. We already
store the unconfirmed UTXO, so at this point the actual block height
and hash are filled in.
Finally, when calculating the balance, if confirmations is zero,
unconfirmed UTXOs (block height -1) will be included in the balance.
Otherwise, they are ignored.
2013-10-22 15:55:53 +02:00
|
|
|
|
|
|
|
// BlockHash is zeroed if Utxo has not yet appeared in a block.
|
2013-09-09 20:14:57 +02:00
|
|
|
BlockHash btcwire.ShaHash
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// OutPoint is a btcwire.OutPoint with custom methods for serialization.
|
2013-09-04 22:16:20 +02:00
|
|
|
type OutPoint btcwire.OutPoint
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// PkScript is a custom type with methods to serialize pubkey scripts
|
|
|
|
// of variable length.
|
|
|
|
type PkScript []byte
|
2013-09-09 19:31:37 +02:00
|
|
|
|
2014-01-23 01:00:10 +01:00
|
|
|
// 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{}
|
2014-02-03 19:29:25 +01:00
|
|
|
GetBlockHeight() int32
|
|
|
|
GetBlockHash() *btcwire.ShaHash
|
|
|
|
GetBlockTime() int64
|
|
|
|
GetTime() int64
|
|
|
|
GetTxID() *btcwire.ShaHash
|
|
|
|
Copy() Tx
|
2014-01-23 01:00:10 +01:00
|
|
|
}
|
2014-01-27 15:30:42 +01:00
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
// TxStore is a slice holding RecvTx and SendTx pointers.
|
2014-01-23 01:00:10 +01:00
|
|
|
type TxStore []Tx
|
2013-08-22 19:14:21 +02:00
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// RecvTx is a type storing information about a transaction that was
|
|
|
|
// received by an address in a wallet.
|
2013-08-22 19:14:21 +02:00
|
|
|
type RecvTx struct {
|
2013-11-22 19:42:25 +01:00
|
|
|
TxID btcwire.ShaHash
|
2013-12-17 19:18:09 +01:00
|
|
|
TxOutIdx uint32
|
2013-11-22 19:42:25 +01:00
|
|
|
TimeReceived int64
|
|
|
|
BlockHeight int32
|
2013-09-09 20:14:57 +02:00
|
|
|
BlockHash btcwire.ShaHash
|
2013-11-22 19:42:25 +01:00
|
|
|
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
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
func (p *Pairs) ReadFromVersion(vers uint32, r io.Reader) (int64, error) {
|
2013-11-22 19:42:25 +01:00
|
|
|
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 {
|
2013-12-17 19:18:09 +01:00
|
|
|
n, err := s[i].ReadFromVersion(vers, r)
|
2013-11-22 19:42:25 +01:00
|
|
|
if err != nil {
|
|
|
|
return read + n, err
|
|
|
|
}
|
|
|
|
read += n
|
|
|
|
}
|
|
|
|
|
|
|
|
*p = s
|
|
|
|
return read, nil
|
|
|
|
}
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
func (p *Pairs) ReadFrom(r io.Reader) (int64, error) {
|
|
|
|
return p.ReadFromVersion(txVersCurrent, r)
|
|
|
|
}
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
// 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
|
2013-12-17 19:18:09 +01:00
|
|
|
Change bool
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Enforce that Pair satisifies the io.ReaderFrom and io.WriterTo
|
|
|
|
// interfaces.
|
|
|
|
var _ io.ReaderFrom = &Pair{}
|
|
|
|
var _ io.WriterTo = &Pair{}
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
// 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))
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
// 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
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
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)
|
|
|
|
|
2014-01-06 19:35:07 +01:00
|
|
|
// 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)
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
return written, nil
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// SendTx is a type storing information about a transaction that was
|
|
|
|
// sent by an address in a wallet.
|
2013-08-22 19:14:21 +02:00
|
|
|
type SendTx struct {
|
2013-11-22 19:42:25 +01:00
|
|
|
TxID btcwire.ShaHash
|
|
|
|
Time int64
|
|
|
|
BlockHeight int32
|
|
|
|
BlockHash btcwire.ShaHash
|
|
|
|
BlockIndex int32
|
|
|
|
BlockTime int64
|
|
|
|
Fee int64 // Measured in Satoshis
|
|
|
|
Receivers Pairs
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2013-09-09 20:14:57 +02:00
|
|
|
if read < binary.Size(data) {
|
|
|
|
return int64(read), io.EOF
|
|
|
|
}
|
2013-08-22 19:14:21 +02:00
|
|
|
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.
|
2013-11-22 19:42:25 +01:00
|
|
|
func (u *UtxoStore) ReadFrom(r io.Reader) (int64, error) {
|
2013-08-22 19:14:21 +02:00
|
|
|
var read int64
|
2013-11-22 19:42:25 +01:00
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
for {
|
|
|
|
// Read Utxo
|
|
|
|
utxo := new(Utxo)
|
2013-11-22 19:42:25 +01:00
|
|
|
n, err := utxo.ReadFrom(r)
|
2013-08-22 19:14:21 +02:00
|
|
|
if err != nil {
|
2013-11-22 19:42:25 +01:00
|
|
|
if n == 0 && err == io.EOF {
|
|
|
|
err = nil
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
return read + n, err
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
read += n
|
2013-09-09 20:14:57 +02:00
|
|
|
*u = append(*u, utxo)
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2013-11-22 19:42:25 +01:00
|
|
|
func (u *UtxoStore) WriteTo(w io.Writer) (int64, error) {
|
2013-08-22 19:14:21 +02:00
|
|
|
var written int64
|
2013-11-22 19:42:25 +01:00
|
|
|
|
|
|
|
// Write file version. This is currently not used.
|
|
|
|
versionBytes := make([]byte, 4) // bytes for a uint32
|
2013-12-17 19:18:09 +01:00
|
|
|
binary.LittleEndian.PutUint32(versionBytes, utxoVersCurrent)
|
2013-11-22 19:42:25 +01:00
|
|
|
n, err := w.Write(versionBytes)
|
|
|
|
if err != nil {
|
|
|
|
return int64(n), err
|
|
|
|
}
|
|
|
|
written = int64(n)
|
|
|
|
|
|
|
|
// Write each utxo in the store.
|
2013-09-09 20:14:57 +02:00
|
|
|
for _, utxo := range *u {
|
2013-08-22 19:14:21 +02:00
|
|
|
// Write Utxo
|
2013-11-22 19:42:25 +01:00
|
|
|
n, err := utxo.WriteTo(w)
|
2013-08-22 19:14:21 +02:00
|
|
|
if err != nil {
|
2013-11-22 19:42:25 +01:00
|
|
|
return written + n, err
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
written += n
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
return written, nil
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// 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.
|
Implement address rescanning.
When a wallet is opened, a rescan request will be sent to btcd with
all active addresses from the wallet, to rescan from the last synced
block (now saved to the wallet file) and the current best block.
As multi-account support is further explored, rescan requests should
be batched together to send a single request for all addresses from
all wallets.
This change introduces several changes to the wallet, tx, and utxo
files. Wallet files are still compatible, however, a rescan will try
to start at the genesis block since no correct "last synced to" or
"created at block X" was saved. The tx and utxo files, however, are
not compatible and should be deleted (or an error will occur on read).
If any errors occur opening the utxo file, a rescan will start
beginning at the creation block saved in the wallet.
2013-10-30 02:22:14 +01:00
|
|
|
func (u *UtxoStore) Rollback(height int32, hash *btcwire.ShaHash) (modified bool) {
|
2013-09-09 20:14:57 +02:00
|
|
|
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
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-09-09 20:14:57 +02:00
|
|
|
*u = s[:endlen]
|
|
|
|
return
|
|
|
|
}()
|
2013-08-22 19:14:21 +02:00
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
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
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
}
|
2013-09-09 20:14:57 +02:00
|
|
|
return
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
|
2013-10-15 16:05:51 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
// ReadFrom satisifies the io.ReaderFrom interface. A Utxo is read
|
|
|
|
// from r with the format:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// AddrHash (20 bytes)
|
|
|
|
// Out (36 bytes)
|
|
|
|
// Subscript (varies)
|
|
|
|
// Amt (8 bytes, little endian)
|
|
|
|
// Height (4 bytes, little endian)
|
|
|
|
// BlockHash (32 bytes)
|
2013-08-22 19:14:21 +02:00
|
|
|
func (u *Utxo) ReadFrom(r io.Reader) (n int64, err error) {
|
|
|
|
datas := []interface{}{
|
2013-10-11 00:44:44 +02:00
|
|
|
&u.AddrHash,
|
2013-09-04 22:16:20 +02:00
|
|
|
&u.Out,
|
2013-09-09 19:31:37 +02:00
|
|
|
&u.Subscript,
|
2013-08-22 19:14:21 +02:00
|
|
|
&u.Amt,
|
|
|
|
&u.Height,
|
2013-09-09 20:14:57 +02:00
|
|
|
&u.BlockHash,
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
var read int64
|
|
|
|
for _, data := range datas {
|
2013-09-04 22:16:20 +02:00
|
|
|
if rf, ok := data.(io.ReaderFrom); ok {
|
|
|
|
read, err = rf.ReadFrom(r)
|
|
|
|
} else {
|
|
|
|
read, err = binaryRead(r, binary.LittleEndian, data)
|
|
|
|
}
|
2013-08-22 19:14:21 +02:00
|
|
|
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:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// AddrHash (20 bytes)
|
|
|
|
// Out (36 bytes)
|
|
|
|
// Subscript (varies)
|
|
|
|
// Amt (8 bytes, little endian)
|
|
|
|
// Height (4 bytes, little endian)
|
|
|
|
// BlockHash (32 bytes)
|
2013-08-22 19:14:21 +02:00
|
|
|
func (u *Utxo) WriteTo(w io.Writer) (n int64, err error) {
|
|
|
|
datas := []interface{}{
|
2013-10-11 00:44:44 +02:00
|
|
|
&u.AddrHash,
|
2013-09-04 22:16:20 +02:00
|
|
|
&u.Out,
|
2013-09-09 19:31:37 +02:00
|
|
|
&u.Subscript,
|
2013-08-22 19:14:21 +02:00
|
|
|
&u.Amt,
|
|
|
|
&u.Height,
|
2013-09-09 20:14:57 +02:00
|
|
|
&u.BlockHash,
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
var written int64
|
2013-09-04 22:16:20 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-09-09 19:31:37 +02:00
|
|
|
// ReadFrom satisifies the io.ReaderFrom interface. An OutPoint is read
|
2013-09-04 22:16:20 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2013-09-09 19:31:37 +02:00
|
|
|
// WriteTo satisifies the io.WriterTo interface. An OutPoint is written
|
2013-09-04 22:16:20 +02:00
|
|
|
// 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
|
2013-08-22 19:14:21 +02:00
|
|
|
for _, data := range datas {
|
|
|
|
written, err = binaryWrite(w, binary.LittleEndian, data)
|
|
|
|
if err != nil {
|
|
|
|
return n + written, err
|
|
|
|
}
|
|
|
|
n += written
|
|
|
|
}
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// ReadFrom satisifies the io.ReaderFrom interface. A PkScript is read
|
2013-09-09 19:31:37 +02:00
|
|
|
// from r with the format:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// Length (4 byte, little endian)
|
|
|
|
// ScriptBytes (Length bytes)
|
2013-09-09 20:14:57 +02:00
|
|
|
func (s *PkScript) ReadFrom(r io.Reader) (n int64, err error) {
|
2013-09-09 19:31:37 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// WriteTo satisifies the io.WriterTo interface. A PkScript is written
|
2013-09-09 19:31:37 +02:00
|
|
|
// to w in the format:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// Length (4 byte, little endian)
|
|
|
|
// ScriptBytes (Length bytes)
|
2013-09-09 20:14:57 +02:00
|
|
|
func (s *PkScript) WriteTo(w io.Writer) (n int64, err error) {
|
2013-09-09 19:31:37 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
// ReadFrom satisifies the io.ReaderFrom interface. A TxStore is read
|
|
|
|
// in from r with the format:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// Version (4 bytes, little endian)
|
|
|
|
// [(TxHeader (1 byte), Tx (varies in size))...]
|
|
|
|
func (txs *TxStore) ReadFrom(r io.Reader) (int64, error) {
|
|
|
|
var read int64
|
|
|
|
|
2014-01-06 19:35:07 +01:00
|
|
|
// Read the file version.
|
2013-11-22 19:42:25 +01:00
|
|
|
versionBytes := make([]byte, 4) // bytes for a uint32
|
|
|
|
n, err := r.Read(versionBytes)
|
|
|
|
if err != nil {
|
|
|
|
return int64(n), err
|
|
|
|
}
|
2013-12-17 19:18:09 +01:00
|
|
|
vers := binary.LittleEndian.Uint32(versionBytes)
|
2013-11-22 19:42:25 +01:00
|
|
|
read += int64(n)
|
|
|
|
|
2014-01-23 01:00:10 +01:00
|
|
|
store := []Tx{}
|
2013-08-22 19:14:21 +02:00
|
|
|
defer func() {
|
|
|
|
*txs = store
|
|
|
|
}()
|
|
|
|
for {
|
|
|
|
// Read header
|
|
|
|
var header byte
|
2013-11-22 19:42:25 +01:00
|
|
|
n, err := binaryRead(r, binary.LittleEndian, &header)
|
2013-08-22 19:14:21 +02:00
|
|
|
if err != nil {
|
|
|
|
// io.EOF is not an error here.
|
|
|
|
if err == io.EOF {
|
2013-11-22 19:42:25 +01:00
|
|
|
err = nil
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
return read + n, err
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
read += n
|
2013-08-22 19:14:21 +02:00
|
|
|
|
2014-01-23 01:00:10 +01:00
|
|
|
var tx Tx
|
2013-12-17 19:18:09 +01:00
|
|
|
// Read tx.
|
2013-08-22 19:14:21 +02:00
|
|
|
switch header {
|
2013-11-22 19:42:25 +01:00
|
|
|
case recvTxHeader:
|
2013-12-17 19:18:09 +01:00
|
|
|
t := new(RecvTx)
|
|
|
|
n, err = t.ReadFromVersion(vers, r)
|
|
|
|
if err != nil {
|
|
|
|
return read + n, err
|
|
|
|
}
|
|
|
|
read += n
|
|
|
|
tx = t
|
2013-11-22 19:42:25 +01:00
|
|
|
|
|
|
|
case sendTxHeader:
|
2013-12-17 19:18:09 +01:00
|
|
|
t := new(SendTx)
|
|
|
|
n, err = t.ReadFromVersion(vers, r)
|
|
|
|
if err != nil {
|
|
|
|
return read + n, err
|
|
|
|
}
|
|
|
|
read += n
|
|
|
|
tx = t
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
default:
|
2013-09-09 20:14:57 +02:00
|
|
|
return n, fmt.Errorf("unknown Tx header")
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
|
2013-08-26 19:34:18 +02:00
|
|
|
store = append(store, tx)
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteTo satisifies the io.WriterTo interface. A TxStore is written
|
|
|
|
// to w in the format:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// Version (4 bytes, little endian)
|
|
|
|
// [(TxHeader (1 byte), Tx (varies in size))...]
|
|
|
|
func (txs *TxStore) WriteTo(w io.Writer) (int64, error) {
|
2013-08-22 19:14:21 +02:00
|
|
|
var written int64
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2014-01-06 19:35:07 +01:00
|
|
|
// Write file version.
|
2013-11-22 19:42:25 +01:00
|
|
|
versionBytes := make([]byte, 4) // bytes for a uint32
|
2014-01-06 19:35:07 +01:00
|
|
|
binary.LittleEndian.PutUint32(versionBytes, txVersCurrent)
|
2013-11-22 19:42:25 +01:00
|
|
|
n, err := w.Write(versionBytes)
|
|
|
|
if err != nil {
|
|
|
|
return int64(n), err
|
|
|
|
}
|
|
|
|
written = int64(n)
|
|
|
|
|
2014-01-23 01:00:10 +01:00
|
|
|
store := ([]Tx)(*txs)
|
2013-08-22 19:14:21 +02:00
|
|
|
for _, tx := range store {
|
2014-01-06 19:35:07 +01:00
|
|
|
// Write header for tx.
|
|
|
|
var header byte
|
2013-08-22 19:14:21 +02:00
|
|
|
switch tx.(type) {
|
|
|
|
case *RecvTx:
|
2014-01-06 19:35:07 +01:00
|
|
|
header = recvTxHeader
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
case *SendTx:
|
2014-01-06 19:35:07 +01:00
|
|
|
header = sendTxHeader
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
default:
|
2013-11-22 19:42:25 +01:00
|
|
|
return written, fmt.Errorf("unknown type in TxStore")
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2014-01-06 19:35:07 +01:00
|
|
|
headerBytes := []byte{header}
|
|
|
|
n, err := w.Write(headerBytes)
|
|
|
|
if err != nil {
|
|
|
|
return written + int64(n), err
|
|
|
|
}
|
|
|
|
written += int64(n)
|
|
|
|
|
|
|
|
// Write tx.
|
2013-08-22 19:14:21 +02:00
|
|
|
wt := tx.(io.WriterTo)
|
2014-01-06 19:35:07 +01:00
|
|
|
n64, err := wt.WriteTo(w)
|
2013-08-22 19:14:21 +02:00
|
|
|
if err != nil {
|
2014-01-06 19:35:07 +01:00
|
|
|
return written + n64, err
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2014-01-06 19:35:07 +01:00
|
|
|
written += n64
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
return written, nil
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// 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.
|
Implement address rescanning.
When a wallet is opened, a rescan request will be sent to btcd with
all active addresses from the wallet, to rescan from the last synced
block (now saved to the wallet file) and the current best block.
As multi-account support is further explored, rescan requests should
be batched together to send a single request for all addresses from
all wallets.
This change introduces several changes to the wallet, tx, and utxo
files. Wallet files are still compatible, however, a rescan will try
to start at the genesis block since no correct "last synced to" or
"created at block X" was saved. The tx and utxo files, however, are
not compatible and should be deleted (or an error will occur on read).
If any errors occur opening the utxo file, a rescan will start
beginning at the creation block saved in the wallet.
2013-10-30 02:22:14 +01:00
|
|
|
func (txs *TxStore) Rollback(height int32, hash *btcwire.ShaHash) (modified bool) {
|
2014-01-23 01:00:10 +01:00
|
|
|
s := ([]Tx)(*txs)
|
2013-09-09 20:14:57 +02:00
|
|
|
|
|
|
|
// 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-- {
|
2013-11-22 19:42:25 +01:00
|
|
|
var txBlockHeight int32
|
|
|
|
var txBlockHash *btcwire.ShaHash
|
|
|
|
switch tx := s[i].(type) {
|
2013-09-09 20:14:57 +02:00
|
|
|
case *RecvTx:
|
2013-11-22 19:42:25 +01:00
|
|
|
if height > tx.BlockHeight {
|
2013-09-09 20:14:57 +02:00
|
|
|
break
|
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
txBlockHeight = tx.BlockHeight
|
|
|
|
txBlockHash = &tx.BlockHash
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
case *SendTx:
|
2013-11-22 19:42:25 +01:00
|
|
|
if height > tx.BlockHeight {
|
2013-09-09 20:14:57 +02:00
|
|
|
break
|
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
txBlockHeight = tx.BlockHeight
|
|
|
|
txBlockHash = &tx.BlockHash
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
if height == txBlockHeight && *hash == *txBlockHash {
|
2013-09-09 20:14:57 +02:00
|
|
|
endlen = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
// ReadFrom satisifies the io.ReaderFrom interface. A RecTx is read
|
|
|
|
// in from r with the format:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// TxID (32 bytes)
|
2013-12-17 19:18:09 +01:00
|
|
|
// TxOutIdx (4 bytes, little endian)
|
2013-11-22 19:42:25 +01:00
|
|
|
// 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)
|
2013-08-22 19:14:21 +02:00
|
|
|
func (tx *RecvTx) ReadFrom(r io.Reader) (n int64, err error) {
|
|
|
|
datas := []interface{}{
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.TxID,
|
2013-12-17 19:18:09 +01:00
|
|
|
&tx.TxOutIdx,
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.TimeReceived,
|
|
|
|
&tx.BlockHeight,
|
2013-09-09 20:14:57 +02:00
|
|
|
&tx.BlockHash,
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.BlockIndex,
|
|
|
|
&tx.BlockTime,
|
|
|
|
&tx.Amount,
|
|
|
|
&tx.ReceiverHash,
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
var read int64
|
|
|
|
for _, data := range datas {
|
2013-11-22 19:42:25 +01:00
|
|
|
switch e := data.(type) {
|
|
|
|
case io.ReaderFrom:
|
|
|
|
read, err = e.ReadFrom(r)
|
|
|
|
default:
|
|
|
|
read, err = binaryRead(r, binary.LittleEndian, data)
|
|
|
|
}
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
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:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// TxID (32 bytes)
|
2014-01-06 19:35:07 +01:00
|
|
|
// TxOutIdx (4 bytes, little endian)
|
2013-11-22 19:42:25 +01:00
|
|
|
// 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)
|
2013-08-22 19:14:21 +02:00
|
|
|
func (tx *RecvTx) WriteTo(w io.Writer) (n int64, err error) {
|
|
|
|
datas := []interface{}{
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.TxID,
|
2014-01-06 19:35:07 +01:00
|
|
|
&tx.TxOutIdx,
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.TimeReceived,
|
|
|
|
&tx.BlockHeight,
|
2013-09-09 20:14:57 +02:00
|
|
|
&tx.BlockHash,
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.BlockIndex,
|
|
|
|
&tx.BlockTime,
|
|
|
|
&tx.Amount,
|
|
|
|
&tx.ReceiverHash,
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
var written int64
|
|
|
|
for _, data := range datas {
|
2013-11-22 19:42:25 +01:00
|
|
|
switch e := data.(type) {
|
|
|
|
case io.WriterTo:
|
|
|
|
written, err = e.WriteTo(w)
|
|
|
|
default:
|
|
|
|
written, err = binaryWrite(w, binary.LittleEndian, data)
|
|
|
|
}
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return n + written, err
|
|
|
|
}
|
|
|
|
n += written
|
|
|
|
}
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
// 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,
|
2014-01-23 01:00:10 +01:00
|
|
|
net btcwire.BitcoinNet) []map[string]interface{} {
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2014-01-06 18:24:29 +01:00
|
|
|
address := "Unknown"
|
|
|
|
addr, err := btcutil.NewAddressPubKeyHash(tx.ReceiverHash, net)
|
|
|
|
if err == nil {
|
|
|
|
address = addr.String()
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
txInfo := map[string]interface{}{
|
|
|
|
"category": "receive",
|
|
|
|
"account": account,
|
|
|
|
"address": address,
|
2013-11-27 19:55:14 +01:00
|
|
|
"amount": float64(tx.Amount) / float64(btcutil.SatoshiPerBitcoin),
|
2013-11-22 19:42:25 +01:00
|
|
|
"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
|
2013-12-17 19:18:09 +01:00
|
|
|
} else {
|
|
|
|
txInfo["confirmations"] = 0
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
|
|
|
|
2014-01-23 01:00:10 +01:00
|
|
|
return []map[string]interface{}{txInfo}
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
|
|
|
|
2014-02-03 19:29:25 +01:00
|
|
|
// GetBlockHeight returns the current blockheight of the transaction,
|
|
|
|
// implementing the Tx interface.
|
|
|
|
func (tx *RecvTx) GetBlockHeight() int32 {
|
2014-01-27 18:53:32 +01:00
|
|
|
return tx.BlockHeight
|
|
|
|
}
|
|
|
|
|
2014-02-03 19:29:25 +01:00
|
|
|
// GetBlockHash return the current blockhash of thet transaction, implementing
|
|
|
|
// the Tx interface.
|
|
|
|
func (tx *RecvTx) GetBlockHash() *btcwire.ShaHash {
|
|
|
|
return &tx.BlockHash
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetBlockTime returns the current block time of the transaction, implementing
|
|
|
|
// the Tx interface.
|
|
|
|
func (tx *RecvTx) GetBlockTime() int64 {
|
|
|
|
return tx.BlockTime
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTime returns the current ID of the transaction, implementing the Tx
|
|
|
|
// interface.
|
|
|
|
func (tx *RecvTx) GetTime() int64 {
|
|
|
|
return tx.TimeReceived
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTxID returns the current ID of the transaction, implementing the Tx
|
|
|
|
// interface.
|
|
|
|
func (tx *RecvTx) GetTxID() *btcwire.ShaHash {
|
|
|
|
return &tx.TxID
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy returns a deep copy of the structure, implementing the Tx interface..
|
|
|
|
func (tx *RecvTx) Copy() Tx {
|
|
|
|
copyTx := *tx
|
|
|
|
|
|
|
|
return ©Tx
|
|
|
|
}
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
func (tx *SendTx) ReadFromVersion(vers uint32, r io.Reader) (n int64, err error) {
|
2013-11-22 19:42:25 +01:00
|
|
|
var read int64
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
datas := []interface{}{
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.TxID,
|
|
|
|
&tx.Time,
|
|
|
|
&tx.BlockHeight,
|
|
|
|
&tx.BlockHash,
|
|
|
|
&tx.BlockIndex,
|
|
|
|
&tx.BlockTime,
|
2013-08-27 18:39:45 +02:00
|
|
|
&tx.Fee,
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.Receivers,
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
for _, data := range datas {
|
2013-11-22 19:42:25 +01:00
|
|
|
switch e := data.(type) {
|
2014-01-06 19:35:07 +01:00
|
|
|
case ReaderFromVersion:
|
|
|
|
read, err = e.ReadFromVersion(vers, r)
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
case io.ReaderFrom:
|
|
|
|
read, err = e.ReadFrom(r)
|
2014-01-06 19:35:07 +01:00
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
default:
|
|
|
|
read, err = binaryRead(r, binary.LittleEndian, data)
|
|
|
|
}
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return n + read, err
|
|
|
|
}
|
|
|
|
n += read
|
|
|
|
}
|
|
|
|
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// WriteTo satisifies the io.WriterTo interface. A SendTx is written to
|
2013-08-22 19:14:21 +02:00
|
|
|
// w in the format:
|
|
|
|
//
|
2013-11-22 19:42:25 +01:00
|
|
|
// 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)
|
2013-08-22 19:14:21 +02:00
|
|
|
func (tx *SendTx) WriteTo(w io.Writer) (n int64, err error) {
|
2013-11-22 19:42:25 +01:00
|
|
|
var written int64
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
datas := []interface{}{
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.TxID,
|
|
|
|
&tx.Time,
|
|
|
|
&tx.BlockHeight,
|
|
|
|
&tx.BlockHash,
|
|
|
|
&tx.BlockIndex,
|
|
|
|
&tx.BlockTime,
|
2013-08-27 18:39:45 +02:00
|
|
|
&tx.Fee,
|
2013-11-22 19:42:25 +01:00
|
|
|
&tx.Receivers,
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
|
|
|
for _, data := range datas {
|
2013-11-22 19:42:25 +01:00
|
|
|
switch e := data.(type) {
|
|
|
|
case io.WriterTo:
|
|
|
|
written, err = e.WriteTo(w)
|
|
|
|
default:
|
|
|
|
written, err = binaryWrite(w, binary.LittleEndian, data)
|
|
|
|
}
|
|
|
|
|
2013-08-22 19:14:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return n + written, err
|
|
|
|
}
|
|
|
|
n += written
|
|
|
|
}
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
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 {
|
2014-01-06 18:24:29 +01:00
|
|
|
address := "Unknown"
|
|
|
|
addr, err := btcutil.NewAddressPubKeyHash(pair.PubkeyHash, net)
|
|
|
|
if err == nil {
|
|
|
|
address = addr.String()
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
info := map[string]interface{}{
|
|
|
|
"account": account,
|
|
|
|
"address": address,
|
|
|
|
"category": "send",
|
2013-11-27 19:55:14 +01:00
|
|
|
"amount": float64(-pair.Amount) / float64(btcutil.SatoshiPerBitcoin),
|
2013-11-22 19:42:25 +01:00
|
|
|
"fee": float64(tx.Fee) / float64(btcutil.SatoshiPerBitcoin),
|
|
|
|
"confirmations": confirmations,
|
|
|
|
"txid": txIDStr,
|
|
|
|
"time": tx.Time,
|
|
|
|
"timereceived": tx.Time,
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
if tx.BlockHeight != -1 {
|
|
|
|
info["blockhash"] = blockHashStr
|
|
|
|
info["blockindex"] = tx.BlockIndex
|
|
|
|
info["blocktime"] = tx.BlockTime
|
|
|
|
}
|
|
|
|
reply[i] = info
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
|
|
|
|
return reply
|
2013-08-22 19:14:21 +02:00
|
|
|
}
|
2014-01-27 18:53:32 +01:00
|
|
|
|
2014-02-03 19:29:25 +01:00
|
|
|
// GetBlockHeight returns the current blockheight of the transaction,
|
|
|
|
// implementing the Tx interface.
|
|
|
|
func (tx *SendTx) GetBlockHeight() int32 {
|
2014-01-27 18:53:32 +01:00
|
|
|
return tx.BlockHeight
|
|
|
|
}
|
2014-02-03 19:29:25 +01:00
|
|
|
|
|
|
|
// GetBlockHash return the current blockhash of thet transaction, implementing
|
|
|
|
// the Tx interface.
|
|
|
|
func (tx *SendTx) GetBlockHash() *btcwire.ShaHash {
|
|
|
|
return &tx.BlockHash
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetBlockTime returns the current block time of the transaction, implementing
|
|
|
|
// the Tx interface.
|
|
|
|
func (tx *SendTx) GetBlockTime() int64 {
|
|
|
|
return tx.BlockTime
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTime returns the current ID of the transaction, implementing the Tx
|
|
|
|
// interface.
|
|
|
|
func (tx *SendTx) GetTime() int64 {
|
|
|
|
return tx.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTxID returns the current ID of the transaction, implementing the Tx
|
|
|
|
// interface.
|
|
|
|
func (tx *SendTx) GetTxID() *btcwire.ShaHash {
|
|
|
|
return &tx.TxID
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy returns a deep copy of the structure, implementing the Tx interface..
|
|
|
|
func (tx *SendTx) Copy() Tx {
|
|
|
|
copyTx := *tx
|
|
|
|
|
|
|
|
return ©Tx
|
|
|
|
}
|