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