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:
parent
a246fc91d6
commit
413f23ea18
7 changed files with 794 additions and 273 deletions
65
account.go
65
account.go
|
@ -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
42
cmd.go
|
@ -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
126
cmdmgr.go
|
@ -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)
|
||||
|
|
43
createtx.go
43
createtx.go
|
@ -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
|
||||
}
|
||||
|
|
36
sockets.go
36
sockets.go
|
@ -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
667
tx/tx.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue