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