github.com/lbryio/lbcd@v0.22.119/integration/rpctest/rpc_harness.go (about)

     1  // Copyright (c) 2016-2017 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package rpctest
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"math/rand"
    11  	"net"
    12  	"os"
    13  	"path/filepath"
    14  	"strconv"
    15  	"sync"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/lbryio/lbcd/btcjson"
    20  	"github.com/lbryio/lbcd/chaincfg"
    21  	"github.com/lbryio/lbcd/chaincfg/chainhash"
    22  	"github.com/lbryio/lbcd/rpcclient"
    23  	"github.com/lbryio/lbcd/wire"
    24  	btcutil "github.com/lbryio/lbcutil"
    25  )
    26  
    27  const (
    28  	// These constants define the minimum and maximum p2p and rpc port
    29  	// numbers used by a test harness.  The min port is inclusive while the
    30  	// max port is exclusive.
    31  	minPeerPort = 10000
    32  	maxPeerPort = 35000
    33  	minRPCPort  = maxPeerPort
    34  	maxRPCPort  = 60000
    35  
    36  	// BlockVersion is the default block version used when generating
    37  	// blocks.
    38  	BlockVersion = 4
    39  
    40  	// DefaultMaxConnectionRetries is the default number of times we re-try
    41  	// to connect to the node after starting it.
    42  	DefaultMaxConnectionRetries = 20
    43  
    44  	// DefaultConnectionRetryTimeout is the default duration we wait between
    45  	// two connection attempts.
    46  	DefaultConnectionRetryTimeout = 50 * time.Millisecond
    47  )
    48  
    49  var (
    50  	// current number of active test nodes.
    51  	numTestInstances = 0
    52  
    53  	// testInstances is a private package-level slice used to keep track of
    54  	// all active test harnesses. This global can be used to perform
    55  	// various "joins", shutdown several active harnesses after a test,
    56  	// etc.
    57  	testInstances = make(map[string]*Harness)
    58  
    59  	// Used to protest concurrent access to above declared variables.
    60  	harnessStateMtx sync.RWMutex
    61  
    62  	// ListenAddressGenerator is a function that is used to generate two
    63  	// listen addresses (host:port), one for the P2P listener and one for
    64  	// the RPC listener. This is exported to allow overwriting of the
    65  	// default behavior which isn't very concurrency safe (just selecting
    66  	// a random port can produce collisions and therefore flakes).
    67  	ListenAddressGenerator = generateListeningAddresses
    68  )
    69  
    70  // HarnessTestCase represents a test-case which utilizes an instance of the
    71  // Harness to exercise functionality.
    72  type HarnessTestCase func(r *Harness, t *testing.T)
    73  
    74  // Harness fully encapsulates an active btcd process to provide a unified
    75  // platform for creating rpc driven integration tests involving btcd. The
    76  // active btcd node will typically be run in simnet mode in order to allow for
    77  // easy generation of test blockchains.  The active btcd process is fully
    78  // managed by Harness, which handles the necessary initialization, and teardown
    79  // of the process along with any temporary directories created as a result.
    80  // Multiple Harness instances may be run concurrently, in order to allow for
    81  // testing complex scenarios involving multiple nodes. The harness also
    82  // includes an in-memory wallet to streamline various classes of tests.
    83  type Harness struct {
    84  	// ActiveNet is the parameters of the blockchain the Harness belongs
    85  	// to.
    86  	ActiveNet *chaincfg.Params
    87  
    88  	// MaxConnRetries is the maximum number of times we re-try to connect to
    89  	// the node after starting it.
    90  	MaxConnRetries int
    91  
    92  	// ConnectionRetryTimeout is the duration we wait between two connection
    93  	// attempts.
    94  	ConnectionRetryTimeout time.Duration
    95  
    96  	Client      *rpcclient.Client
    97  	BatchClient *rpcclient.Client
    98  	node        *node
    99  	handlers    *rpcclient.NotificationHandlers
   100  
   101  	wallet *memWallet
   102  
   103  	testNodeDir string
   104  	nodeNum     int
   105  
   106  	sync.Mutex
   107  }
   108  
   109  // New creates and initializes new instance of the rpc test harness.
   110  // Optionally, websocket handlers and a specified configuration may be passed.
   111  // In the case that a nil config is passed, a default configuration will be
   112  // used. If a custom btcd executable is specified, it will be used to start the
   113  // harness node. Otherwise a new binary is built on demand.
   114  //
   115  // NOTE: This function is safe for concurrent access.
   116  func New(activeNet *chaincfg.Params, handlers *rpcclient.NotificationHandlers,
   117  	extraArgs []string, customExePath string) (*Harness, error) {
   118  
   119  	harnessStateMtx.Lock()
   120  	defer harnessStateMtx.Unlock()
   121  
   122  	// Add a flag for the appropriate network type based on the provided
   123  	// chain params.
   124  	switch activeNet.Net {
   125  	case wire.MainNet:
   126  		// No extra flags since mainnet is the default
   127  	case wire.TestNet3:
   128  		extraArgs = append(extraArgs, "--testnet")
   129  	case wire.TestNet:
   130  		extraArgs = append(extraArgs, "--regtest")
   131  	case wire.SimNet:
   132  		extraArgs = append(extraArgs, "--simnet")
   133  	default:
   134  		return nil, fmt.Errorf("rpctest.New must be called with one " +
   135  			"of the supported chain networks")
   136  	}
   137  
   138  	testDir, err := baseDir()
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	harnessID := strconv.Itoa(numTestInstances)
   144  	nodeTestData, err := ioutil.TempDir(testDir, "harness-"+harnessID)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	certFile := filepath.Join(nodeTestData, "rpc.cert")
   150  	keyFile := filepath.Join(nodeTestData, "rpc.key")
   151  	if err := genCertPair(certFile, keyFile); err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	wallet, err := newMemWallet(activeNet, uint32(numTestInstances))
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	miningAddr := fmt.Sprintf("--miningaddr=%s", wallet.coinbaseAddr)
   161  	extraArgs = append(extraArgs, miningAddr)
   162  
   163  	config, err := newConfig(
   164  		"rpctest", certFile, keyFile, extraArgs, customExePath,
   165  	)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	// Generate p2p+rpc listening addresses.
   171  	config.listen, config.rpcListen = ListenAddressGenerator()
   172  
   173  	// Create the testing node bounded to the simnet.
   174  	node, err := newNode(config, nodeTestData)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	nodeNum := numTestInstances
   180  	numTestInstances++
   181  
   182  	if handlers == nil {
   183  		handlers = &rpcclient.NotificationHandlers{}
   184  	}
   185  
   186  	// If a handler for the OnFilteredBlock{Connected,Disconnected} callback
   187  	// callback has already been set, then create a wrapper callback which
   188  	// executes both the currently registered callback and the mem wallet's
   189  	// callback.
   190  	if handlers.OnFilteredBlockConnected != nil {
   191  		obc := handlers.OnFilteredBlockConnected
   192  		handlers.OnFilteredBlockConnected = func(height int32, header *wire.BlockHeader, filteredTxns []*btcutil.Tx) {
   193  			wallet.IngestBlock(height, header, filteredTxns)
   194  			obc(height, header, filteredTxns)
   195  		}
   196  	} else {
   197  		// Otherwise, we can claim the callback ourselves.
   198  		handlers.OnFilteredBlockConnected = wallet.IngestBlock
   199  	}
   200  	if handlers.OnFilteredBlockDisconnected != nil {
   201  		obd := handlers.OnFilteredBlockDisconnected
   202  		handlers.OnFilteredBlockDisconnected = func(height int32, header *wire.BlockHeader) {
   203  			wallet.UnwindBlock(height, header)
   204  			obd(height, header)
   205  		}
   206  	} else {
   207  		handlers.OnFilteredBlockDisconnected = wallet.UnwindBlock
   208  	}
   209  
   210  	h := &Harness{
   211  		handlers:               handlers,
   212  		node:                   node,
   213  		MaxConnRetries:         DefaultMaxConnectionRetries,
   214  		ConnectionRetryTimeout: DefaultConnectionRetryTimeout,
   215  		testNodeDir:            nodeTestData,
   216  		ActiveNet:              activeNet,
   217  		nodeNum:                nodeNum,
   218  		wallet:                 wallet,
   219  	}
   220  
   221  	// Track this newly created test instance within the package level
   222  	// global map of all active test instances.
   223  	testInstances[h.testNodeDir] = h
   224  
   225  	return h, nil
   226  }
   227  
   228  // SetUp initializes the rpc test state. Initialization includes: starting up a
   229  // simnet node, creating a websockets client and connecting to the started
   230  // node, and finally: optionally generating and submitting a testchain with a
   231  // configurable number of mature coinbase outputs coinbase outputs.
   232  //
   233  // NOTE: This method and TearDown should always be called from the same
   234  // goroutine as they are not concurrent safe.
   235  func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
   236  	// Start the btcd node itself. This spawns a new process which will be
   237  	// managed
   238  	if err := h.node.start(); err != nil {
   239  		return err
   240  	}
   241  	if err := h.connectRPCClient(); err != nil {
   242  		return err
   243  	}
   244  
   245  	h.wallet.Start()
   246  
   247  	// Filter transactions that pay to the coinbase associated with the
   248  	// wallet.
   249  	filterAddrs := []btcutil.Address{h.wallet.coinbaseAddr}
   250  	if err := h.Client.LoadTxFilter(true, filterAddrs, nil); err != nil {
   251  		return err
   252  	}
   253  
   254  	// Ensure btcd properly dispatches our registered call-back for each new
   255  	// block. Otherwise, the memWallet won't function properly.
   256  	if err := h.Client.NotifyBlocks(); err != nil {
   257  		return err
   258  	}
   259  
   260  	numToGenerate := uint32(0)
   261  	// Create a test chain with the desired number of mature coinbase
   262  	// outputs.
   263  	if createTestChain && numMatureOutputs != 0 {
   264  		numToGenerate = uint32(h.ActiveNet.CoinbaseMaturity) + numMatureOutputs
   265  		_, err := h.Client.Generate(numToGenerate)
   266  		if err != nil {
   267  			return err
   268  		}
   269  	}
   270  
   271  	// Block until the wallet has fully synced up to the tip of the main
   272  	// chain.
   273  	_, height, err := h.Client.GetBestBlock()
   274  	if err != nil {
   275  		return err
   276  	}
   277  	if numToGenerate > 0 && uint32(height) < numToGenerate {
   278  		return fmt.Errorf("failed to generate this many blocks: %d", numToGenerate)
   279  	}
   280  	ticker := time.NewTicker(time.Millisecond * 100)
   281  	for range ticker.C {
   282  		walletHeight := h.wallet.SyncedHeight()
   283  		if walletHeight == height {
   284  			break
   285  		}
   286  	}
   287  	ticker.Stop()
   288  
   289  	return nil
   290  }
   291  
   292  // tearDown stops the running rpc test instance.  All created processes are
   293  // killed, and temporary directories removed.
   294  //
   295  // This function MUST be called with the harness state mutex held (for writes).
   296  func (h *Harness) tearDown() error {
   297  	if h.Client != nil {
   298  		h.Client.Shutdown()
   299  	}
   300  
   301  	if h.BatchClient != nil {
   302  		h.BatchClient.Shutdown()
   303  	}
   304  
   305  	if err := h.node.shutdown(); err != nil {
   306  		return err
   307  	}
   308  
   309  	if err := os.RemoveAll(h.testNodeDir); err != nil {
   310  		return err
   311  	}
   312  
   313  	delete(testInstances, h.testNodeDir)
   314  
   315  	return nil
   316  }
   317  
   318  // TearDown stops the running rpc test instance. All created processes are
   319  // killed, and temporary directories removed.
   320  //
   321  // NOTE: This method and SetUp should always be called from the same goroutine
   322  // as they are not concurrent safe.
   323  func (h *Harness) TearDown() error {
   324  	harnessStateMtx.Lock()
   325  	defer harnessStateMtx.Unlock()
   326  
   327  	return h.tearDown()
   328  }
   329  
   330  // connectRPCClient attempts to establish an RPC connection to the created btcd
   331  // process belonging to this Harness instance. If the initial connection
   332  // attempt fails, this function will retry h.maxConnRetries times, backing off
   333  // the time between subsequent attempts. If after h.maxConnRetries attempts,
   334  // we're not able to establish a connection, this function returns with an
   335  // error.
   336  func (h *Harness) connectRPCClient() error {
   337  	var client, batchClient *rpcclient.Client
   338  	var err error
   339  
   340  	rpcConf := h.node.config.rpcConnConfig()
   341  	batchConf := h.node.config.rpcConnConfig()
   342  	batchConf.HTTPPostMode = true
   343  	for i := 0; i < h.MaxConnRetries; i++ {
   344  		fail := false
   345  		if client == nil {
   346  			if client, err = rpcclient.New(&rpcConf, h.handlers); err != nil {
   347  				time.Sleep(time.Duration(i) * h.ConnectionRetryTimeout)
   348  				fail = true
   349  			}
   350  		}
   351  		if batchClient == nil {
   352  			if batchClient, err = rpcclient.NewBatch(&batchConf); err != nil {
   353  				time.Sleep(time.Duration(i) * h.ConnectionRetryTimeout)
   354  				fail = true
   355  			}
   356  		}
   357  		if !fail {
   358  			break
   359  		}
   360  	}
   361  
   362  	if client == nil || batchClient == nil {
   363  		return fmt.Errorf("connection timeout")
   364  	}
   365  
   366  	h.Client = client
   367  	h.wallet.SetRPCClient(client)
   368  	h.BatchClient = batchClient
   369  	return nil
   370  }
   371  
   372  // NewAddress returns a fresh address spendable by the Harness' internal
   373  // wallet.
   374  //
   375  // This function is safe for concurrent access.
   376  func (h *Harness) NewAddress() (btcutil.Address, error) {
   377  	return h.wallet.NewAddress()
   378  }
   379  
   380  // ConfirmedBalance returns the confirmed balance of the Harness' internal
   381  // wallet.
   382  //
   383  // This function is safe for concurrent access.
   384  func (h *Harness) ConfirmedBalance() btcutil.Amount {
   385  	return h.wallet.ConfirmedBalance()
   386  }
   387  
   388  // SendOutputs creates, signs, and finally broadcasts a transaction spending
   389  // the harness' available mature coinbase outputs creating new outputs
   390  // according to targetOutputs.
   391  //
   392  // This function is safe for concurrent access.
   393  func (h *Harness) SendOutputs(targetOutputs []*wire.TxOut,
   394  	feeRate btcutil.Amount) (*chainhash.Hash, error) {
   395  
   396  	return h.wallet.SendOutputs(targetOutputs, feeRate)
   397  }
   398  
   399  // SendOutputsWithoutChange creates and sends a transaction that pays to the
   400  // specified outputs while observing the passed fee rate and ignoring a change
   401  // output. The passed fee rate should be expressed in sat/b.
   402  //
   403  // This function is safe for concurrent access.
   404  func (h *Harness) SendOutputsWithoutChange(targetOutputs []*wire.TxOut,
   405  	feeRate btcutil.Amount) (*chainhash.Hash, error) {
   406  
   407  	return h.wallet.SendOutputsWithoutChange(targetOutputs, feeRate)
   408  }
   409  
   410  // CreateTransaction returns a fully signed transaction paying to the specified
   411  // outputs while observing the desired fee rate. The passed fee rate should be
   412  // expressed in satoshis-per-byte. The transaction being created can optionally
   413  // include a change output indicated by the change boolean. Any unspent outputs
   414  // selected as inputs for the crafted transaction are marked as unspendable in
   415  // order to avoid potential double-spends by future calls to this method. If the
   416  // created transaction is cancelled for any reason then the selected inputs MUST
   417  // be freed via a call to UnlockOutputs. Otherwise, the locked inputs won't be
   418  // returned to the pool of spendable outputs.
   419  //
   420  // This function is safe for concurrent access.
   421  func (h *Harness) CreateTransaction(targetOutputs []*wire.TxOut,
   422  	feeRate btcutil.Amount, change bool) (*wire.MsgTx, error) {
   423  
   424  	return h.wallet.CreateTransaction(targetOutputs, feeRate, change)
   425  }
   426  
   427  // UnlockOutputs unlocks any outputs which were previously marked as
   428  // unspendabe due to being selected to fund a transaction via the
   429  // CreateTransaction method.
   430  //
   431  // This function is safe for concurrent access.
   432  func (h *Harness) UnlockOutputs(inputs []*wire.TxIn) {
   433  	h.wallet.UnlockOutputs(inputs)
   434  }
   435  
   436  // RPCConfig returns the harnesses current rpc configuration. This allows other
   437  // potential RPC clients created within tests to connect to a given test
   438  // harness instance.
   439  func (h *Harness) RPCConfig() rpcclient.ConnConfig {
   440  	return h.node.config.rpcConnConfig()
   441  }
   442  
   443  // P2PAddress returns the harness' P2P listening address. This allows potential
   444  // peers (such as SPV peers) created within tests to connect to a given test
   445  // harness instance.
   446  func (h *Harness) P2PAddress() string {
   447  	return h.node.config.listen
   448  }
   449  
   450  // GenerateAndSubmitBlock creates a block whose contents include the passed
   451  // transactions and submits it to the running simnet node. For generating
   452  // blocks with only a coinbase tx, callers can simply pass nil instead of
   453  // transactions to be mined. Additionally, a custom block version can be set by
   454  // the caller. A blockVersion of -1 indicates that the current default block
   455  // version should be used. An uninitialized time.Time should be used for the
   456  // blockTime parameter if one doesn't wish to set a custom time.
   457  //
   458  // This function is safe for concurrent access.
   459  func (h *Harness) GenerateAndSubmitBlock(txns []*btcutil.Tx, blockVersion int32,
   460  	blockTime time.Time) (*btcutil.Block, error) {
   461  	return h.GenerateAndSubmitBlockWithCustomCoinbaseOutputs(txns,
   462  		blockVersion, blockTime, []wire.TxOut{})
   463  }
   464  
   465  // GenerateAndSubmitBlockWithCustomCoinbaseOutputs creates a block whose
   466  // contents include the passed coinbase outputs and transactions and submits
   467  // it to the running simnet node. For generating blocks with only a coinbase tx,
   468  // callers can simply pass nil instead of transactions to be mined.
   469  // Additionally, a custom block version can be set by the caller. A blockVersion
   470  // of -1 indicates that the current default block version should be used. An
   471  // uninitialized time.Time should be used for the blockTime parameter if one
   472  // doesn't wish to set a custom time. The mineTo list of outputs will be added
   473  // to the coinbase; this is not checked for correctness until the block is
   474  // submitted; thus, it is the caller's responsibility to ensure that the outputs
   475  // are correct. If the list is empty, the coinbase reward goes to the wallet
   476  // managed by the Harness.
   477  //
   478  // This function is safe for concurrent access.
   479  func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
   480  	txns []*btcutil.Tx, blockVersion int32, blockTime time.Time,
   481  	mineTo []wire.TxOut) (*btcutil.Block, error) {
   482  
   483  	h.Lock()
   484  	defer h.Unlock()
   485  
   486  	if blockVersion == -1 {
   487  		blockVersion = BlockVersion
   488  	}
   489  
   490  	prevBlockHash, prevBlockHeight, err := h.Client.GetBestBlock()
   491  	if err != nil {
   492  		return nil, err
   493  	}
   494  	mBlock, err := h.Client.GetBlock(prevBlockHash)
   495  	if err != nil {
   496  		return nil, err
   497  	}
   498  	prevBlock := btcutil.NewBlock(mBlock)
   499  	prevBlock.SetHeight(prevBlockHeight)
   500  
   501  	// Create a new block including the specified transactions
   502  	newBlock, err := CreateBlock(prevBlock, txns, blockVersion,
   503  		blockTime, h.wallet.coinbaseAddr, mineTo, h.ActiveNet)
   504  	if err != nil {
   505  		return nil, err
   506  	}
   507  
   508  	// Submit the block to the simnet node.
   509  	if err := h.Client.SubmitBlock(newBlock, nil); err != nil {
   510  		return nil, err
   511  	}
   512  
   513  	return newBlock, nil
   514  }
   515  
   516  // GetBlockStats returns block statistics. First argument specifies height or
   517  // hash of the target block. Second argument allows to select certain stats to
   518  // return. If second argument is empty, all stats are returned.
   519  func (h *Harness) GetBlockStats(hashOrHeight interface{}, stats *[]string) (
   520  	*btcjson.GetBlockStatsResult, error) {
   521  
   522  	h.Lock()
   523  	defer h.Unlock()
   524  
   525  	return h.Client.GetBlockStats(hashOrHeight, stats)
   526  }
   527  
   528  // generateListeningAddresses returns two strings representing listening
   529  // addresses designated for the current rpc test. If there haven't been any
   530  // test instances created, the default ports are used. Otherwise, in order to
   531  // support multiple test nodes running at once, the p2p and rpc port are
   532  // picked at random between {min/max}PeerPort and {min/max}RPCPort respectively.
   533  func generateListeningAddresses() (string, string) {
   534  	localhost := "127.0.0.1"
   535  
   536  	rand.Seed(time.Now().UnixNano())
   537  
   538  	portString := func(minPort, maxPort int) string {
   539  		port := minPort + rand.Intn(maxPort-minPort)
   540  		return strconv.Itoa(port)
   541  	}
   542  
   543  	p2p := net.JoinHostPort(localhost, portString(minPeerPort, maxPeerPort))
   544  	rpc := net.JoinHostPort(localhost, portString(minRPCPort, maxRPCPort))
   545  	return p2p, rpc
   546  }
   547  
   548  // baseDir is the directory path of the temp directory for all rpctest files.
   549  func baseDir() (string, error) {
   550  	dirPath := filepath.Join(os.TempDir(), "lbcd", "rpctest")
   551  	err := os.MkdirAll(dirPath, 0755)
   552  	return dirPath, err
   553  }