gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/p2p/simulations/adapters/exec.go (about) 1 // Copyright 2018 The aquachain Authors 2 // This file is part of the aquachain library. 3 // 4 // The aquachain library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The aquachain library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the aquachain library. If not, see <http://www.gnu.org/licenses/>. 16 17 package adapters 18 19 import ( 20 "bufio" 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "net" 27 "os" 28 "os/exec" 29 "os/signal" 30 "path/filepath" 31 "regexp" 32 "strings" 33 "sync" 34 "syscall" 35 "time" 36 37 "gitlab.com/aquachain/aquachain/common/log" 38 "gitlab.com/aquachain/aquachain/node" 39 "gitlab.com/aquachain/aquachain/opt/reexec" 40 "gitlab.com/aquachain/aquachain/p2p" 41 "gitlab.com/aquachain/aquachain/p2p/discover" 42 aquarpc "gitlab.com/aquachain/aquachain/rpc" 43 rpc "gitlab.com/aquachain/aquachain/rpc/rpcclient" 44 "golang.org/x/net/websocket" 45 ) 46 47 // ExecAdapter is a NodeAdapter which runs simulation nodes by executing the 48 // current binary as a child process. 49 // 50 // An init hook is used so that the child process executes the node services 51 // (rather than whataver the main() function would normally do), see the 52 // execP2PNode function for more information. 53 type ExecAdapter struct { 54 // BaseDir is the directory under which the data directories for each 55 // simulation node are created. 56 BaseDir string 57 58 nodes map[discover.NodeID]*ExecNode 59 } 60 61 // NewExecAdapter returns an ExecAdapter which stores node data in 62 // subdirectories of the given base directory 63 func NewExecAdapter(baseDir string) *ExecAdapter { 64 return &ExecAdapter{ 65 BaseDir: baseDir, 66 nodes: make(map[discover.NodeID]*ExecNode), 67 } 68 } 69 70 // Name returns the name of the adapter for logging purposes 71 func (e *ExecAdapter) Name() string { 72 return "exec-adapter" 73 } 74 75 // NewNode returns a new ExecNode using the given config 76 func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { 77 if len(config.Services) == 0 { 78 return nil, errors.New("node must have at least one service") 79 } 80 for _, service := range config.Services { 81 if _, exists := serviceFuncs[service]; !exists { 82 return nil, fmt.Errorf("unknown node service %q", service) 83 } 84 } 85 86 // create the node directory using the first 12 characters of the ID 87 // as Unix socket paths cannot be longer than 256 characters 88 dir := filepath.Join(e.BaseDir, config.ID.String()[:12]) 89 if err := os.Mkdir(dir, 0755); err != nil { 90 return nil, fmt.Errorf("error creating node directory: %s", err) 91 } 92 93 // generate the config 94 conf := &execNodeConfig{ 95 Stack: node.DefaultConfig, 96 Node: config, 97 } 98 conf.Stack.DataDir = filepath.Join(dir, "data") 99 conf.Stack.WSHost = "127.0.0.1" 100 conf.Stack.WSPort = 0 101 conf.Stack.WSOrigins = []string{"*"} 102 conf.Stack.WSExposeAll = true 103 conf.Stack.P2P.EnableMsgEvents = false 104 conf.Stack.P2P.NoDiscovery = true 105 conf.Stack.P2P.NAT = nil 106 107 // listen on a random localhost port (we'll get the actual port after 108 // starting the node through the RPC admin.nodeInfo method) 109 conf.Stack.P2P.ListenAddr = "127.0.0.1:0" 110 111 node := &ExecNode{ 112 ID: config.ID, 113 Dir: dir, 114 Config: conf, 115 adapter: e, 116 } 117 node.newCmd = node.execCommand 118 e.nodes[node.ID] = node 119 return node, nil 120 } 121 122 // ExecNode starts a simulation node by exec'ing the current binary and 123 // running the configured services 124 type ExecNode struct { 125 ID discover.NodeID 126 Dir string 127 Config *execNodeConfig 128 Cmd *exec.Cmd 129 Info *p2p.NodeInfo 130 131 adapter *ExecAdapter 132 client *rpc.Client 133 wsAddr string 134 newCmd func() *exec.Cmd 135 } 136 137 // Addr returns the node's enode URL 138 func (n *ExecNode) Addr() []byte { 139 if n.Info == nil { 140 return nil 141 } 142 return []byte(n.Info.Enode) 143 } 144 145 // Client returns an rpc.Client which can be used to communicate with the 146 // underlying services (it is set once the node has started) 147 func (n *ExecNode) Client() (*rpc.Client, error) { 148 return n.client, nil 149 } 150 151 // wsAddrPattern is a regex used to read the WebSocket address from the node's 152 // log 153 var wsAddrPattern = regexp.MustCompile(`ws://[\d.:]+`) 154 155 // Start exec's the node passing the ID and service as command line arguments 156 // and the node config encoded as JSON in the _P2P_NODE_CONFIG environment 157 // variable 158 func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { 159 if n.Cmd != nil { 160 return errors.New("already started") 161 } 162 defer func() { 163 if err != nil { 164 log.Error("node failed to start", "err", err) 165 n.Stop() 166 } 167 }() 168 169 // encode a copy of the config containing the snapshot 170 confCopy := *n.Config 171 confCopy.Snapshots = snapshots 172 confCopy.PeerAddrs = make(map[string]string) 173 for id, node := range n.adapter.nodes { 174 confCopy.PeerAddrs[id.String()] = node.wsAddr 175 } 176 confData, err := json.Marshal(confCopy) 177 if err != nil { 178 return fmt.Errorf("error generating node config: %s", err) 179 } 180 181 // use a pipe for stderr so we can both copy the node's stderr to 182 // os.Stderr and read the WebSocket address from the logs 183 stderrR, stderrW := io.Pipe() 184 stderr := io.MultiWriter(os.Stderr, stderrW) 185 186 // start the node 187 cmd := n.newCmd() 188 cmd.Stdout = os.Stdout 189 cmd.Stderr = stderr 190 cmd.Env = append(os.Environ(), fmt.Sprintf("_P2P_NODE_CONFIG=%s", confData)) 191 if err := cmd.Start(); err != nil { 192 return fmt.Errorf("error starting node: %s", err) 193 } 194 n.Cmd = cmd 195 196 // read the WebSocket address from the stderr logs 197 var wsAddr string 198 wsAddrC := make(chan string) 199 go func() { 200 s := bufio.NewScanner(stderrR) 201 for s.Scan() { 202 if strings.Contains(s.Text(), "WebSocket endpoint opened:") { 203 wsAddrC <- wsAddrPattern.FindString(s.Text()) 204 } 205 } 206 }() 207 select { 208 case wsAddr = <-wsAddrC: 209 if wsAddr == "" { 210 return errors.New("failed to read WebSocket address from stderr") 211 } 212 case <-time.After(10 * time.Second): 213 return errors.New("timed out waiting for WebSocket address on stderr") 214 } 215 216 // create the RPC client and load the node info 217 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 218 defer cancel() 219 client, err := rpc.DialWebsocket(ctx, wsAddr, "") 220 if err != nil { 221 return fmt.Errorf("error dialing rpc websocket: %s", err) 222 } 223 var info p2p.NodeInfo 224 if err := client.CallContext(ctx, &info, "admin_nodeInfo"); err != nil { 225 return fmt.Errorf("error getting node info: %s", err) 226 } 227 n.client = client 228 n.wsAddr = wsAddr 229 n.Info = &info 230 231 return nil 232 } 233 234 // execCommand returns a command which runs the node locally by exec'ing 235 // the current binary but setting argv[0] to "p2p-node" so that the child 236 // runs execP2PNode 237 func (n *ExecNode) execCommand() *exec.Cmd { 238 return &exec.Cmd{ 239 Path: reexec.Self(), 240 Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()}, 241 } 242 } 243 244 // Stop stops the node by first sending SIGTERM and then SIGKILL if the node 245 // doesn't stop within 5s 246 func (n *ExecNode) Stop() error { 247 if n.Cmd == nil { 248 return nil 249 } 250 defer func() { 251 n.Cmd = nil 252 }() 253 254 if n.client != nil { 255 n.client.Close() 256 n.client = nil 257 n.wsAddr = "" 258 n.Info = nil 259 } 260 261 if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil { 262 return n.Cmd.Process.Kill() 263 } 264 waitErr := make(chan error) 265 go func() { 266 waitErr <- n.Cmd.Wait() 267 }() 268 select { 269 case err := <-waitErr: 270 return err 271 case <-time.After(5 * time.Second): 272 return n.Cmd.Process.Kill() 273 } 274 } 275 276 // NodeInfo returns information about the node 277 func (n *ExecNode) NodeInfo() *p2p.NodeInfo { 278 info := &p2p.NodeInfo{ 279 ID: n.ID.String(), 280 } 281 if n.client != nil { 282 n.client.Call(&info, "admin_nodeInfo") 283 } 284 return info 285 } 286 287 // ServeRPC serves RPC requests over the given connection by dialling the 288 // node's WebSocket address and joining the two connections 289 func (n *ExecNode) ServeRPC(clientConn net.Conn) error { 290 conn, err := websocket.Dial(n.wsAddr, "", "http://localhost") 291 if err != nil { 292 return err 293 } 294 var wg sync.WaitGroup 295 wg.Add(2) 296 join := func(src, dst net.Conn) { 297 defer wg.Done() 298 io.Copy(dst, src) 299 // close the write end of the destination connection 300 if cw, ok := dst.(interface { 301 CloseWrite() error 302 }); ok { 303 cw.CloseWrite() 304 } else { 305 dst.Close() 306 } 307 } 308 go join(conn, clientConn) 309 go join(clientConn, conn) 310 wg.Wait() 311 return nil 312 } 313 314 // Snapshots creates snapshots of the services by calling the 315 // simulation_snapshot RPC method 316 func (n *ExecNode) Snapshots() (map[string][]byte, error) { 317 if n.client == nil { 318 return nil, errors.New("RPC not started") 319 } 320 var snapshots map[string][]byte 321 return snapshots, n.client.Call(&snapshots, "simulation_snapshot") 322 } 323 324 func init() { 325 // register a reexec function to start a devp2p node when the current 326 // binary is executed as "p2p-node" 327 reexec.Register("p2p-node", execP2PNode) 328 } 329 330 // execNodeConfig is used to serialize the node configuration so it can be 331 // passed to the child process as a JSON encoded environment variable 332 type execNodeConfig struct { 333 Stack node.Config `json:"stack"` 334 Node *NodeConfig `json:"node"` 335 Snapshots map[string][]byte `json:"snapshots,omitempty"` 336 PeerAddrs map[string]string `json:"peer_addrs,omitempty"` 337 } 338 339 // execP2PNode starts a devp2p node when the current binary is executed with 340 // argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2] 341 // and the node config from the _P2P_NODE_CONFIG environment variable 342 func execP2PNode() { 343 glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat())) 344 glogger.Verbosity(log.LvlInfo) 345 log.Root().SetHandler(glogger) 346 347 // read the services from argv 348 serviceNames := strings.Split(os.Args[1], ",") 349 350 // decode the config 351 confEnv := os.Getenv("_P2P_NODE_CONFIG") 352 if confEnv == "" { 353 log.Crit("missing _P2P_NODE_CONFIG") 354 } 355 var conf execNodeConfig 356 if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { 357 log.Crit("error decoding _P2P_NODE_CONFIG", "err", err) 358 } 359 conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey 360 conf.Stack.Logger = log.New("node.id", conf.Node.ID.String()) 361 362 // use explicit IP address in ListenAddr so that Enode URL is usable 363 externalIP := func() string { 364 addrs, err := net.InterfaceAddrs() 365 if err != nil { 366 log.Crit("error getting IP address", "err", err) 367 } 368 for _, addr := range addrs { 369 if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() { 370 return ip.IP.String() 371 } 372 } 373 log.Crit("unable to determine explicit IP address") 374 return "" 375 } 376 if strings.HasPrefix(conf.Stack.P2P.ListenAddr, ":") { 377 conf.Stack.P2P.ListenAddr = externalIP() + conf.Stack.P2P.ListenAddr 378 } 379 if conf.Stack.WSHost == "0.0.0.0" { 380 conf.Stack.WSHost = externalIP() 381 } 382 383 // initialize the devp2p stack 384 stack, err := node.New(&conf.Stack) 385 if err != nil { 386 log.Crit("error creating node stack", "err", err) 387 } 388 389 // register the services, collecting them into a map so we can wrap 390 // them in a snapshot service 391 services := make(map[string]node.Service, len(serviceNames)) 392 for _, svcname := range serviceNames { 393 svcname := svcname 394 serviceFunc, exists := serviceFuncs[svcname] 395 if !exists { 396 log.Crit("unknown node service", "name", svcname) 397 } 398 constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) { 399 name := svcname 400 ctx := &ServiceContext{ 401 RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, 402 NodeContext: nodeCtx, 403 Config: conf.Node, 404 } 405 if conf.Snapshots != nil { 406 ctx.Snapshot = conf.Snapshots[name] 407 } 408 service, err := serviceFunc(ctx) 409 if err != nil { 410 return nil, err 411 } 412 services[name] = service 413 return service, nil 414 } 415 if err := stack.Register(constructor); err != nil { 416 log.Crit("error starting service", "name", svcname, "err", err) 417 } 418 } 419 420 // register the snapshot service 421 if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 422 return &snapshotService{services}, nil 423 }); err != nil { 424 log.Crit("error starting snapshot service", "err", err) 425 } 426 427 // start the stack 428 if err := stack.Start(); err != nil { 429 log.Crit("error stating node stack", "err", err) 430 } 431 432 // stop the stack if we get a SIGTERM signal 433 go func() { 434 sigc := make(chan os.Signal, 1) 435 signal.Notify(sigc, syscall.SIGTERM) 436 defer signal.Stop(sigc) 437 <-sigc 438 log.Info("Received SIGTERM, shutting down...") 439 stack.Stop() 440 }() 441 442 // wait for the stack to exit 443 stack.Wait() 444 } 445 446 // snapshotService is a node.Service which wraps a list of services and 447 // exposes an API to generate a snapshot of those services 448 type snapshotService struct { 449 services map[string]node.Service 450 } 451 452 func (s *snapshotService) APIs() []aquarpc.API { 453 return []aquarpc.API{{ 454 Namespace: "simulation", 455 Version: "1.0", 456 Service: SnapshotAPI{s.services}, 457 }} 458 } 459 460 func (s *snapshotService) Protocols() []p2p.Protocol { 461 return nil 462 } 463 464 func (s *snapshotService) Start(*p2p.Server) error { 465 return nil 466 } 467 468 func (s *snapshotService) Stop() error { 469 return nil 470 } 471 472 // SnapshotAPI provides an RPC method to create snapshots of services 473 type SnapshotAPI struct { 474 services map[string]node.Service 475 } 476 477 func (api SnapshotAPI) Snapshot() (map[string][]byte, error) { 478 snapshots := make(map[string][]byte) 479 for name, service := range api.services { 480 if s, ok := service.(interface { 481 Snapshot() ([]byte, error) 482 }); ok { 483 snap, err := s.Snapshot() 484 if err != nil { 485 return nil, err 486 } 487 snapshots[name] = snap 488 } 489 } 490 return snapshots, nil 491 } 492 493 type wsRPCDialer struct { 494 addrs map[string]string 495 } 496 497 // DialRPC implements the RPCDialer interface by creating a WebSocket RPC 498 // client of the given node 499 func (w *wsRPCDialer) DialRPC(id discover.NodeID) (*rpc.Client, error) { 500 addr, ok := w.addrs[id.String()] 501 if !ok { 502 return nil, fmt.Errorf("unknown node: %s", id) 503 } 504 return rpc.DialWebsocket(context.Background(), addr, "http://localhost") 505 }