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