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.
171 lines
4.3 KiB
Go
171 lines
4.3 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 (
|
|
"net"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcrpcclient"
|
|
)
|
|
|
|
// JoinType is an enum representing a particular type of "node join". A node
|
|
// join is a synchronization tool used to wait until a subset of nodes have a
|
|
// consistent state with respect to an attribute.
|
|
type JoinType uint8
|
|
|
|
const (
|
|
// Blocks is a JoinType which waits until all nodes share the same
|
|
// block height.
|
|
Blocks JoinType = iota
|
|
|
|
// Mempools is a JoinType which blocks until all nodes have identical
|
|
// mempool.
|
|
Mempools
|
|
)
|
|
|
|
// JoinNodes is a synchronization tool used to block until all passed nodes are
|
|
// fully synced with respect to an attribute. This function will block for a
|
|
// period of time, finally returning once all nodes are synced according to the
|
|
// passed JoinType. This function be used to to ensure all active test
|
|
// harnesses are at a consistent state before proceeding to an assertion or
|
|
// check within rpc tests.
|
|
func JoinNodes(nodes []*Harness, joinType JoinType) error {
|
|
switch joinType {
|
|
case Blocks:
|
|
return syncBlocks(nodes)
|
|
case Mempools:
|
|
return syncMempools(nodes)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// syncMempools blocks until all nodes have identical mempools.
|
|
func syncMempools(nodes []*Harness) error {
|
|
poolsMatch := false
|
|
|
|
retry:
|
|
for !poolsMatch {
|
|
firstPool, err := nodes[0].Node.GetRawMempool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If all nodes have an identical mempool with respect to the
|
|
// first node, then we're done. Otherwise, drop back to the top
|
|
// of the loop and retry after a short wait period.
|
|
for _, node := range nodes[1:] {
|
|
nodePool, err := node.Node.GetRawMempool()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !reflect.DeepEqual(firstPool, nodePool) {
|
|
time.Sleep(time.Millisecond * 100)
|
|
continue retry
|
|
}
|
|
}
|
|
|
|
poolsMatch = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// syncBlocks blocks until all nodes report the same best chain.
|
|
func syncBlocks(nodes []*Harness) error {
|
|
blocksMatch := false
|
|
|
|
retry:
|
|
for !blocksMatch {
|
|
var prevHash *chainhash.Hash
|
|
var prevHeight int32
|
|
for _, node := range nodes {
|
|
blockHash, blockHeight, err := node.Node.GetBestBlock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if prevHash != nil && (*blockHash != *prevHash ||
|
|
blockHeight != prevHeight) {
|
|
|
|
time.Sleep(time.Millisecond * 100)
|
|
continue retry
|
|
}
|
|
prevHash, prevHeight = blockHash, blockHeight
|
|
}
|
|
|
|
blocksMatch = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ConnectNode establishes a new peer-to-peer connection between the "from"
|
|
// harness and the "to" harness. The connection made is flagged as persistent,
|
|
// therefore in the case of disconnects, "from" will attempt to reestablish a
|
|
// connection to the "to" harness.
|
|
func ConnectNode(from *Harness, to *Harness) error {
|
|
// Calculate the target p2p addr+port for the node to be connected to.
|
|
// p2p ports uses within the package are always even, so we multiply
|
|
// the node number by two before offsetting from the defaultP2pPort.
|
|
targetPort := defaultP2pPort + (2 * to.nodeNum)
|
|
targetAddr := net.JoinHostPort("127.0.0.1", strconv.Itoa(targetPort))
|
|
|
|
peerInfo, err := from.Node.GetPeerInfo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
numPeers := len(peerInfo)
|
|
|
|
if err := from.Node.AddNode(targetAddr, btcrpcclient.ANAdd); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Block until a new connection has been established.
|
|
peerInfo, err = from.Node.GetPeerInfo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for len(peerInfo) <= numPeers {
|
|
peerInfo, err = from.Node.GetPeerInfo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TearDownAll tears down all active test harnesses.
|
|
func TearDownAll() error {
|
|
harnessStateMtx.Lock()
|
|
defer harnessStateMtx.Unlock()
|
|
|
|
for _, harness := range testInstances {
|
|
if err := harness.TearDown(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ActiveHarnesses returns a slice of all currently active test harnesses. A
|
|
// test harness if considered "active" if it has been created, but not yet torn
|
|
// down.
|
|
func ActiveHarnesses() []*Harness {
|
|
harnessStateMtx.RLock()
|
|
defer harnessStateMtx.RUnlock()
|
|
|
|
activeNodes := make([]*Harness, 0, len(testInstances))
|
|
for _, harness := range testInstances {
|
|
activeNodes = append(activeNodes, harness)
|
|
}
|
|
|
|
return activeNodes
|
|
}
|