github.com/btcsuite/btcd@v0.24.0/integration/rpctest/node.go (about)

     1  // Copyright (c) 2016 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  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"time"
    15  
    16  	"github.com/btcsuite/btcd/btcutil"
    17  	rpc "github.com/btcsuite/btcd/rpcclient"
    18  )
    19  
    20  // nodeConfig contains all the args, and data required to launch a btcd process
    21  // and connect the rpc client to it.
    22  type nodeConfig struct {
    23  	rpcUser    string
    24  	rpcPass    string
    25  	listen     string
    26  	rpcListen  string
    27  	rpcConnect string
    28  	dataDir    string
    29  	logDir     string
    30  	profile    string
    31  	debugLevel string
    32  	extra      []string
    33  	nodeDir    string
    34  
    35  	exe          string
    36  	endpoint     string
    37  	certFile     string
    38  	keyFile      string
    39  	certificates []byte
    40  }
    41  
    42  // newConfig returns a newConfig with all default values.
    43  func newConfig(nodeDir, certFile, keyFile string, extra []string,
    44  	customExePath string) (*nodeConfig, error) {
    45  
    46  	var btcdPath string
    47  	if customExePath != "" {
    48  		btcdPath = customExePath
    49  	} else {
    50  		var err error
    51  		btcdPath, err = btcdExecutablePath()
    52  		if err != nil {
    53  			btcdPath = "btcd"
    54  		}
    55  	}
    56  
    57  	a := &nodeConfig{
    58  		listen:    "127.0.0.1:18555",
    59  		rpcListen: "127.0.0.1:18556",
    60  		rpcUser:   "user",
    61  		rpcPass:   "pass",
    62  		extra:     extra,
    63  		nodeDir:   nodeDir,
    64  		exe:       btcdPath,
    65  		endpoint:  "ws",
    66  		certFile:  certFile,
    67  		keyFile:   keyFile,
    68  	}
    69  	if err := a.setDefaults(); err != nil {
    70  		return nil, err
    71  	}
    72  	return a, nil
    73  }
    74  
    75  // setDefaults sets the default values of the config. It also creates the
    76  // temporary data, and log directories which must be cleaned up with a call to
    77  // cleanup().
    78  func (n *nodeConfig) setDefaults() error {
    79  	n.dataDir = filepath.Join(n.nodeDir, "data")
    80  	n.logDir = filepath.Join(n.nodeDir, "logs")
    81  	cert, err := os.ReadFile(n.certFile)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	n.certificates = cert
    86  	return nil
    87  }
    88  
    89  // arguments returns an array of arguments that be used to launch the btcd
    90  // process.
    91  func (n *nodeConfig) arguments() []string {
    92  	args := []string{}
    93  	if n.rpcUser != "" {
    94  		// --rpcuser
    95  		args = append(args, fmt.Sprintf("--rpcuser=%s", n.rpcUser))
    96  	}
    97  	if n.rpcPass != "" {
    98  		// --rpcpass
    99  		args = append(args, fmt.Sprintf("--rpcpass=%s", n.rpcPass))
   100  	}
   101  	if n.listen != "" {
   102  		// --listen
   103  		args = append(args, fmt.Sprintf("--listen=%s", n.listen))
   104  	}
   105  	if n.rpcListen != "" {
   106  		// --rpclisten
   107  		args = append(args, fmt.Sprintf("--rpclisten=%s", n.rpcListen))
   108  	}
   109  	if n.rpcConnect != "" {
   110  		// --rpcconnect
   111  		args = append(args, fmt.Sprintf("--rpcconnect=%s", n.rpcConnect))
   112  	}
   113  	// --rpccert
   114  	args = append(args, fmt.Sprintf("--rpccert=%s", n.certFile))
   115  	// --rpckey
   116  	args = append(args, fmt.Sprintf("--rpckey=%s", n.keyFile))
   117  	if n.dataDir != "" {
   118  		// --datadir
   119  		args = append(args, fmt.Sprintf("--datadir=%s", n.dataDir))
   120  	}
   121  	if n.logDir != "" {
   122  		// --logdir
   123  		args = append(args, fmt.Sprintf("--logdir=%s", n.logDir))
   124  	}
   125  	if n.profile != "" {
   126  		// --profile
   127  		args = append(args, fmt.Sprintf("--profile=%s", n.profile))
   128  	}
   129  	if n.debugLevel != "" {
   130  		// --debuglevel
   131  		args = append(args, fmt.Sprintf("--debuglevel=%s", n.debugLevel))
   132  	}
   133  	args = append(args, n.extra...)
   134  	return args
   135  }
   136  
   137  // command returns the exec.Cmd which will be used to start the btcd process.
   138  func (n *nodeConfig) command() *exec.Cmd {
   139  	return exec.Command(n.exe, n.arguments()...)
   140  }
   141  
   142  // rpcConnConfig returns the rpc connection config that can be used to connect
   143  // to the btcd process that is launched via Start().
   144  func (n *nodeConfig) rpcConnConfig() rpc.ConnConfig {
   145  	return rpc.ConnConfig{
   146  		Host:                 n.rpcListen,
   147  		Endpoint:             n.endpoint,
   148  		User:                 n.rpcUser,
   149  		Pass:                 n.rpcPass,
   150  		Certificates:         n.certificates,
   151  		DisableAutoReconnect: true,
   152  	}
   153  }
   154  
   155  // String returns the string representation of this nodeConfig.
   156  func (n *nodeConfig) String() string {
   157  	return n.nodeDir
   158  }
   159  
   160  // node houses the necessary state required to configure, launch, and manage a
   161  // btcd process.
   162  type node struct {
   163  	config *nodeConfig
   164  
   165  	cmd     *exec.Cmd
   166  	pidFile string
   167  
   168  	dataDir string
   169  }
   170  
   171  // newNode creates a new node instance according to the passed config. dataDir
   172  // will be used to hold a file recording the pid of the launched process, and
   173  // as the base for the log and data directories for btcd.
   174  func newNode(config *nodeConfig, dataDir string) (*node, error) {
   175  	return &node{
   176  		config:  config,
   177  		dataDir: dataDir,
   178  		cmd:     config.command(),
   179  	}, nil
   180  }
   181  
   182  // start creates a new btcd process, and writes its pid in a file reserved for
   183  // recording the pid of the launched process. This file can be used to
   184  // terminate the process in case of a hang, or panic. In the case of a failing
   185  // test case, or panic, it is important that the process be stopped via stop(),
   186  // otherwise, it will persist unless explicitly killed.
   187  func (n *node) start() error {
   188  	if err := n.cmd.Start(); err != nil {
   189  		return err
   190  	}
   191  
   192  	pid, err := os.Create(filepath.Join(n.dataDir, "btcd.pid"))
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	n.pidFile = pid.Name()
   198  	if _, err = fmt.Fprintf(pid, "%d\n", n.cmd.Process.Pid); err != nil {
   199  		return err
   200  	}
   201  
   202  	if err := pid.Close(); err != nil {
   203  		return err
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  // stop interrupts the running btcd process process, and waits until it exits
   210  // properly. On windows, interrupt is not supported, so a kill signal is used
   211  // instead
   212  func (n *node) stop() error {
   213  	if n.cmd == nil || n.cmd.Process == nil {
   214  		// return if not properly initialized
   215  		// or error starting the process
   216  		return nil
   217  	}
   218  	defer n.cmd.Wait()
   219  	if runtime.GOOS == "windows" {
   220  		return n.cmd.Process.Signal(os.Kill)
   221  	}
   222  	return n.cmd.Process.Signal(os.Interrupt)
   223  }
   224  
   225  // cleanup cleanups process and args files. The file housing the pid of the
   226  // created process will be deleted, as well as any directories created by the
   227  // process.
   228  func (n *node) cleanup() error {
   229  	if n.pidFile != "" {
   230  		if err := os.Remove(n.pidFile); err != nil {
   231  			log.Printf("unable to remove file %s: %v", n.pidFile,
   232  				err)
   233  		}
   234  	}
   235  
   236  	// Since the node's main data directory is passed in to the node config,
   237  	// it isn't our responsibility to clean it up. So we're done after
   238  	// removing the pid file.
   239  	return nil
   240  }
   241  
   242  // shutdown terminates the running btcd process, and cleans up all
   243  // file/directories created by node.
   244  func (n *node) shutdown() error {
   245  	if err := n.stop(); err != nil {
   246  		return err
   247  	}
   248  	if err := n.cleanup(); err != nil {
   249  		return err
   250  	}
   251  	return nil
   252  }
   253  
   254  // genCertPair generates a key/cert pair to the paths provided.
   255  func genCertPair(certFile, keyFile string) error {
   256  	org := "rpctest autogenerated cert"
   257  	validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
   258  	cert, key, err := btcutil.NewTLSCertPair(org, validUntil, nil)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	// Write cert and key files.
   264  	if err = os.WriteFile(certFile, cert, 0666); err != nil {
   265  		return err
   266  	}
   267  	if err = os.WriteFile(keyFile, key, 0600); err != nil {
   268  		_ = os.Remove(certFile)
   269  		return err
   270  	}
   271  
   272  	return nil
   273  }