github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/p2p/simulations/adapters/exec.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 12:09:44</date> 10 //</624342660601090048> 11 12 13 package adapters 14 15 import ( 16 "bufio" 17 "context" 18 "crypto/ecdsa" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "io" 23 "net" 24 "os" 25 "os/exec" 26 "os/signal" 27 "path/filepath" 28 "strings" 29 "sync" 30 "syscall" 31 "time" 32 33 "github.com/docker/docker/pkg/reexec" 34 "github.com/ethereum/go-ethereum/log" 35 "github.com/ethereum/go-ethereum/node" 36 "github.com/ethereum/go-ethereum/p2p" 37 "github.com/ethereum/go-ethereum/p2p/discover" 38 "github.com/ethereum/go-ethereum/rpc" 39 "golang.org/x/net/websocket" 40 ) 41 42 //Execadapter是一个节点适配器,通过执行 43 //当前二进制文件作为子进程。 44 // 45 //使用init钩子以便子进程执行节点服务 46 //(而不是main()函数通常执行的操作),请参见 47 //有关详细信息,请参阅execp2pnode函数。 48 type ExecAdapter struct { 49 //basedir是每个目录下的数据目录 50 //创建模拟节点。 51 BaseDir string 52 53 nodes map[discover.NodeID]*ExecNode 54 } 55 56 //newexecadapter返回一个execadapter,该execadapter将节点数据存储在 57 //给定基目录的子目录 58 func NewExecAdapter(baseDir string) *ExecAdapter { 59 return &ExecAdapter{ 60 BaseDir: baseDir, 61 nodes: make(map[discover.NodeID]*ExecNode), 62 } 63 } 64 65 //name返回用于日志记录的适配器的名称 66 func (e *ExecAdapter) Name() string { 67 return "exec-adapter" 68 } 69 70 //newnode使用给定的配置返回新的execnode 71 func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) { 72 if len(config.Services) == 0 { 73 return nil, errors.New("node must have at least one service") 74 } 75 for _, service := range config.Services { 76 if _, exists := serviceFuncs[service]; !exists { 77 return nil, fmt.Errorf("unknown node service %q", service) 78 } 79 } 80 81 //使用ID的前12个字符创建节点目录 82 //因为unix套接字路径不能超过256个字符 83 dir := filepath.Join(e.BaseDir, config.ID.String()[:12]) 84 if err := os.Mkdir(dir, 0755); err != nil { 85 return nil, fmt.Errorf("error creating node directory: %s", err) 86 } 87 88 //生成配置 89 conf := &execNodeConfig{ 90 Stack: node.DefaultConfig, 91 Node: config, 92 } 93 conf.Stack.DataDir = filepath.Join(dir, "data") 94 conf.Stack.WSHost = "127.0.0.1" 95 conf.Stack.WSPort = 0 96 conf.Stack.WSOrigins = []string{"*"} 97 conf.Stack.WSExposeAll = true 98 conf.Stack.P2P.EnableMsgEvents = false 99 conf.Stack.P2P.NoDiscovery = true 100 conf.Stack.P2P.NAT = nil 101 conf.Stack.NoUSB = true 102 103 //监听本地主机端口,当我们 104 //初始化nodeconfig(通常是随机端口) 105 conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port) 106 107 node := &ExecNode{ 108 ID: config.ID, 109 Dir: dir, 110 Config: conf, 111 adapter: e, 112 } 113 node.newCmd = node.execCommand 114 e.nodes[node.ID] = node 115 return node, nil 116 } 117 118 //exec node通过执行当前二进制文件和 119 //运行配置的服务 120 type ExecNode struct { 121 ID discover.NodeID 122 Dir string 123 Config *execNodeConfig 124 Cmd *exec.Cmd 125 Info *p2p.NodeInfo 126 127 adapter *ExecAdapter 128 client *rpc.Client 129 wsAddr string 130 newCmd func() *exec.Cmd 131 key *ecdsa.PrivateKey 132 } 133 134 //addr返回节点的enode url 135 func (n *ExecNode) Addr() []byte { 136 if n.Info == nil { 137 return nil 138 } 139 return []byte(n.Info.Enode) 140 } 141 142 //客户端返回一个rpc.client,可用于与 143 //基础服务(节点启动后设置) 144 func (n *ExecNode) Client() (*rpc.Client, error) { 145 return n.client, nil 146 } 147 148 //start exec是将ID和服务作为命令行参数传递的节点 149 //节点配置在节点配置环境中编码为json 150 //变量 151 func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { 152 if n.Cmd != nil { 153 return errors.New("already started") 154 } 155 defer func() { 156 if err != nil { 157 log.Error("node failed to start", "err", err) 158 n.Stop() 159 } 160 }() 161 162 //对包含快照的配置副本进行编码 163 confCopy := *n.Config 164 confCopy.Snapshots = snapshots 165 confCopy.PeerAddrs = make(map[string]string) 166 for id, node := range n.adapter.nodes { 167 confCopy.PeerAddrs[id.String()] = node.wsAddr 168 } 169 confData, err := json.Marshal(confCopy) 170 if err != nil { 171 return fmt.Errorf("error generating node config: %s", err) 172 } 173 174 //为stderr使用管道,这样我们都可以将节点的stderr复制到 175 //os.stderr并从日志中读取websocket地址 176 stderrR, stderrW := io.Pipe() 177 stderr := io.MultiWriter(os.Stderr, stderrW) 178 179 //启动节点 180 cmd := n.newCmd() 181 cmd.Stdout = os.Stdout 182 cmd.Stderr = stderr 183 cmd.Env = append(os.Environ(), fmt.Sprintf("_P2P_NODE_CONFIG=%s", confData)) 184 if err := cmd.Start(); err != nil { 185 return fmt.Errorf("error starting node: %s", err) 186 } 187 n.Cmd = cmd 188 189 //从stderr日志中读取websocket地址 190 var wsAddr string 191 wsAddrC := make(chan string) 192 go func() { 193 s := bufio.NewScanner(stderrR) 194 for s.Scan() { 195 if strings.Contains(s.Text(), "WebSocket endpoint opened") { 196 wsAddrC <- wsAddrPattern.FindString(s.Text()) 197 } 198 } 199 }() 200 select { 201 case wsAddr = <-wsAddrC: 202 if wsAddr == "" { 203 return errors.New("failed to read WebSocket address from stderr") 204 } 205 case <-time.After(10 * time.Second): 206 return errors.New("timed out waiting for WebSocket address on stderr") 207 } 208 209 //创建RPC客户端并加载节点信息 210 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 211 defer cancel() 212 client, err := rpc.DialWebsocket(ctx, wsAddr, "") 213 if err != nil { 214 return fmt.Errorf("error dialing rpc websocket: %s", err) 215 } 216 var info p2p.NodeInfo 217 if err := client.CallContext(ctx, &info, "admin_nodeInfo"); err != nil { 218 return fmt.Errorf("error getting node info: %s", err) 219 } 220 n.client = client 221 n.wsAddr = wsAddr 222 n.Info = &info 223 224 return nil 225 } 226 227 //exec command返回一个命令,该命令通过exeng在本地运行节点 228 //当前二进制文件,但将argv[0]设置为“p2p node”,以便子级 229 //运行execp2pnode 230 func (n *ExecNode) execCommand() *exec.Cmd { 231 return &exec.Cmd{ 232 Path: reexec.Self(), 233 Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()}, 234 } 235 } 236 237 //stop首先发送sigterm,然后在节点 238 //在5秒内没有停止 239 func (n *ExecNode) Stop() error { 240 if n.Cmd == nil { 241 return nil 242 } 243 defer func() { 244 n.Cmd = nil 245 }() 246 247 if n.client != nil { 248 n.client.Close() 249 n.client = nil 250 n.wsAddr = "" 251 n.Info = nil 252 } 253 254 if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil { 255 return n.Cmd.Process.Kill() 256 } 257 waitErr := make(chan error) 258 go func() { 259 waitErr <- n.Cmd.Wait() 260 }() 261 select { 262 case err := <-waitErr: 263 return err 264 case <-time.After(5 * time.Second): 265 return n.Cmd.Process.Kill() 266 } 267 } 268 269 //nodeinfo返回有关节点的信息 270 func (n *ExecNode) NodeInfo() *p2p.NodeInfo { 271 info := &p2p.NodeInfo{ 272 ID: n.ID.String(), 273 } 274 if n.client != nil { 275 n.client.Call(&info, "admin_nodeInfo") 276 } 277 return info 278 } 279 280 //serverpc通过拨 281 //节点的WebSocket地址和连接两个连接 282 func (n *ExecNode) ServeRPC(clientConn net.Conn) error { 283 conn, err := websocket.Dial(n.wsAddr, "", "http://“本地主机” 284 if err != nil { 285 return err 286 } 287 var wg sync.WaitGroup 288 wg.Add(2) 289 join := func(src, dst net.Conn) { 290 defer wg.Done() 291 io.Copy(dst, src) 292 //关闭目标连接的写入端 293 if cw, ok := dst.(interface { 294 CloseWrite() error 295 }); ok { 296 cw.CloseWrite() 297 } else { 298 dst.Close() 299 } 300 } 301 go join(conn, clientConn) 302 go join(clientConn, conn) 303 wg.Wait() 304 return nil 305 } 306 307 //快照通过调用 308 //模拟快照RPC方法 309 func (n *ExecNode) Snapshots() (map[string][]byte, error) { 310 if n.client == nil { 311 return nil, errors.New("RPC not started") 312 } 313 var snapshots map[string][]byte 314 return snapshots, n.client.Call(&snapshots, "simulation_snapshot") 315 } 316 317 func init() { 318 //注册reexec函数以在当前 319 //二进制作为“p2p节点”执行 320 reexec.Register("p2p-node", execP2PNode) 321 } 322 323 //ExecOnDeconfig用于序列化节点配置,以便 324 //作为JSON编码的环境变量传递给子进程 325 type execNodeConfig struct { 326 Stack node.Config `json:"stack"` 327 Node *NodeConfig `json:"node"` 328 Snapshots map[string][]byte `json:"snapshots,omitempty"` 329 PeerAddrs map[string]string `json:"peer_addrs,omitempty"` 330 } 331 332 //ExternalIP获取外部IP地址,以便enode url可用 333 func ExternalIP() net.IP { 334 addrs, err := net.InterfaceAddrs() 335 if err != nil { 336 log.Crit("error getting IP address", "err", err) 337 } 338 for _, addr := range addrs { 339 if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() { 340 return ip.IP 341 } 342 } 343 log.Warn("unable to determine explicit IP address, falling back to loopback") 344 return net.IP{127, 0, 0, 1} 345 } 346 347 //执行当前二进制文件时,execp2pnode启动devp2p节点 348 //argv[0]为“p2p节点”,从argv[1]/argv[2]读取服务/id 349 //以及_p2p_node_config环境变量中的节点配置 350 func execP2PNode() { 351 glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat())) 352 glogger.Verbosity(log.LvlInfo) 353 log.Root().SetHandler(glogger) 354 355 //从argv读取服务 356 serviceNames := strings.Split(os.Args[1], ",") 357 358 //解码配置 359 confEnv := os.Getenv("_P2P_NODE_CONFIG") 360 if confEnv == "" { 361 log.Crit("missing _P2P_NODE_CONFIG") 362 } 363 var conf execNodeConfig 364 if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { 365 log.Crit("error decoding _P2P_NODE_CONFIG", "err", err) 366 } 367 conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey 368 conf.Stack.Logger = log.New("node.id", conf.Node.ID.String()) 369 370 if strings.HasPrefix(conf.Stack.P2P.ListenAddr, ":") { 371 conf.Stack.P2P.ListenAddr = ExternalIP().String() + conf.Stack.P2P.ListenAddr 372 } 373 if conf.Stack.WSHost == "0.0.0.0" { 374 conf.Stack.WSHost = ExternalIP().String() 375 } 376 377 //初始化devp2p堆栈 378 stack, err := node.New(&conf.Stack) 379 if err != nil { 380 log.Crit("error creating node stack", "err", err) 381 } 382 383 //注册服务,将它们收集到地图中,以便我们可以包装 384 //它们在快照服务中 385 services := make(map[string]node.Service, len(serviceNames)) 386 for _, name := range serviceNames { 387 serviceFunc, exists := serviceFuncs[name] 388 if !exists { 389 log.Crit("unknown node service", "name", name) 390 } 391 constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) { 392 ctx := &ServiceContext{ 393 RPCDialer: &wsRPCDialer{addrs: conf.PeerAddrs}, 394 NodeContext: nodeCtx, 395 Config: conf.Node, 396 } 397 if conf.Snapshots != nil { 398 ctx.Snapshot = conf.Snapshots[name] 399 } 400 service, err := serviceFunc(ctx) 401 if err != nil { 402 return nil, err 403 } 404 services[name] = service 405 return service, nil 406 } 407 if err := stack.Register(constructor); err != nil { 408 log.Crit("error starting service", "name", name, "err", err) 409 } 410 } 411 412 //注册快照服务 413 if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 414 return &snapshotService{services}, nil 415 }); err != nil { 416 log.Crit("error starting snapshot service", "err", err) 417 } 418 419 //启动堆栈 420 if err := stack.Start(); err != nil { 421 log.Crit("error stating node stack", "err", err) 422 } 423 424 //如果我们得到一个sigterm信号,就停止堆栈 425 go func() { 426 sigc := make(chan os.Signal, 1) 427 signal.Notify(sigc, syscall.SIGTERM) 428 defer signal.Stop(sigc) 429 <-sigc 430 log.Info("Received SIGTERM, shutting down...") 431 stack.Stop() 432 }() 433 434 //等待堆栈退出 435 stack.Wait() 436 } 437 438 //SnapshotService是一个node.service,它包装了服务列表和 439 //公开API以生成这些服务的快照 440 type snapshotService struct { 441 services map[string]node.Service 442 } 443 444 func (s *snapshotService) APIs() []rpc.API { 445 return []rpc.API{{ 446 Namespace: "simulation", 447 Version: "1.0", 448 Service: SnapshotAPI{s.services}, 449 }} 450 } 451 452 func (s *snapshotService) Protocols() []p2p.Protocol { 453 return nil 454 } 455 456 func (s *snapshotService) Start(*p2p.Server) error { 457 return nil 458 } 459 460 func (s *snapshotService) Stop() error { 461 return nil 462 } 463 464 //Snapshotapi提供了一个RPC方法来创建服务的快照 465 type SnapshotAPI struct { 466 services map[string]node.Service 467 } 468 469 func (api SnapshotAPI) Snapshot() (map[string][]byte, error) { 470 snapshots := make(map[string][]byte) 471 for name, service := range api.services { 472 if s, ok := service.(interface { 473 Snapshot() ([]byte, error) 474 }); ok { 475 snap, err := s.Snapshot() 476 if err != nil { 477 return nil, err 478 } 479 snapshots[name] = snap 480 } 481 } 482 return snapshots, nil 483 } 484 485 type wsRPCDialer struct { 486 addrs map[string]string 487 } 488 489 //DialRPC通过创建WebSocket RPC来实现RpcDialer接口 490 //给定节点的客户端 491 func (w *wsRPCDialer) DialRPC(id discover.NodeID) (*rpc.Client, error) { 492 addr, ok := w.addrs[id.String()] 493 if !ok { 494 return nil, fmt.Errorf("unknown node: %s", id) 495 } 496 return rpc.DialWebsocket(context.Background(), addr, "http://“本地主机” 497 } 498