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