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 }