github.com/btcsuite/btcd@v0.24.0/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  	"net"
    10  	"os"
    11  	"path/filepath"
    12  	"strconv"
    13  	"sync"
    14  	"sync/atomic"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/btcsuite/btcd/btcutil"
    19  	"github.com/btcsuite/btcd/chaincfg"
    20  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    21  	"github.com/btcsuite/btcd/rpcclient"
    22  	"github.com/btcsuite/btcd/wire"
    23  )
    24  
    25  const (
    26  	// These constants define the minimum and maximum p2p and rpc port
    27  	// numbers used by a test harness.  The min port is inclusive while the
    28  	// max port is exclusive.
    29  	minPeerPort = 10000
    30  	maxPeerPort = 35000
    31  	minRPCPort  = maxPeerPort
    32  	maxRPCPort  = 60000
    33  
    34  	// BlockVersion is the default block version used when generating
    35  	// blocks.
    36  	BlockVersion = 4
    37  
    38  	// DefaultMaxConnectionRetries is the default number of times we re-try
    39  	// to connect to the node after starting it.
    40  	DefaultMaxConnectionRetries = 20
    41  
    42  	// DefaultConnectionRetryTimeout is the default duration we wait between
    43  	// two connection attempts.
    44  	DefaultConnectionRetryTimeout = 50 * time.Millisecond
    45  )
    46  
    47  var (
    48  	// current number of active test nodes.
    49  	numTestInstances = 0
    50  
    51  	// testInstances is a private package-level slice used to keep track of
    52  	// all active test harnesses. This global can be used to perform
    53  	// various "joins", shutdown several active harnesses after a test,
    54  	// etc.
    55  	testInstances = make(map[string]*Harness)
    56  
    57  	// Used to protest concurrent access to above declared variables.
    58  	harnessStateMtx sync.RWMutex
    59  
    60  	// ListenAddressGenerator is a function that is used to generate two
    61  	// listen addresses (host:port), one for the P2P listener and one for
    62  	// the RPC listener. This is exported to allow overwriting of the
    63  	// default behavior which isn't very concurrency safe (just selecting
    64  	// a random port can produce collisions and therefore flakes).
    65  	ListenAddressGenerator = generateListeningAddresses
    66  
    67  	// defaultNodePort is the start of the range for listening ports of
    68  	// harness nodes. Ports are monotonically increasing starting from this
    69  	// number and are determined by the results of nextAvailablePort().
    70  	defaultNodePort uint32 = 8333
    71  
    72  	// ListenerFormat is the format string that is used to generate local
    73  	// listener addresses.
    74  	ListenerFormat = "127.0.0.1:%d"
    75  
    76  	// lastPort is the last port determined to be free for use by a new
    77  	// node. It should be used atomically.
    78  	lastPort uint32 = defaultNodePort
    79  )
    80  
    81  // HarnessTestCase represents a test-case which utilizes an instance of the
    82  // Harness to exercise functionality.
    83  type HarnessTestCase func(r *Harness, t *testing.T)
    84  
    85  // Harness fully encapsulates an active btcd process to provide a unified
    86  // platform for creating rpc driven integration tests involving btcd. The
    87  // active btcd node will typically be run in simnet mode in order to allow for
    88  // easy generation of test blockchains.  The active btcd process is fully
    89  // managed by Harness, which handles the necessary initialization, and teardown
    90  // of the process along with any temporary directories created as a result.
    91  // Multiple Harness instances may be run concurrently, in order to allow for
    92  // testing complex scenarios involving multiple nodes. The harness also
    93  // includes an in-memory wallet to streamline various classes of tests.
    94  type Harness struct {
    95  	// ActiveNet is the parameters of the blockchain the Harness belongs
    96  	// to.
    97  	ActiveNet *chaincfg.Params
    98  
    99  	// MaxConnRetries is the maximum number of times we re-try to connect to
   100  	// the node after starting it.
   101  	MaxConnRetries int
   102  
   103  	// ConnectionRetryTimeout is the duration we wait between two connection
   104  	// attempts.
   105  	ConnectionRetryTimeout time.Duration
   106  
   107  	Client      *rpcclient.Client
   108  	BatchClient *rpcclient.Client
   109  	node        *node
   110  	handlers    *rpcclient.NotificationHandlers
   111  
   112  	wallet *memWallet
   113  
   114  	testNodeDir string
   115  	nodeNum     int
   116  
   117  	sync.Mutex
   118  }
   119  
   120  // New creates and initializes new instance of the rpc test harness.
   121  // Optionally, websocket handlers and a specified configuration may be passed.
   122  // In the case that a nil config is passed, a default configuration will be
   123  // used. If a custom btcd executable is specified, it will be used to start the
   124  // harness node. Otherwise a new binary is built on demand.
   125  //
   126  // NOTE: This function is safe for concurrent access.
   127  func New(activeNet *chaincfg.Params, handlers *rpcclient.NotificationHandlers,
   128  	extraArgs []string, customExePath string) (*Harness, error) {
   129  
   130  	harnessStateMtx.Lock()
   131  	defer harnessStateMtx.Unlock()
   132  
   133  	// Add a flag for the appropriate network type based on the provided
   134  	// chain params.
   135  	switch activeNet.Net {
   136  	case wire.MainNet:
   137  		// No extra flags since mainnet is the default
   138  	case wire.TestNet3:
   139  		extraArgs = append(extraArgs, "--testnet")
   140  	case wire.TestNet:
   141  		extraArgs = append(extraArgs, "--regtest")
   142  	case wire.SimNet:
   143  		extraArgs = append(extraArgs, "--simnet")
   144  	default:
   145  		return nil, fmt.Errorf("rpctest.New must be called with one " +
   146  			"of the supported chain networks")
   147  	}
   148  
   149  	testDir, err := baseDir()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	nodeTestData, err := os.MkdirTemp(testDir, "rpc-node")
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	certFile := filepath.Join(nodeTestData, "rpc.cert")
   160  	keyFile := filepath.Join(nodeTestData, "rpc.key")
   161  	if err := genCertPair(certFile, keyFile); err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	wallet, err := newMemWallet(activeNet, uint32(numTestInstances))
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	miningAddr := fmt.Sprintf("--miningaddr=%s", wallet.coinbaseAddr)
   171  	extraArgs = append(extraArgs, miningAddr)
   172  
   173  	config, err := newConfig(
   174  		nodeTestData, certFile, keyFile, extraArgs, customExePath,
   175  	)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	// Generate p2p+rpc listening addresses.
   181  	config.listen, config.rpcListen = ListenAddressGenerator()
   182  
   183  	// Create the testing node bounded to the simnet.
   184  	node, err := newNode(config, nodeTestData)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	nodeNum := numTestInstances
   190  	numTestInstances++
   191  
   192  	if handlers == nil {
   193  		handlers = &rpcclient.NotificationHandlers{}
   194  	}
   195  
   196  	// If a handler for the OnFilteredBlock{Connected,Disconnected} callback
   197  	// callback has already been set, then create a wrapper callback which
   198  	// executes both the currently registered callback and the mem wallet's
   199  	// callback.
   200  	if handlers.OnFilteredBlockConnected != nil {
   201  		obc := handlers.OnFilteredBlockConnected
   202  		handlers.OnFilteredBlockConnected = func(height int32, header *wire.BlockHeader, filteredTxns []*btcutil.Tx) {
   203  			wallet.IngestBlock(height, header, filteredTxns)
   204  			obc(height, header, filteredTxns)
   205  		}
   206  	} else {
   207  		// Otherwise, we can claim the callback ourselves.
   208  		handlers.OnFilteredBlockConnected = wallet.IngestBlock
   209  	}
   210  	if handlers.OnFilteredBlockDisconnected != nil {
   211  		obd := handlers.OnFilteredBlockDisconnected
   212  		handlers.OnFilteredBlockDisconnected = func(height int32, header *wire.BlockHeader) {
   213  			wallet.UnwindBlock(height, header)
   214  			obd(height, header)
   215  		}
   216  	} else {
   217  		handlers.OnFilteredBlockDisconnected = wallet.UnwindBlock
   218  	}
   219  
   220  	h := &Harness{
   221  		handlers:               handlers,
   222  		node:                   node,
   223  		MaxConnRetries:         DefaultMaxConnectionRetries,
   224  		ConnectionRetryTimeout: DefaultConnectionRetryTimeout,
   225  		testNodeDir:            nodeTestData,
   226  		ActiveNet:              activeNet,
   227  		nodeNum:                nodeNum,
   228  		wallet:                 wallet,
   229  	}
   230  
   231  	// Track this newly created test instance within the package level
   232  	// global map of all active test instances.
   233  	testInstances[h.testNodeDir] = h
   234  
   235  	return h, nil
   236  }
   237  
   238  // SetUp initializes the rpc test state. Initialization includes: starting up a
   239  // simnet node, creating a websockets client and connecting to the started
   240  // node, and finally: optionally generating and submitting a testchain with a
   241  // configurable number of mature coinbase outputs coinbase outputs.
   242  //
   243  // NOTE: This method and TearDown should always be called from the same
   244  // goroutine as they are not concurrent safe.
   245  func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
   246  	// Start the btcd node itself. This spawns a new process which will be
   247  	// managed
   248  	if err := h.node.start(); err != nil {
   249  		return fmt.Errorf("error starting node: %w", err)
   250  	}
   251  	if err := h.connectRPCClient(); err != nil {
   252  		return fmt.Errorf("error connecting RPC client: %w", err)
   253  	}
   254  
   255  	h.wallet.Start()
   256  
   257  	// Filter transactions that pay to the coinbase associated with the
   258  	// wallet.
   259  	filterAddrs := []btcutil.Address{h.wallet.coinbaseAddr}
   260  	if err := h.Client.LoadTxFilter(true, filterAddrs, nil); err != nil {
   261  		return err
   262  	}
   263  
   264  	// Ensure btcd properly dispatches our registered call-back for each new
   265  	// block. Otherwise, the memWallet won't function properly.
   266  	if err := h.Client.NotifyBlocks(); err != nil {
   267  		return err
   268  	}
   269  
   270  	// Create a test chain with the desired number of mature coinbase
   271  	// outputs.
   272  	if createTestChain && numMatureOutputs != 0 {
   273  		coinbaseMaturity := uint32(h.ActiveNet.CoinbaseMaturity)
   274  		numToGenerate := coinbaseMaturity + numMatureOutputs
   275  		_, err := h.Client.Generate(numToGenerate)
   276  		if err != nil {
   277  			return err
   278  		}
   279  	}
   280  
   281  	// Block until the wallet has fully synced up to the tip of the main
   282  	// chain.
   283  	_, height, err := h.Client.GetBestBlock()
   284  	if err != nil {
   285  		return err
   286  	}
   287  	ticker := time.NewTicker(time.Millisecond * 100)
   288  	for range ticker.C {
   289  		walletHeight := h.wallet.SyncedHeight()
   290  		if walletHeight == height {
   291  			break
   292  		}
   293  	}
   294  	ticker.Stop()
   295  
   296  	return nil
   297  }
   298  
   299  // tearDown stops the running rpc test instance.  All created processes are
   300  // killed, and temporary directories removed.
   301  //
   302  // This function MUST be called with the harness state mutex held (for writes).
   303  func (h *Harness) tearDown() error {
   304  	if h.Client != nil {
   305  		h.Client.Shutdown()
   306  	}
   307  
   308  	if h.BatchClient != nil {
   309  		h.BatchClient.Shutdown()
   310  	}
   311  
   312  	if err := h.node.shutdown(); err != nil {
   313  		return err
   314  	}
   315  
   316  	if err := os.RemoveAll(h.testNodeDir); err != nil {
   317  		return err
   318  	}
   319  
   320  	delete(testInstances, h.testNodeDir)
   321  
   322  	return nil
   323  }
   324  
   325  // TearDown stops the running rpc test instance. All created processes are
   326  // killed, and temporary directories removed.
   327  //
   328  // NOTE: This method and SetUp should always be called from the same goroutine
   329  // as they are not concurrent safe.
   330  func (h *Harness) TearDown() error {
   331  	harnessStateMtx.Lock()
   332  	defer harnessStateMtx.Unlock()
   333  
   334  	return h.tearDown()
   335  }
   336  
   337  // connectRPCClient attempts to establish an RPC connection to the created btcd
   338  // process belonging to this Harness instance. If the initial connection
   339  // attempt fails, this function will retry h.maxConnRetries times, backing off
   340  // the time between subsequent attempts. If after h.maxConnRetries attempts,
   341  // we're not able to establish a connection, this function returns with an
   342  // error.
   343  func (h *Harness) connectRPCClient() error {
   344  	var client, batchClient *rpcclient.Client
   345  	var err error
   346  
   347  	rpcConf := h.node.config.rpcConnConfig()
   348  	batchConf := h.node.config.rpcConnConfig()
   349  	batchConf.HTTPPostMode = true
   350  	for i := 0; i < h.MaxConnRetries; i++ {
   351  		fail := false
   352  		timeout := time.Duration(i) * h.ConnectionRetryTimeout
   353  		if client == nil {
   354  			client, err = rpcclient.New(&rpcConf, h.handlers)
   355  			if err != nil {
   356  				time.Sleep(timeout)
   357  				fail = true
   358  			}
   359  		}
   360  		if batchClient == nil {
   361  			batchClient, err = rpcclient.NewBatch(&batchConf)
   362  			if err != nil {
   363  				time.Sleep(timeout)
   364  				fail = true
   365  			}
   366  		}
   367  		if !fail {
   368  			break
   369  		}
   370  	}
   371  
   372  	if client == nil || batchClient == nil {
   373  		return fmt.Errorf("connection timeout, tried %d times with "+
   374  			"timeout %v, last err: %w", h.MaxConnRetries,
   375  			h.ConnectionRetryTimeout, err)
   376  	}
   377  
   378  	h.Client = client
   379  	h.wallet.SetRPCClient(client)
   380  	h.BatchClient = batchClient
   381  	return nil
   382  }
   383  
   384  // NewAddress returns a fresh address spendable by the Harness' internal
   385  // wallet.
   386  //
   387  // This function is safe for concurrent access.
   388  func (h *Harness) NewAddress() (btcutil.Address, error) {
   389  	return h.wallet.NewAddress()
   390  }
   391  
   392  // ConfirmedBalance returns the confirmed balance of the Harness' internal
   393  // wallet.
   394  //
   395  // This function is safe for concurrent access.
   396  func (h *Harness) ConfirmedBalance() btcutil.Amount {
   397  	return h.wallet.ConfirmedBalance()
   398  }
   399  
   400  // SendOutputs creates, signs, and finally broadcasts a transaction spending
   401  // the harness' available mature coinbase outputs creating new outputs
   402  // according to targetOutputs.
   403  //
   404  // This function is safe for concurrent access.
   405  func (h *Harness) SendOutputs(targetOutputs []*wire.TxOut,
   406  	feeRate btcutil.Amount) (*chainhash.Hash, error) {
   407  
   408  	return h.wallet.SendOutputs(targetOutputs, feeRate)
   409  }
   410  
   411  // SendOutputsWithoutChange creates and sends a transaction that pays to the
   412  // specified outputs while observing the passed fee rate and ignoring a change
   413  // output. The passed fee rate should be expressed in sat/b.
   414  //
   415  // This function is safe for concurrent access.
   416  func (h *Harness) SendOutputsWithoutChange(targetOutputs []*wire.TxOut,
   417  	feeRate btcutil.Amount) (*chainhash.Hash, error) {
   418  
   419  	return h.wallet.SendOutputsWithoutChange(targetOutputs, feeRate)
   420  }
   421  
   422  // CreateTransaction returns a fully signed transaction paying to the specified
   423  // outputs while observing the desired fee rate. The passed fee rate should be
   424  // expressed in satoshis-per-byte. The transaction being created can optionally
   425  // include a change output indicated by the change boolean. Any unspent outputs
   426  // selected as inputs for the crafted transaction are marked as unspendable in
   427  // order to avoid potential double-spends by future calls to this method. If the
   428  // created transaction is cancelled for any reason then the selected inputs MUST
   429  // be freed via a call to UnlockOutputs. Otherwise, the locked inputs won't be
   430  // returned to the pool of spendable outputs.
   431  //
   432  // This function is safe for concurrent access.
   433  func (h *Harness) CreateTransaction(targetOutputs []*wire.TxOut,
   434  	feeRate btcutil.Amount, change bool) (*wire.MsgTx, error) {
   435  
   436  	return h.wallet.CreateTransaction(targetOutputs, feeRate, change)
   437  }
   438  
   439  // UnlockOutputs unlocks any outputs which were previously marked as
   440  // unspendabe due to being selected to fund a transaction via the
   441  // CreateTransaction method.
   442  //
   443  // This function is safe for concurrent access.
   444  func (h *Harness) UnlockOutputs(inputs []*wire.TxIn) {
   445  	h.wallet.UnlockOutputs(inputs)
   446  }
   447  
   448  // RPCConfig returns the harnesses current rpc configuration. This allows other
   449  // potential RPC clients created within tests to connect to a given test
   450  // harness instance.
   451  func (h *Harness) RPCConfig() rpcclient.ConnConfig {
   452  	return h.node.config.rpcConnConfig()
   453  }
   454  
   455  // P2PAddress returns the harness' P2P listening address. This allows potential
   456  // peers (such as SPV peers) created within tests to connect to a given test
   457  // harness instance.
   458  func (h *Harness) P2PAddress() string {
   459  	return h.node.config.listen
   460  }
   461  
   462  // GenerateAndSubmitBlock creates a block whose contents include the passed
   463  // transactions and submits it to the running simnet node. For generating
   464  // blocks with only a coinbase tx, callers can simply pass nil instead of
   465  // transactions to be mined. Additionally, a custom block version can be set by
   466  // the caller. A blockVersion of -1 indicates that the current default block
   467  // version should be used. An uninitialized time.Time should be used for the
   468  // blockTime parameter if one doesn't wish to set a custom time.
   469  //
   470  // This function is safe for concurrent access.
   471  func (h *Harness) GenerateAndSubmitBlock(txns []*btcutil.Tx, blockVersion int32,
   472  	blockTime time.Time) (*btcutil.Block, error) {
   473  	return h.GenerateAndSubmitBlockWithCustomCoinbaseOutputs(txns,
   474  		blockVersion, blockTime, []wire.TxOut{})
   475  }
   476  
   477  // GenerateAndSubmitBlockWithCustomCoinbaseOutputs creates a block whose
   478  // contents include the passed coinbase outputs and transactions and submits
   479  // it to the running simnet node. For generating blocks with only a coinbase tx,
   480  // callers can simply pass nil instead of transactions to be mined.
   481  // Additionally, a custom block version can be set by the caller. A blockVersion
   482  // of -1 indicates that the current default block version should be used. An
   483  // uninitialized time.Time should be used for the blockTime parameter if one
   484  // doesn't wish to set a custom time. The mineTo list of outputs will be added
   485  // to the coinbase; this is not checked for correctness until the block is
   486  // submitted; thus, it is the caller's responsibility to ensure that the outputs
   487  // are correct. If the list is empty, the coinbase reward goes to the wallet
   488  // managed by the Harness.
   489  //
   490  // This function is safe for concurrent access.
   491  func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
   492  	txns []*btcutil.Tx, blockVersion int32, blockTime time.Time,
   493  	mineTo []wire.TxOut) (*btcutil.Block, error) {
   494  
   495  	h.Lock()
   496  	defer h.Unlock()
   497  
   498  	if blockVersion == -1 {
   499  		blockVersion = BlockVersion
   500  	}
   501  
   502  	prevBlockHash, prevBlockHeight, err := h.Client.GetBestBlock()
   503  	if err != nil {
   504  		return nil, err
   505  	}
   506  	mBlock, err := h.Client.GetBlock(prevBlockHash)
   507  	if err != nil {
   508  		return nil, err
   509  	}
   510  	prevBlock := btcutil.NewBlock(mBlock)
   511  	prevBlock.SetHeight(prevBlockHeight)
   512  
   513  	// Create a new block including the specified transactions
   514  	newBlock, err := CreateBlock(prevBlock, txns, blockVersion,
   515  		blockTime, h.wallet.coinbaseAddr, mineTo, h.ActiveNet)
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  
   520  	// Submit the block to the simnet node.
   521  	if err := h.Client.SubmitBlock(newBlock, nil); err != nil {
   522  		return nil, err
   523  	}
   524  
   525  	return newBlock, nil
   526  }
   527  
   528  // generateListeningAddresses is a function that returns two listener
   529  // addresses with unique ports and should be used to overwrite rpctest's
   530  // default generator which is prone to use colliding ports.
   531  func generateListeningAddresses() (string, string) {
   532  	return fmt.Sprintf(ListenerFormat, NextAvailablePort()),
   533  		fmt.Sprintf(ListenerFormat, NextAvailablePort())
   534  }
   535  
   536  // NextAvailablePort returns the first port that is available for listening by
   537  // a new node. It panics if no port is found and the maximum available TCP port
   538  // is reached.
   539  func NextAvailablePort() int {
   540  	port := atomic.AddUint32(&lastPort, 1)
   541  	for port < 65535 {
   542  		// If there are no errors while attempting to listen on this
   543  		// port, close the socket and return it as available. While it
   544  		// could be the case that some other process picks up this port
   545  		// between the time the socket is closed and it's reopened in
   546  		// the harness node, in practice in CI servers this seems much
   547  		// less likely than simply some other process already being
   548  		// bound at the start of the tests.
   549  		addr := fmt.Sprintf(ListenerFormat, port)
   550  		l, err := net.Listen("tcp4", addr)
   551  		if err == nil {
   552  			err := l.Close()
   553  			if err == nil {
   554  				return int(port)
   555  			}
   556  		}
   557  		port = atomic.AddUint32(&lastPort, 1)
   558  	}
   559  
   560  	// No ports available? Must be a mistake.
   561  	panic("no ports available for listening")
   562  }
   563  
   564  // NextAvailablePortForProcess returns the first port that is available for
   565  // listening by a new node, using a lock file to make sure concurrent access for
   566  // parallel tasks within the same process don't re-use the same port. It panics
   567  // if no port is found and the maximum available TCP port is reached.
   568  func NextAvailablePortForProcess(pid int) int {
   569  	lockFile := filepath.Join(
   570  		os.TempDir(), fmt.Sprintf("rpctest-port-pid-%d.lock", pid),
   571  	)
   572  	timeout := time.After(time.Second)
   573  	
   574  	var (
   575  		lockFileHandle *os.File
   576  		err error
   577  	)
   578  	for {
   579  		// Attempt to acquire the lock file. If it already exists, wait
   580  		// for a bit and retry.
   581  		lockFileHandle, err = os.OpenFile(
   582  			lockFile, os.O_CREATE|os.O_EXCL, 0600,
   583  		)
   584  		if err == nil {
   585  			// Lock acquired.
   586  			break
   587  		}
   588  
   589  		// Wait for a bit and retry.
   590  		select {
   591  		case <-timeout:
   592  			panic("timeout waiting for lock file")
   593  		case <-time.After(10 * time.Millisecond):
   594  		}
   595  	}
   596  
   597  	// Release the lock file when we're done.
   598  	defer func() {
   599  		// Always close file first, Windows won't allow us to remove it
   600  		// otherwise.
   601  		_ = lockFileHandle.Close()
   602  		err := os.Remove(lockFile)
   603  		if err != nil {
   604  			panic(fmt.Errorf("couldn't remove lock file: %w", err))
   605  		}
   606  	}()
   607  
   608  	portFile := filepath.Join(
   609  		os.TempDir(), fmt.Sprintf("rpctest-port-pid-%d", pid),
   610  	)
   611  	port, err := os.ReadFile(portFile)
   612  	if err != nil {
   613  		if !os.IsNotExist(err) {
   614  			panic(fmt.Errorf("error reading port file: %w", err))
   615  		}
   616  		port = []byte(strconv.Itoa(int(defaultNodePort)))
   617  	}
   618  
   619  	lastPort, err := strconv.Atoi(string(port))
   620  	if err != nil {
   621  		panic(fmt.Errorf("error parsing port: %w", err))
   622  	}
   623  
   624  	// We take the next one.
   625  	lastPort++
   626  	for lastPort < 65535 {
   627  		// If there are no errors while attempting to listen on this
   628  		// port, close the socket and return it as available. While it
   629  		// could be the case that some other process picks up this port
   630  		// between the time the socket is closed and it's reopened in
   631  		// the harness node, in practice in CI servers this seems much
   632  		// less likely than simply some other process already being
   633  		// bound at the start of the tests.
   634  		addr := fmt.Sprintf(ListenerFormat, lastPort)
   635  		l, err := net.Listen("tcp4", addr)
   636  		if err == nil {
   637  			err := l.Close()
   638  			if err == nil {
   639  				err := os.WriteFile(
   640  					portFile,
   641  					[]byte(strconv.Itoa(lastPort)), 0600,
   642  				)
   643  				if err != nil {
   644  					panic(fmt.Errorf("error updating "+
   645  						"port file: %w", err))
   646  				}
   647  
   648  				return lastPort
   649  			}
   650  		}
   651  		lastPort++
   652  	}
   653  
   654  	// No ports available? Must be a mistake.
   655  	panic("no ports available for listening")
   656  }
   657  
   658  // GenerateProcessUniqueListenerAddresses is a function that returns two
   659  // listener addresses with unique ports per the given process id and should be
   660  // used to overwrite rpctest's default generator which is prone to use colliding
   661  // ports.
   662  func GenerateProcessUniqueListenerAddresses(pid int) (string, string) {
   663  	port1 := NextAvailablePortForProcess(pid)
   664  	port2 := NextAvailablePortForProcess(pid)
   665  	return fmt.Sprintf(ListenerFormat, port1),
   666  		fmt.Sprintf(ListenerFormat, port2)
   667  }
   668  
   669  // baseDir is the directory path of the temp directory for all rpctest files.
   670  func baseDir() (string, error) {
   671  	dirPath := filepath.Join(os.TempDir(), "btcd", "rpctest")
   672  	err := os.MkdirAll(dirPath, 0755)
   673  	return dirPath, err
   674  }