lbcwallet/createtx_test.go
Dave Collins 8f9f53a618 Switch to new waddrmgr package
This commit converts the wallet to use the new secure hierarchical
deterministic wallet address manager package as well as the walletdb
package.

The following is an overview of modified functionality:

- The wallet must now be created before starting the executable
- A new flag --create has been added to create the new wallet using wizard
  style question and answer prompts
- Starting the process without an existing wallet will instruct now
  display a message to run it with --create
- Providing the --create flag with an existing wallet will simply show an
  error and return

In addition the snacl package has been modified to return the memory after
performing scrypt operations to the OS.

Previously a runtime.GC was being invoked which forced it to release the
memory as far as the garbage collector is concerned, but the memory was
not released back to the OS immediatley.  This modification allows the
memory to be released immedately since it won't be needed again until the
next wallet unlock.
2015-03-02 11:55:42 -06:00

234 lines
7.8 KiB
Go

package main
import (
"encoding/hex"
"os"
"path/filepath"
"reflect"
"sort"
"testing"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb"
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
)
// This is a tx that transfers funds (0.371 BTC) to addresses of known privKeys.
// It contains 6 outputs, in this order, with the following values/addresses:
// {0: 0.2283 (addr: myVT6o4GfR57Cfw7pP3vayfHZzMHh2BxXJ - change),
// 1: 0.03 (addr: mjqnv9JoxdYyQK7NMZGCKLxNWHfA6XFVC7),
// 2: 0.09 (addr: mqi4izJxVr9wRJmoHe3CUjdb7YDzpJmTwr),
// 3: 0.1 (addr: mu7q5vxiGCXYKXEtvspP77bYxjnsEobJGv),
// 4: 0.15 (addr: mw66YGmegSNv3yfS4brrtj6ZfAZ4DMmhQN),
// 5: 0.001 (addr: mgLBkENLdGXXMfu5RZYPuhJdC88UgvsAxY)}
var txInfo = struct {
hex string
amount btcutil.Amount
privKeys []string
}{
hex: "010000000113918955c6ba3c7a2e8ec02ca3e91a2571cb11ade7d5c3e9c1a73b3ac8309d74000000006b483045022100a6f33d4ad476d126ee45e19e43190971e148a1e940abe4165bc686d22ac847e502200936efa4da4225787d4b7e11e8f3389dba626817d7ece0cab38b4f456b0880d6012103ccb8b1038ad6af10a15f68e8d5e347c08befa6cc2ab1718a37e3ea0e38102b92ffffffff06b05b5c01000000001976a914c5297a660cef8088b8472755f4827df7577c612988acc0c62d00000000001976a9142f7094083d750bdfc1f2fad814779e2dde35ce2088ac40548900000000001976a9146fcb336a187619ca20b84af9eac9fbff68d1061d88ac80969800000000001976a91495322d12e18345f4855cbe863d4a8ebcc0e95e0188acc0e1e400000000001976a914aace7f06f94fa298685f6e58769543993fa5fae888aca0860100000000001976a91408eec7602655fdb2531f71070cca4c363c3a15ab88ac00000000",
amount: btcutil.Amount(3e6 + 9e6 + 1e7 + 1.5e7 + 1e5),
privKeys: []string{
"cSYUVdPL6pkabu7Fxp4PaKqYjJFz2Aopw5ygunFbek9HAimLYxp4",
"cVnNzZm3DiwkN1Ghs4W8cwcJC9f6TynCCcqzYt8n1c4hwjN2PfTw",
"cUgo8PrKj7NzttKRMKwgF3ahXNrLA253pqjWkPGS7Z9iZcKT8EKG",
"cSosEHx1freK7B1B6QicPcrH1h5VqReSHew6ZYhv6ntiUJRhowRc",
"cR9ApAZ3FLtRMfqRBEr3niD9Mmmvfh3V8Uh56qfJ5b4bFH8ibDkA"}}
var (
outAddr1 = "1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX"
outAddr2 = "12MzCDwodF9G1e7jfwLXfR164RNtx4BRVG"
)
// fastScrypt are options to passed to the wallet address manager to speed up
// the scrypt derivations.
var fastScrypt = &waddrmgr.Options{
ScryptN: 16,
ScryptR: 8,
ScryptP: 1,
}
func Test_addOutputs(t *testing.T) {
msgtx := wire.NewMsgTx()
pairs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1}
if _, err := addOutputs(msgtx, pairs); err != nil {
t.Fatal(err)
}
if len(msgtx.TxOut) != 2 {
t.Fatalf("Expected 2 outputs, found only %d", len(msgtx.TxOut))
}
values := []int{int(msgtx.TxOut[0].Value), int(msgtx.TxOut[1].Value)}
sort.Ints(values)
if !reflect.DeepEqual(values, []int{1, 10}) {
t.Fatalf("Expected values to be [1, 10], got: %v", values)
}
}
func TestCreateTx(t *testing.T) {
cfg = &config{DisallowFree: false}
bs := &waddrmgr.BlockStamp{Height: 11111}
mgr := newManager(t, txInfo.privKeys, bs)
changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", activeNet.Params)
var tstChangeAddress = func(bs *waddrmgr.BlockStamp) (btcutil.Address, error) {
return changeAddr, nil
}
// Pick all utxos from txInfo as eligible input.
eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1, 2, 3, 4, 5})
// Now create a new TX sending 25e6 satoshis to the following addresses:
outputs := map[string]btcutil.Amount{outAddr1: 15e6, outAddr2: 10e6}
tx, err := createTx(eligible, outputs, bs, defaultFeeIncrement, mgr, tstChangeAddress)
if err != nil {
t.Fatal(err)
}
if tx.changeAddr.String() != changeAddr.String() {
t.Fatalf("Unexpected change address; got %v, want %v",
tx.changeAddr.String(), changeAddr.String())
}
msgTx := tx.tx.MsgTx()
if len(msgTx.TxOut) != 3 {
t.Fatalf("Unexpected number of outputs; got %d, want 3", len(msgTx.TxOut))
}
// The outputs in our new TX amount to 25e6 satoshis, so to fulfil that
// createTx should have picked the utxos with indices 4, 3 and 5, which
// total 25.1e6.
if len(msgTx.TxIn) != 3 {
t.Fatalf("Unexpected number of inputs; got %d, want 3", len(msgTx.TxIn))
}
// Given the input (15e6 + 10e6 + 1e7) and requested output (15e6 + 10e6)
// amounts in the new TX, we should have a change output with 8.99e6, which
// implies a fee of 1e3 satoshis.
expectedChange := btcutil.Amount(8.999e6)
outputs[changeAddr.String()] = expectedChange
checkOutputsMatch(t, msgTx, outputs)
minFee := feeForSize(defaultFeeIncrement, msgTx.SerializeSize())
actualFee := btcutil.Amount(1e3)
if minFee > actualFee {
t.Fatalf("Requested fee (%v) for tx size higher than actual fee (%v)", minFee, actualFee)
}
}
func TestCreateTxInsufficientFundsError(t *testing.T) {
cfg = &config{DisallowFree: false}
outputs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1e9}
eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1})
bs := &waddrmgr.BlockStamp{Height: 11111}
changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", activeNet.Params)
var tstChangeAddress = func(bs *waddrmgr.BlockStamp) (btcutil.Address, error) {
return changeAddr, nil
}
_, err := createTx(eligible, outputs, bs, defaultFeeIncrement, nil, tstChangeAddress)
if err == nil {
t.Error("Expected InsufficientFundsError, got no error")
} else if _, ok := err.(InsufficientFundsError); !ok {
t.Errorf("Unexpected error, got %v, want InsufficientFundsError", err)
}
}
// checkOutputsMatch checks that the outputs in the tx match the expected ones.
func checkOutputsMatch(t *testing.T, msgtx *wire.MsgTx, expected map[string]btcutil.Amount) {
// This is a bit convoluted because the index of the change output is randomized.
for addrStr, v := range expected {
addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params)
if err != nil {
t.Fatalf("Cannot decode address: %v", err)
}
pkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("Cannot create pkScript: %v", err)
}
found := false
for _, txout := range msgtx.TxOut {
if reflect.DeepEqual(txout.PkScript, pkScript) && txout.Value == int64(v) {
found = true
break
}
}
if !found {
t.Fatalf("PkScript %v not found in msgtx.TxOut: %v", pkScript, msgtx.TxOut)
}
}
}
// newManager creates a new waddrmgr and imports the given privKey into it.
func newManager(t *testing.T, privKeys []string, bs *waddrmgr.BlockStamp) *waddrmgr.Manager {
dbPath := filepath.Join(os.TempDir(), "wallet.bin")
os.Remove(dbPath)
db, err := walletdb.Create("bdb", dbPath)
if err != nil {
t.Fatal(err)
}
namespace, err := db.Namespace(waddrmgrNamespaceKey)
if err != nil {
t.Fatal(err)
}
seed, err := hdkeychain.GenerateSeed(hdkeychain.RecommendedSeedLen)
if err != nil {
t.Fatal(err)
}
pubPassphrase := []byte("pub")
privPassphrase := []byte("priv")
mgr, err := waddrmgr.Create(namespace, seed, pubPassphrase,
privPassphrase, activeNet.Params, fastScrypt)
if err != nil {
t.Fatal(err)
}
for _, key := range privKeys {
wif, err := btcutil.DecodeWIF(key)
if err != nil {
t.Fatal(err)
}
if err = mgr.Unlock(privPassphrase); err != nil {
t.Fatal(err)
}
_, err = mgr.ImportPrivateKey(wif, bs)
if err != nil {
t.Fatal(err)
}
}
return mgr
}
// eligibleInputsFromTx decodes the given txHex and returns the outputs with
// the given indices as eligible inputs.
func eligibleInputsFromTx(t *testing.T, txHex string, indices []uint32) []txstore.Credit {
serialized, err := hex.DecodeString(txHex)
if err != nil {
t.Fatal(err)
}
tx, err := btcutil.NewTxFromBytes(serialized)
if err != nil {
t.Fatal(err)
}
s := txstore.New("/tmp/tx.bin")
r, err := s.InsertTx(tx, nil)
if err != nil {
t.Fatal(err)
}
eligible := make([]txstore.Credit, len(indices))
for i, idx := range indices {
credit, err := r.AddCredit(idx, false)
if err != nil {
t.Fatal(err)
}
eligible[i] = credit
}
return eligible
}