413f23ea18
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.
307 lines
7.4 KiB
Go
307 lines
7.4 KiB
Go
/*
|
|
* Copyright (c) 2013 Conformal Systems LLC <info@conformal.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
package tx
|
|
|
|
import (
|
|
"bytes"
|
|
"code.google.com/p/go.crypto/ripemd160"
|
|
"github.com/conformal/btcwire"
|
|
"github.com/davecgh/go-spew/spew"
|
|
"io"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
var (
|
|
recvtx = &RecvTx{
|
|
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,
|
|
},
|
|
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,
|
|
},
|
|
}
|
|
|
|
sendtx = &SendTx{
|
|
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,
|
|
},
|
|
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, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
|
30, 31,
|
|
},
|
|
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,
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func TestUtxoWriteRead(t *testing.T) {
|
|
utxo1 := &Utxo{
|
|
AddrHash: [ripemd160.Size]byte{
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
16, 17, 18, 19,
|
|
},
|
|
Out: OutPoint{
|
|
Hash: [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,
|
|
},
|
|
Index: 1,
|
|
},
|
|
Subscript: []byte{},
|
|
Amt: 69,
|
|
Height: 1337,
|
|
}
|
|
bufWriter := &bytes.Buffer{}
|
|
written, err := utxo1.WriteTo(bufWriter)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
utxoBytes := bufWriter.Bytes()
|
|
|
|
utxo2 := new(Utxo)
|
|
read, err := utxo2.ReadFrom(bytes.NewBuffer(utxoBytes))
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if written != read {
|
|
t.Error("Reading and Writing Utxo: Size Mismatch")
|
|
}
|
|
|
|
if !reflect.DeepEqual(utxo1, utxo2) {
|
|
spew.Dump(utxo1, utxo2)
|
|
t.Error("Utxos do not match.")
|
|
}
|
|
|
|
truncatedReadBuf := bytes.NewBuffer(utxoBytes)
|
|
truncatedReadBuf.Truncate(btcwire.HashSize)
|
|
utxo3 := new(Utxo)
|
|
n, err := utxo3.ReadFrom(truncatedReadBuf)
|
|
if err != io.EOF {
|
|
t.Error("Expected err = io.EOF reading from truncated buffer.")
|
|
}
|
|
if n != btcwire.HashSize {
|
|
t.Error("Incorrect number of bytes read from truncated buffer.")
|
|
}
|
|
}
|
|
|
|
func TestUtxoStoreWriteRead(t *testing.T) {
|
|
store1 := new(UtxoStore)
|
|
for i := 0; i < 20; i++ {
|
|
utxo := new(Utxo)
|
|
for j := range utxo.Out.Hash[:] {
|
|
utxo.Out.Hash[j] = byte(i + 1)
|
|
}
|
|
utxo.Out.Index = uint32(i + 2)
|
|
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{}
|
|
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)
|
|
nRead, err := store2.ReadFrom(bufReader)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if nWritten != nRead {
|
|
t.Errorf("Bytes written (%v) does not match bytes read (%v).", nWritten, nRead)
|
|
}
|
|
|
|
if !reflect.DeepEqual(store1, store2) {
|
|
spew.Dump(store1, store2)
|
|
t.Error("Stores do not match.")
|
|
}
|
|
|
|
truncatedLen := 101
|
|
truncatedReadBuf := bytes.NewBuffer(storeBytes[:truncatedLen])
|
|
store3 := new(UtxoStore)
|
|
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.Errorf("Incorrect number of bytes (%v) read from truncated buffer (len %v).", n, truncatedLen)
|
|
}
|
|
}
|
|
|
|
func TestRecvTxWriteRead(t *testing.T) {
|
|
bufWriter := &bytes.Buffer{}
|
|
n, err := recvtx.WriteTo(bufWriter)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
txBytes := bufWriter.Bytes()
|
|
|
|
tx := new(RecvTx)
|
|
n, err = tx.ReadFrom(bytes.NewBuffer(txBytes))
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(recvtx, tx) {
|
|
t.Error("Txs do not match.")
|
|
return
|
|
}
|
|
|
|
truncatedReadBuf := bytes.NewBuffer(txBytes)
|
|
truncatedReadBuf.Truncate(btcwire.HashSize)
|
|
n, err = tx.ReadFrom(truncatedReadBuf)
|
|
if err != io.EOF {
|
|
t.Error("Expected err = io.EOF reading from truncated buffer.")
|
|
return
|
|
}
|
|
if n != btcwire.HashSize {
|
|
t.Error("Incorrect number of bytes read from truncated buffer.")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestSendTxWriteRead(t *testing.T) {
|
|
bufWriter := &bytes.Buffer{}
|
|
n1, err := sendtx.WriteTo(bufWriter)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
txBytes := bufWriter.Bytes()
|
|
|
|
tx := new(SendTx)
|
|
n2, err := tx.ReadFrom(bytes.NewBuffer(txBytes))
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if n1 != n2 {
|
|
t.Errorf("Number of bytes written and read mismatch, %d != %d",
|
|
n1, n2)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(sendtx, tx) {
|
|
t.Error("Txs do not match.")
|
|
return
|
|
}
|
|
|
|
truncatedReadBuf := bytes.NewBuffer(txBytes)
|
|
truncatedReadBuf.Truncate(btcwire.HashSize)
|
|
n, err := tx.ReadFrom(truncatedReadBuf)
|
|
if err != io.EOF {
|
|
t.Error("Expected err = io.EOF reading from truncated buffer.")
|
|
return
|
|
}
|
|
if n != btcwire.HashSize {
|
|
t.Error("Incorrect number of bytes read from truncated buffer.")
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestTxStoreWriteRead(t *testing.T) {
|
|
s := []interface{}{recvtx, sendtx}
|
|
store := TxStore(s)
|
|
|
|
bufWriter := &bytes.Buffer{}
|
|
n1, err := store.WriteTo(bufWriter)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
txsBytes := bufWriter.Bytes()
|
|
|
|
txs := TxStore{}
|
|
n2, err := txs.ReadFrom(bytes.NewBuffer(txsBytes))
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if n1 != n2 {
|
|
t.Error("Number of bytes written and read mismatch.")
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(store, txs) {
|
|
spew.Dump(store, txs)
|
|
t.Error("TxStores do not match.")
|
|
return
|
|
}
|
|
|
|
truncatedReadBuf := bytes.NewBuffer(txsBytes)
|
|
truncatedReadBuf.Truncate(50)
|
|
n, err := txs.ReadFrom(truncatedReadBuf)
|
|
if err != io.EOF {
|
|
t.Error("Expected err = io.EOF reading from truncated buffer.")
|
|
return
|
|
}
|
|
if n != 50 {
|
|
t.Error("Incorrect number of bytes read from truncated buffer.")
|
|
return
|
|
}
|
|
}
|