6cf60b5fae
This corrects several issues with the new rpctest package. The following is an overview of the issues that have been corrected: - The JoinNodes code was incorrect for both mempool and blocks. - For mempool it was only checking the first node against itself - For blocks it was only testing the height instead of hash and height which means the chains could be different and it would claim they're synced - The test for mempool joins was inaccurate in a few ways: - It was generating separate chains such that the generated transaction was invalid (an orphan) on one chain, but not the other - Mempools are not automatically synced when nodes are connected and there is a large random delay before any transaction rebroadcast happens, so it can't be relied on for the purposes of this test - The test for block joins was generating two independent chains of the same height with the same difficulty and was only passing due to the aforementioned bug in JoinNodes - All of the ConnectNode calls were connecting the main harness outbound to the local test harness instances - This is not correct because ConnectNode makes the outbound connection persistent, which means once the local test harness is gone, it would keep trying to connect for the remainder of the tests to a node that is never coming back and instead ends up connecting to an independent test harness.
547 lines
16 KiB
Go
547 lines
16 KiB
Go
// Copyright (c) 2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
package rpctest
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
)
|
|
|
|
func testSendOutputs(r *Harness, t *testing.T) {
|
|
genSpend := func(amt btcutil.Amount) *chainhash.Hash {
|
|
// Grab a fresh address from the wallet.
|
|
addr, err := r.NewAddress()
|
|
if err != nil {
|
|
t.Fatalf("unable to get new address: %v", err)
|
|
}
|
|
|
|
// Next, send amt BTC to this address, spending from one of our mature
|
|
// coinbase outputs.
|
|
addrScript, err := txscript.PayToAddrScript(addr)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate pkscript to addr: %v", err)
|
|
}
|
|
output := wire.NewTxOut(int64(amt), addrScript)
|
|
txid, err := r.SendOutputs([]*wire.TxOut{output}, 10)
|
|
if err != nil {
|
|
t.Fatalf("coinbase spend failed: %v", err)
|
|
}
|
|
return txid
|
|
}
|
|
|
|
assertTxMined := func(txid *chainhash.Hash, blockHash *chainhash.Hash) {
|
|
block, err := r.Node.GetBlock(blockHash)
|
|
if err != nil {
|
|
t.Fatalf("unable to get block: %v", err)
|
|
}
|
|
|
|
numBlockTxns := len(block.Transactions())
|
|
if numBlockTxns < 2 {
|
|
t.Fatalf("crafted transaction wasn't mined, block should have "+
|
|
"at least %v transactions instead has %v", 2, numBlockTxns)
|
|
}
|
|
|
|
minedTx := block.Transactions()[1]
|
|
txHash := minedTx.Hash()
|
|
if *txHash != *txid {
|
|
t.Fatalf("txid's don't match, %v vs %v", txHash, txid)
|
|
}
|
|
}
|
|
|
|
// First, generate a small spend which will require only a single
|
|
// input.
|
|
txid := genSpend(btcutil.Amount(5 * btcutil.SatoshiPerBitcoin))
|
|
|
|
// Generate a single block, the transaction the wallet created should
|
|
// be found in this block.
|
|
blockHashes, err := r.Node.Generate(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
assertTxMined(txid, blockHashes[0])
|
|
|
|
// Next, generate a spend much greater than the block reward. This
|
|
// transaction should also have been mined properly.
|
|
txid = genSpend(btcutil.Amount(500 * btcutil.SatoshiPerBitcoin))
|
|
blockHashes, err = r.Node.Generate(1)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate single block: %v", err)
|
|
}
|
|
assertTxMined(txid, blockHashes[0])
|
|
}
|
|
|
|
func assertConnectedTo(t *testing.T, nodeA *Harness, nodeB *Harness) {
|
|
nodePort := defaultP2pPort + (2 * nodeB.nodeNum)
|
|
nodeAddr := net.JoinHostPort("127.0.0.1", strconv.Itoa(nodePort))
|
|
|
|
nodeAPeers, err := nodeA.Node.GetPeerInfo()
|
|
if err != nil {
|
|
t.Fatalf("unable to get nodeA's peer info")
|
|
}
|
|
|
|
addrFound := false
|
|
for _, peerInfo := range nodeAPeers {
|
|
if peerInfo.Addr == nodeAddr {
|
|
addrFound = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !addrFound {
|
|
t.Fatal("nodeA not connected to nodeB")
|
|
}
|
|
}
|
|
|
|
func testConnectNode(r *Harness, t *testing.T) {
|
|
// Create a fresh test harness.
|
|
harness, err := New(&chaincfg.SimNetParams, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := harness.SetUp(false, 0); err != nil {
|
|
t.Fatalf("unable to complete rpctest setup: %v", err)
|
|
}
|
|
defer harness.TearDown()
|
|
|
|
// Establish a p2p connection from our new local harness to the main
|
|
// harness.
|
|
if err := ConnectNode(harness, r); err != nil {
|
|
t.Fatalf("unable to connect local to main harness: %v", err)
|
|
}
|
|
|
|
// The main harness should show up in our local harness' peer's list,
|
|
// and vice verse.
|
|
assertConnectedTo(t, harness, r)
|
|
}
|
|
|
|
func testTearDownAll(t *testing.T) {
|
|
// Grab a local copy of the currently active harnesses before
|
|
// attempting to tear them all down.
|
|
initialActiveHarnesses := ActiveHarnesses()
|
|
|
|
// Tear down all currently active harnesses.
|
|
if err := TearDownAll(); err != nil {
|
|
t.Fatalf("unable to teardown all harnesses: %v", err)
|
|
}
|
|
|
|
// The global testInstances map should now be fully purged with no
|
|
// active test harnesses remaining.
|
|
if len(ActiveHarnesses()) != 0 {
|
|
t.Fatalf("test harnesses still active after TearDownAll")
|
|
}
|
|
|
|
for _, harness := range initialActiveHarnesses {
|
|
// Ensure all test directories have been deleted.
|
|
if _, err := os.Stat(harness.testNodeDir); err == nil {
|
|
t.Errorf("created test datadir was not deleted.")
|
|
}
|
|
}
|
|
}
|
|
|
|
func testActiveHarnesses(r *Harness, t *testing.T) {
|
|
numInitialHarnesses := len(ActiveHarnesses())
|
|
|
|
// Create a single test harness.
|
|
harness1, err := New(&chaincfg.SimNetParams, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer harness1.TearDown()
|
|
|
|
// With the harness created above, a single harness should be detected
|
|
// as active.
|
|
numActiveHarnesses := len(ActiveHarnesses())
|
|
if !(numActiveHarnesses > numInitialHarnesses) {
|
|
t.Fatalf("ActiveHarnesses not updated, should have an " +
|
|
"additional test harness listed.")
|
|
}
|
|
}
|
|
|
|
func testJoinMempools(r *Harness, t *testing.T) {
|
|
// Assert main test harness has no transactions in its mempool.
|
|
pooledHashes, err := r.Node.GetRawMempool()
|
|
if err != nil {
|
|
t.Fatalf("unable to get mempool for main test harness: %v", err)
|
|
}
|
|
if len(pooledHashes) != 0 {
|
|
t.Fatal("main test harness mempool not empty")
|
|
}
|
|
|
|
// Create a local test harness with only the genesis block. The nodes
|
|
// will be synced below so the same transaction can be sent to both
|
|
// nodes without it being an orphan.
|
|
harness, err := New(&chaincfg.SimNetParams, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := harness.SetUp(false, 0); err != nil {
|
|
t.Fatalf("unable to complete rpctest setup: %v", err)
|
|
}
|
|
defer harness.TearDown()
|
|
|
|
nodeSlice := []*Harness{r, harness}
|
|
|
|
// Both mempools should be considered synced as they are empty.
|
|
// Therefore, this should return instantly.
|
|
if err := JoinNodes(nodeSlice, Mempools); err != nil {
|
|
t.Fatalf("unable to join node on mempools: %v", err)
|
|
}
|
|
|
|
// Generate a coinbase spend to a new address within the main harness'
|
|
// mempool.
|
|
addr, err := r.NewAddress()
|
|
addrScript, err := txscript.PayToAddrScript(addr)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate pkscript to addr: %v", err)
|
|
}
|
|
output := wire.NewTxOut(5e8, addrScript)
|
|
testTx, err := r.CreateTransaction([]*wire.TxOut{output}, 10)
|
|
if err != nil {
|
|
t.Fatalf("coinbase spend failed: %v", err)
|
|
}
|
|
if _, err := r.Node.SendRawTransaction(testTx, true); err != nil {
|
|
t.Fatalf("send transaction failed: %v", err)
|
|
}
|
|
|
|
// Wait until the transaction shows up to ensure the two mempools are
|
|
// not the same.
|
|
harnessSynced := make(chan struct{})
|
|
go func() {
|
|
for {
|
|
poolHashes, err := r.Node.GetRawMempool()
|
|
if err != nil {
|
|
t.Fatalf("failed to retrieve harness mempool: %v", err)
|
|
}
|
|
if len(poolHashes) > 0 {
|
|
break
|
|
}
|
|
time.Sleep(time.Millisecond * 100)
|
|
}
|
|
harnessSynced <- struct{}{}
|
|
}()
|
|
select {
|
|
case <-harnessSynced:
|
|
case <-time.After(time.Minute):
|
|
t.Fatalf("harness node never received transaction")
|
|
}
|
|
|
|
// This select case should fall through to the default as the goroutine
|
|
// should be blocked on the JoinNodes call.
|
|
poolsSynced := make(chan struct{})
|
|
go func() {
|
|
if err := JoinNodes(nodeSlice, Mempools); err != nil {
|
|
t.Fatalf("unable to join node on mempools: %v", err)
|
|
}
|
|
poolsSynced <- struct{}{}
|
|
}()
|
|
select {
|
|
case <-poolsSynced:
|
|
t.Fatalf("mempools detected as synced yet harness has a new tx")
|
|
default:
|
|
}
|
|
|
|
// Establish an outbound connection from the local harness to the main
|
|
// harness and wait for the chains to be synced.
|
|
if err := ConnectNode(harness, r); err != nil {
|
|
t.Fatalf("unable to connect harnesses: %v", err)
|
|
}
|
|
if err := JoinNodes(nodeSlice, Blocks); err != nil {
|
|
t.Fatalf("unable to join node on blocks: %v", err)
|
|
}
|
|
|
|
// Send the transaction to the local harness which will result in synced
|
|
// mempools.
|
|
if _, err := harness.Node.SendRawTransaction(testTx, true); err != nil {
|
|
t.Fatalf("send transaction failed: %v", err)
|
|
}
|
|
|
|
// Select once again with a special timeout case after 1 minute. The
|
|
// goroutine above should now be blocked on sending into the unbuffered
|
|
// channel. The send should immediately succeed. In order to avoid the
|
|
// test hanging indefinitely, a 1 minute timeout is in place.
|
|
select {
|
|
case <-poolsSynced:
|
|
// fall through
|
|
case <-time.After(time.Minute):
|
|
t.Fatalf("mempools never detected as synced")
|
|
}
|
|
}
|
|
|
|
func testJoinBlocks(r *Harness, t *testing.T) {
|
|
// Create a second harness with only the genesis block so it is behind
|
|
// the main harness.
|
|
harness, err := New(&chaincfg.SimNetParams, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := harness.SetUp(false, 0); err != nil {
|
|
t.Fatalf("unable to complete rpctest setup: %v", err)
|
|
}
|
|
defer harness.TearDown()
|
|
|
|
nodeSlice := []*Harness{r, harness}
|
|
blocksSynced := make(chan struct{})
|
|
go func() {
|
|
if err := JoinNodes(nodeSlice, Blocks); err != nil {
|
|
t.Fatalf("unable to join node on blocks: %v", err)
|
|
}
|
|
blocksSynced <- struct{}{}
|
|
}()
|
|
|
|
// This select case should fall through to the default as the goroutine
|
|
// should be blocked on the JoinNodes calls.
|
|
select {
|
|
case <-blocksSynced:
|
|
t.Fatalf("blocks detected as synced yet local harness is behind")
|
|
default:
|
|
}
|
|
|
|
// Connect the local harness to the main harness which will sync the
|
|
// chains.
|
|
if err := ConnectNode(harness, r); err != nil {
|
|
t.Fatalf("unable to connect harnesses: %v", err)
|
|
}
|
|
|
|
// Select once again with a special timeout case after 1 minute. The
|
|
// goroutine above should now be blocked on sending into the unbuffered
|
|
// channel. The send should immediately succeed. In order to avoid the
|
|
// test hanging indefinitely, a 1 minute timeout is in place.
|
|
select {
|
|
case <-blocksSynced:
|
|
// fall through
|
|
case <-time.After(time.Minute):
|
|
t.Fatalf("blocks never detected as synced")
|
|
}
|
|
}
|
|
|
|
func testGenerateAndSubmitBlock(r *Harness, t *testing.T) {
|
|
// Generate a few test spend transactions.
|
|
addr, err := r.NewAddress()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate new address: %v", err)
|
|
}
|
|
pkScript, err := txscript.PayToAddrScript(addr)
|
|
if err != nil {
|
|
t.Fatalf("unable to create script: %v", err)
|
|
}
|
|
output := wire.NewTxOut(btcutil.SatoshiPerBitcoin, pkScript)
|
|
|
|
const numTxns = 5
|
|
txns := make([]*btcutil.Tx, 0, numTxns)
|
|
for i := 0; i < numTxns; i++ {
|
|
tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10)
|
|
if err != nil {
|
|
t.Fatalf("unable to create tx: %v", err)
|
|
}
|
|
|
|
txns = append(txns, btcutil.NewTx(tx))
|
|
}
|
|
|
|
// Now generate a block with the default block version, and a zero'd
|
|
// out time.
|
|
block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{})
|
|
if err != nil {
|
|
t.Fatalf("unable to generate block: %v", err)
|
|
}
|
|
|
|
// Ensure that all created transactions were included, and that the
|
|
// block version was properly set to the default.
|
|
numBlocksTxns := len(block.Transactions())
|
|
if numBlocksTxns != numTxns+1 {
|
|
t.Fatalf("block did not include all transactions: "+
|
|
"expected %v, got %v", numTxns+1, numBlocksTxns)
|
|
}
|
|
blockVersion := block.MsgBlock().Header.Version
|
|
if blockVersion != wire.BlockVersion {
|
|
t.Fatalf("block version is not default: expected %v, got %v",
|
|
wire.BlockVersion, blockVersion)
|
|
}
|
|
|
|
// Next generate a block with a "non-standard" block version along with
|
|
// time stamp a minute after the previous block's timestamp.
|
|
timestamp := block.MsgBlock().Header.Timestamp.Add(time.Minute)
|
|
targetBlockVersion := int32(1337)
|
|
block, err = r.GenerateAndSubmitBlock(nil, targetBlockVersion, timestamp)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate block: %v", err)
|
|
}
|
|
|
|
// Finally ensure that the desired block version and timestamp were set
|
|
// properly.
|
|
header := block.MsgBlock().Header
|
|
blockVersion = header.Version
|
|
if blockVersion != targetBlockVersion {
|
|
t.Fatalf("block version mismatch: expected %v, got %v",
|
|
targetBlockVersion, blockVersion)
|
|
}
|
|
if !timestamp.Equal(header.Timestamp) {
|
|
t.Fatalf("header time stamp mismatch: expected %v, got %v",
|
|
timestamp, header.Timestamp)
|
|
}
|
|
}
|
|
|
|
func testMemWalletReorg(r *Harness, t *testing.T) {
|
|
// Create a fresh harness, we'll be using the main harness to force a
|
|
// re-org on this local harness.
|
|
harness, err := New(&chaincfg.SimNetParams, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := harness.SetUp(true, 5); err != nil {
|
|
t.Fatalf("unable to complete rpctest setup: %v", err)
|
|
}
|
|
defer harness.TearDown()
|
|
|
|
// The internal wallet of this harness should now have 250 BTC.
|
|
expectedBalance := btcutil.Amount(250 * btcutil.SatoshiPerBitcoin)
|
|
walletBalance := harness.ConfirmedBalance()
|
|
if expectedBalance != walletBalance {
|
|
t.Fatalf("wallet balance incorrect: expected %v, got %v",
|
|
expectedBalance, walletBalance)
|
|
}
|
|
|
|
// Now connect this local harness to the main harness then wait for
|
|
// their chains to synchronize.
|
|
if err := ConnectNode(harness, r); err != nil {
|
|
t.Fatalf("unable to connect harnesses: %v", err)
|
|
}
|
|
nodeSlice := []*Harness{r, harness}
|
|
if err := JoinNodes(nodeSlice, Blocks); err != nil {
|
|
t.Fatalf("unable to join node on blocks: %v", err)
|
|
}
|
|
|
|
// The original wallet should now have a balance of 0 BTC as its entire
|
|
// chain should have been decimated in favor of the main harness'
|
|
// chain.
|
|
expectedBalance = btcutil.Amount(0)
|
|
walletBalance = harness.ConfirmedBalance()
|
|
if expectedBalance != walletBalance {
|
|
t.Fatalf("wallet balance incorrect: expected %v, got %v",
|
|
expectedBalance, walletBalance)
|
|
}
|
|
}
|
|
|
|
func testMemWalletLockedOutputs(r *Harness, t *testing.T) {
|
|
// Obtain the initial balance of the wallet at this point.
|
|
startingBalance := r.ConfirmedBalance()
|
|
|
|
// First, create a signed transaction spending some outputs.
|
|
addr, err := r.NewAddress()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate new address: %v", err)
|
|
}
|
|
pkScript, err := txscript.PayToAddrScript(addr)
|
|
if err != nil {
|
|
t.Fatalf("unable to create script: %v", err)
|
|
}
|
|
outputAmt := btcutil.Amount(50 * btcutil.SatoshiPerBitcoin)
|
|
output := wire.NewTxOut(int64(outputAmt), pkScript)
|
|
tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10)
|
|
if err != nil {
|
|
t.Fatalf("unable to create transaction: %v", err)
|
|
}
|
|
|
|
// The current wallet balance should now be at least 50 BTC less
|
|
// (accounting for fees) than the period balance
|
|
currentBalance := r.ConfirmedBalance()
|
|
if !(currentBalance <= startingBalance-outputAmt) {
|
|
t.Fatalf("spent outputs not locked: previous balance %v, "+
|
|
"current balance %v", startingBalance, currentBalance)
|
|
}
|
|
|
|
// Now unlocked all the spent inputs within the unbroadcast signed
|
|
// transaction. The current balance should now be exactly that of the
|
|
// starting balance.
|
|
r.UnlockOutputs(tx.TxIn)
|
|
currentBalance = r.ConfirmedBalance()
|
|
if currentBalance != startingBalance {
|
|
t.Fatalf("current and starting balance should now match: "+
|
|
"expected %v, got %v", startingBalance, currentBalance)
|
|
}
|
|
}
|
|
|
|
var harnessTestCases = []HarnessTestCase{
|
|
testSendOutputs,
|
|
testConnectNode,
|
|
testActiveHarnesses,
|
|
testJoinBlocks,
|
|
testJoinMempools, // Depends on results of testJoinBlocks
|
|
testGenerateAndSubmitBlock,
|
|
testMemWalletReorg,
|
|
testMemWalletLockedOutputs,
|
|
}
|
|
|
|
var mainHarness *Harness
|
|
|
|
const (
|
|
numMatureOutputs = 25
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
var err error
|
|
mainHarness, err = New(&chaincfg.SimNetParams, nil, nil)
|
|
if err != nil {
|
|
fmt.Println("unable to create main harness: ", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Initialize the main mining node with a chain of length 125,
|
|
// providing 25 mature coinbases to allow spending from for testing
|
|
// purposes.
|
|
if err = mainHarness.SetUp(true, numMatureOutputs); err != nil {
|
|
fmt.Println("unable to setup test chain: ", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
exitCode := m.Run()
|
|
|
|
// Clean up any active harnesses that are still currently running.
|
|
if len(ActiveHarnesses()) > 0 {
|
|
if err := TearDownAll(); err != nil {
|
|
fmt.Println("unable to tear down chain: ", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func TestHarness(t *testing.T) {
|
|
// We should have (numMatureOutputs * 50 BTC) of mature unspendable
|
|
// outputs.
|
|
expectedBalance := btcutil.Amount(numMatureOutputs * 50 * btcutil.SatoshiPerBitcoin)
|
|
harnessBalance := mainHarness.ConfirmedBalance()
|
|
if harnessBalance != expectedBalance {
|
|
t.Fatalf("expected wallet balance of %v instead have %v",
|
|
expectedBalance, harnessBalance)
|
|
}
|
|
|
|
// Current tip should be at a height of numMatureOutputs plus the
|
|
// required number of blocks for coinbase maturity.
|
|
nodeInfo, err := mainHarness.Node.GetInfo()
|
|
if err != nil {
|
|
t.Fatalf("unable to execute getinfo on node: %v", err)
|
|
}
|
|
expectedChainHeight := numMatureOutputs + uint32(mainHarness.ActiveNet.CoinbaseMaturity)
|
|
if uint32(nodeInfo.Blocks) != expectedChainHeight {
|
|
t.Errorf("Chain height is %v, should be %v",
|
|
nodeInfo.Blocks, expectedChainHeight)
|
|
}
|
|
|
|
for _, testCase := range harnessTestCases {
|
|
testCase(mainHarness, t)
|
|
}
|
|
|
|
testTearDownAll(t)
|
|
}
|