389 lines
12 KiB
Go
389 lines
12 KiB
Go
|
/*
|
||
|
* Copyright (c) 2014 Conformal Systems LLC <info@conformal.com>
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
* copyright notice and this permission notice appear in all copies.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
// Helpers to create parameterized objects to use in tests.
|
||
|
|
||
|
package votingpool
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"sync/atomic"
|
||
|
"testing"
|
||
|
|
||
|
"github.com/btcsuite/btcd/chaincfg"
|
||
|
"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"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// seed is the master seed used to create extended keys.
|
||
|
seed = bytes.Repeat([]byte{0x2a, 0x64, 0xdf, 0x08}, 8)
|
||
|
pubPassphrase = []byte("_DJr{fL4H0O}*-0\n:V1izc)(6BomK")
|
||
|
privPassphrase = []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj")
|
||
|
uniqueCounter = uint32(0)
|
||
|
// The block height where all our test inputs are created.
|
||
|
TstInputsBlock = int32(10)
|
||
|
)
|
||
|
|
||
|
func getUniqueID() uint32 {
|
||
|
return atomic.AddUint32(&uniqueCounter, 1)
|
||
|
}
|
||
|
|
||
|
// createWithdrawalTx creates a withdrawalTx with the given input and output amounts.
|
||
|
func createWithdrawalTx(t *testing.T, pool *Pool, store *txstore.Store, inputAmounts []int64,
|
||
|
outputAmounts []int64) *withdrawalTx {
|
||
|
net := pool.Manager().ChainParams()
|
||
|
tx := newWithdrawalTx()
|
||
|
_, credits := TstCreateCredits(t, pool, inputAmounts, store)
|
||
|
for _, c := range credits {
|
||
|
tx.addInput(c)
|
||
|
}
|
||
|
for i, amount := range outputAmounts {
|
||
|
request := TstNewOutputRequest(
|
||
|
t, uint32(i), "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", btcutil.Amount(amount), net)
|
||
|
tx.addOutput(request)
|
||
|
}
|
||
|
return tx
|
||
|
}
|
||
|
|
||
|
func createMsgTx(pkScript []byte, amts []int64) *wire.MsgTx {
|
||
|
msgtx := &wire.MsgTx{
|
||
|
Version: 1,
|
||
|
TxIn: []*wire.TxIn{
|
||
|
{
|
||
|
PreviousOutPoint: wire.OutPoint{
|
||
|
Hash: wire.ShaHash{},
|
||
|
Index: 0xffffffff,
|
||
|
},
|
||
|
SignatureScript: []byte{txscript.OP_NOP},
|
||
|
Sequence: 0xffffffff,
|
||
|
},
|
||
|
},
|
||
|
LockTime: 0,
|
||
|
}
|
||
|
|
||
|
for _, amt := range amts {
|
||
|
msgtx.AddTxOut(wire.NewTxOut(amt, pkScript))
|
||
|
}
|
||
|
return msgtx
|
||
|
}
|
||
|
|
||
|
func TstNewDepositScript(t *testing.T, p *Pool, seriesID uint32, branch Branch, idx Index) []byte {
|
||
|
script, err := p.DepositScript(seriesID, branch, idx)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to create deposit script for series %d, branch %d, index %d: %v",
|
||
|
seriesID, branch, idx, err)
|
||
|
}
|
||
|
return script
|
||
|
}
|
||
|
|
||
|
// TstEnsureUsedAddr ensures the addresses defined by the given series/branch and
|
||
|
// index==0..idx are present in the set of used addresses for the given Pool.
|
||
|
func TstEnsureUsedAddr(t *testing.T, p *Pool, seriesID uint32, branch Branch, idx Index) []byte {
|
||
|
addr, err := p.getUsedAddr(seriesID, branch, idx)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
} else if addr != nil {
|
||
|
var script []byte
|
||
|
TstRunWithManagerUnlocked(t, p.Manager(), func() {
|
||
|
script, err = addr.Script()
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
return script
|
||
|
}
|
||
|
TstRunWithManagerUnlocked(t, p.Manager(), func() {
|
||
|
err = p.EnsureUsedAddr(seriesID, branch, idx)
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
return TstNewDepositScript(t, p, seriesID, branch, idx)
|
||
|
}
|
||
|
|
||
|
func TstCreatePkScript(t *testing.T, p *Pool, seriesID uint32, branch Branch, idx Index) []byte {
|
||
|
script := TstEnsureUsedAddr(t, p, seriesID, branch, idx)
|
||
|
addr, err := p.addressFor(script)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
pkScript, err := txscript.PayToAddrScript(addr)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
return pkScript
|
||
|
}
|
||
|
|
||
|
func TstCreateTxStore(t *testing.T) (store *txstore.Store, tearDown func()) {
|
||
|
dir, err := ioutil.TempDir("", "tx.bin")
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to create db file: %v", err)
|
||
|
}
|
||
|
s := txstore.New(dir)
|
||
|
return s, func() { os.RemoveAll(dir) }
|
||
|
}
|
||
|
|
||
|
type TstSeriesDef struct {
|
||
|
ReqSigs uint32
|
||
|
PubKeys []string
|
||
|
PrivKeys []string
|
||
|
SeriesID uint32
|
||
|
Inactive bool
|
||
|
}
|
||
|
|
||
|
// TstCreateSeries creates a new Series for every definition in the given slice
|
||
|
// of TstSeriesDef. If the definition includes any private keys, the Series is
|
||
|
// empowered with them.
|
||
|
func TstCreateSeries(t *testing.T, pool *Pool, definitions []TstSeriesDef) {
|
||
|
for _, def := range definitions {
|
||
|
err := pool.CreateSeries(CurrentVersion, def.SeriesID, def.ReqSigs, def.PubKeys)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Cannot creates series %d: %v", def.SeriesID, err)
|
||
|
}
|
||
|
TstRunWithManagerUnlocked(t, pool.Manager(), func() {
|
||
|
for _, key := range def.PrivKeys {
|
||
|
if err := pool.EmpowerSeries(def.SeriesID, key); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
pool.Series(def.SeriesID).active = !def.Inactive
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TstCreateMasterKey(t *testing.T, seed []byte) *hdkeychain.ExtendedKey {
|
||
|
key, err := hdkeychain.NewMaster(seed)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
return key
|
||
|
}
|
||
|
|
||
|
// createMasterKeys creates count master ExtendedKeys with unique seeds.
|
||
|
func createMasterKeys(t *testing.T, count int) []*hdkeychain.ExtendedKey {
|
||
|
keys := make([]*hdkeychain.ExtendedKey, count)
|
||
|
for i := range keys {
|
||
|
keys[i] = TstCreateMasterKey(t, bytes.Repeat(uint32ToBytes(getUniqueID()), 4))
|
||
|
}
|
||
|
return keys
|
||
|
}
|
||
|
|
||
|
// TstCreateSeriesDef creates a TstSeriesDef with a unique SeriesID, the given
|
||
|
// reqSigs and the raw public/private keys extracted from the list of private
|
||
|
// keys. The new series will be empowered with all private keys.
|
||
|
func TstCreateSeriesDef(t *testing.T, pool *Pool, reqSigs uint32, keys []*hdkeychain.ExtendedKey) TstSeriesDef {
|
||
|
pubKeys := make([]string, len(keys))
|
||
|
privKeys := make([]string, len(keys))
|
||
|
for i, key := range keys {
|
||
|
privKeys[i] = key.String()
|
||
|
pubkey, _ := key.Neuter()
|
||
|
pubKeys[i] = pubkey.String()
|
||
|
}
|
||
|
seriesID := uint32(len(pool.seriesLookup))
|
||
|
if seriesID == 0 {
|
||
|
seriesID++
|
||
|
}
|
||
|
return TstSeriesDef{
|
||
|
ReqSigs: reqSigs, SeriesID: seriesID, PubKeys: pubKeys, PrivKeys: privKeys}
|
||
|
}
|
||
|
|
||
|
func TstCreatePoolAndTxStore(t *testing.T) (tearDown func(), pool *Pool, store *txstore.Store) {
|
||
|
mgrTearDown, _, pool := TstCreatePool(t)
|
||
|
store, storeTearDown := TstCreateTxStore(t)
|
||
|
tearDown = func() {
|
||
|
mgrTearDown()
|
||
|
storeTearDown()
|
||
|
}
|
||
|
return tearDown, pool, store
|
||
|
}
|
||
|
|
||
|
// TstCreateCredits creates a new Series (with a unique ID) and a slice of
|
||
|
// credits locked to the series' address with branch==1 and index==0. The new
|
||
|
// Series will use a 2-of-3 configuration and will be empowered with all of its
|
||
|
// private keys.
|
||
|
func TstCreateCredits(t *testing.T, pool *Pool, amounts []int64, store *txstore.Store) (
|
||
|
uint32, []Credit) {
|
||
|
masters := []*hdkeychain.ExtendedKey{
|
||
|
TstCreateMasterKey(t, bytes.Repeat(uint32ToBytes(getUniqueID()), 4)),
|
||
|
TstCreateMasterKey(t, bytes.Repeat(uint32ToBytes(getUniqueID()), 4)),
|
||
|
TstCreateMasterKey(t, bytes.Repeat(uint32ToBytes(getUniqueID()), 4)),
|
||
|
}
|
||
|
def := TstCreateSeriesDef(t, pool, 2, masters)
|
||
|
TstCreateSeries(t, pool, []TstSeriesDef{def})
|
||
|
return def.SeriesID, TstCreateCreditsOnSeries(t, pool, def.SeriesID, amounts, store)
|
||
|
}
|
||
|
|
||
|
// TstCreateCreditsOnSeries creates a slice of credits locked to the given
|
||
|
// series' address with branch==1 and index==0.
|
||
|
func TstCreateCreditsOnSeries(t *testing.T, pool *Pool, seriesID uint32, amounts []int64,
|
||
|
store *txstore.Store) []Credit {
|
||
|
branch := Branch(1)
|
||
|
idx := Index(0)
|
||
|
pkScript := TstCreatePkScript(t, pool, seriesID, branch, idx)
|
||
|
eligible := make([]Credit, len(amounts))
|
||
|
for i, credit := range TstCreateInputs(t, store, pkScript, amounts) {
|
||
|
eligible[i] = newCredit(credit, *TstNewWithdrawalAddress(t, pool, seriesID, branch, idx))
|
||
|
}
|
||
|
return eligible
|
||
|
}
|
||
|
|
||
|
// TstCreateInputs is a convenience function. See TstCreateInputsOnBlock
|
||
|
// for a more flexible version.
|
||
|
func TstCreateInputs(t *testing.T, store *txstore.Store, pkScript []byte, amounts []int64) []txstore.Credit {
|
||
|
return TstCreateInputsOnBlock(t, store, 1, pkScript, amounts)
|
||
|
}
|
||
|
|
||
|
// TstCreateInputsOnBlock creates a number of inputs by creating a transaction
|
||
|
// with a number of outputs corresponding to the elements of the amounts slice.
|
||
|
//
|
||
|
// The transaction is added to a block and the index and blockheight must be
|
||
|
// specified.
|
||
|
func TstCreateInputsOnBlock(t *testing.T, s *txstore.Store,
|
||
|
blockTxIndex int, pkScript []byte, amounts []int64) []txstore.Credit {
|
||
|
msgTx := createMsgTx(pkScript, amounts)
|
||
|
block := &txstore.Block{
|
||
|
Height: TstInputsBlock,
|
||
|
}
|
||
|
|
||
|
tx := btcutil.NewTx(msgTx)
|
||
|
tx.SetIndex(blockTxIndex)
|
||
|
|
||
|
r, err := s.InsertTx(tx, block)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to create inputs: ", err)
|
||
|
}
|
||
|
|
||
|
credits := make([]txstore.Credit, len(msgTx.TxOut))
|
||
|
for i := range msgTx.TxOut {
|
||
|
credit, err := r.AddCredit(uint32(i), false)
|
||
|
if err != nil {
|
||
|
t.Fatal("Failed to create inputs: ", err)
|
||
|
}
|
||
|
credits[i] = credit
|
||
|
}
|
||
|
return credits
|
||
|
}
|
||
|
|
||
|
// TstCreatePool creates a Pool on a fresh walletdb and returns it. It also
|
||
|
// returns the pool's waddrmgr.Manager (which uses the same walletdb, but with a
|
||
|
// different namespace) as a convenience, and a teardown function that closes
|
||
|
// the Manager and removes the directory used to store the database.
|
||
|
func TstCreatePool(t *testing.T) (tearDownFunc func(), mgr *waddrmgr.Manager, pool *Pool) {
|
||
|
// This should be moved somewhere else eventually as not all of our tests
|
||
|
// call this function, but right now the only option would be to have the
|
||
|
// t.Parallel() call in each of our tests.
|
||
|
t.Parallel()
|
||
|
|
||
|
// Create a new wallet DB and addr manager.
|
||
|
dir, err := ioutil.TempDir("", "pool_test")
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to create db dir: %v", err)
|
||
|
}
|
||
|
db, err := walletdb.Create("bdb", filepath.Join(dir, "wallet.db"))
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to create wallet DB: %v", err)
|
||
|
}
|
||
|
mgrNamespace, err := db.Namespace([]byte("waddrmgr"))
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to create addr manager DB namespace: %v", err)
|
||
|
}
|
||
|
var fastScrypt = &waddrmgr.Options{ScryptN: 16, ScryptR: 8, ScryptP: 1}
|
||
|
mgr, err = waddrmgr.Create(mgrNamespace, seed, pubPassphrase, privPassphrase,
|
||
|
&chaincfg.MainNetParams, fastScrypt)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to create addr manager: %v", err)
|
||
|
}
|
||
|
|
||
|
// Create a walletdb for votingpools.
|
||
|
vpNamespace, err := db.Namespace([]byte("votingpool"))
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to create VotingPool DB namespace: %v", err)
|
||
|
}
|
||
|
pool, err = Create(vpNamespace, mgr, []byte{0x00})
|
||
|
if err != nil {
|
||
|
t.Fatalf("Voting Pool creation failed: %v", err)
|
||
|
}
|
||
|
tearDownFunc = func() {
|
||
|
db.Close()
|
||
|
mgr.Close()
|
||
|
os.RemoveAll(dir)
|
||
|
}
|
||
|
return tearDownFunc, mgr, pool
|
||
|
}
|
||
|
|
||
|
func TstNewOutputRequest(t *testing.T, transaction uint32, address string, amount btcutil.Amount,
|
||
|
net *chaincfg.Params) OutputRequest {
|
||
|
addr, err := btcutil.DecodeAddress(address, net)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Unable to decode address %s", address)
|
||
|
}
|
||
|
pkScript, err := txscript.PayToAddrScript(addr)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Unable to generate pkScript for %v", addr)
|
||
|
}
|
||
|
return OutputRequest{
|
||
|
PkScript: pkScript,
|
||
|
Address: addr,
|
||
|
Amount: amount,
|
||
|
Server: "server",
|
||
|
Transaction: transaction,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TstNewWithdrawalOutput(r OutputRequest, status outputStatus,
|
||
|
outpoints []OutBailmentOutpoint) *WithdrawalOutput {
|
||
|
output := &WithdrawalOutput{
|
||
|
request: r,
|
||
|
status: status,
|
||
|
outpoints: outpoints,
|
||
|
}
|
||
|
return output
|
||
|
}
|
||
|
|
||
|
func TstNewWithdrawalAddress(t *testing.T, p *Pool, seriesID uint32, branch Branch,
|
||
|
index Index) (addr *WithdrawalAddress) {
|
||
|
TstEnsureUsedAddr(t, p, seriesID, branch, index)
|
||
|
var err error
|
||
|
TstRunWithManagerUnlocked(t, p.Manager(), func() {
|
||
|
addr, err = p.WithdrawalAddress(seriesID, branch, index)
|
||
|
})
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to get WithdrawalAddress: %v", err)
|
||
|
}
|
||
|
return addr
|
||
|
}
|
||
|
|
||
|
func TstNewChangeAddress(t *testing.T, p *Pool, seriesID uint32, idx Index) (addr *ChangeAddress) {
|
||
|
addr, err := p.ChangeAddress(seriesID, idx)
|
||
|
if err != nil {
|
||
|
t.Fatalf("Failed to get ChangeAddress: %v", err)
|
||
|
}
|
||
|
return addr
|
||
|
}
|
||
|
|
||
|
func TstConstantFee(fee btcutil.Amount) func(tx *withdrawalTx) btcutil.Amount {
|
||
|
return func(tx *withdrawalTx) btcutil.Amount { return fee }
|
||
|
}
|