github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/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 19:16:41</date> 10 //</624450106535448576> 11 12 13 package adapters 14 15 import ( 16 "bytes" 17 "context" 18 "crypto/ecdsa" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "io" 23 "net" 24 "net/http" 25 "os" 26 "os/exec" 27 "os/signal" 28 "path/filepath" 29 "strings" 30 "sync" 31 "syscall" 32 "time" 33 34 "github.com/docker/docker/pkg/reexec" 35 "github.com/ethereum/go-ethereum/log" 36 "github.com/ethereum/go-ethereum/node" 37 "github.com/ethereum/go-ethereum/p2p" 38 "github.com/ethereum/go-ethereum/p2p/enode" 39 "github.com/ethereum/go-ethereum/rpc" 40 "golang.org/x/net/websocket" 41 ) 42 43 func init() { 44 //当当前二进制文件为 45 //作为“p2p节点”执行(而不是主()函数通常执行的任何操作)。 46 reexec.Register("p2p-node", execP2PNode) 47 } 48 49 //execadapter是一个节点适配器,通过执行当前二进制文件来运行模拟节点。 50 //作为一个孩子的过程。 51 type ExecAdapter struct { 52 //basedir是每个目录下的数据目录 53 //创建模拟节点。 54 BaseDir string 55 56 nodes map[enode.ID]*ExecNode 57 } 58 59 //newexecadapter返回一个execadapter,该execadapter将节点数据存储在 60 //给定基目录的子目录 61 func NewExecAdapter(baseDir string) *ExecAdapter { 62 return &ExecAdapter{ 63 BaseDir: baseDir, 64 nodes: make(map[enode.ID]*ExecNode), 65 } 66 } 67 68 //name返回用于日志记录的适配器的名称 69 func (e *ExecAdapter) Name() string { 70 return "exec-adapter" 71 } 72 73 //newnode使用给定的配置返回新的execnode 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 //使用ID的前12个字符创建节点目录 85 //因为unix套接字路径不能超过256个字符 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 //生成配置 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 //监听本地主机端口,当我们 107 //初始化nodeconfig(通常是随机端口) 108 conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port) 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 //exec node通过执行当前二进制文件和 122 //运行配置的服务 123 type ExecNode struct { 124 ID enode.ID 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返回节点的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 //客户端返回一个rpc.client,可用于与 146 //基础服务(节点启动后设置) 147 func (n *ExecNode) Client() (*rpc.Client, error) { 148 return n.client, nil 149 } 150 151 //start exec是将ID和服务作为命令行参数传递的节点 152 //节点配置在环境变量中编码为JSON。 153 func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { 154 if n.Cmd != nil { 155 return errors.New("already started") 156 } 157 defer func() { 158 if err != nil { 159 n.Stop() 160 } 161 }() 162 163 //对包含快照的配置副本进行编码 164 confCopy := *n.Config 165 confCopy.Snapshots = snapshots 166 confCopy.PeerAddrs = make(map[string]string) 167 for id, node := range n.adapter.nodes { 168 confCopy.PeerAddrs[id.String()] = node.wsAddr 169 } 170 confData, err := json.Marshal(confCopy) 171 if err != nil { 172 return fmt.Errorf("error generating node config: %s", err) 173 } 174 175 //启动等待启动信息的一次性服务器 176 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 177 defer cancel() 178 statusURL, statusC := n.waitForStartupJSON(ctx) 179 180 //启动节点 181 cmd := n.newCmd() 182 cmd.Stdout = os.Stdout 183 cmd.Stderr = os.Stderr 184 cmd.Env = append(os.Environ(), 185 envStatusURL+"="+statusURL, 186 envNodeConfig+"="+string(confData), 187 ) 188 if err := cmd.Start(); err != nil { 189 return fmt.Errorf("error starting node: %s", err) 190 } 191 n.Cmd = cmd 192 193 //从stderr日志中读取websocket地址 194 status := <-statusC 195 if status.Err != "" { 196 return errors.New(status.Err) 197 } 198 client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "http://“本地主机” 199 if err != nil { 200 return fmt.Errorf("can't connect to RPC server: %v", err) 201 } 202 203 //节点准备好: 204 n.client = client 205 n.wsAddr = status.WSEndpoint 206 n.Info = status.NodeInfo 207 return nil 208 } 209 210 //WaitForStartupJSON运行一个一次性HTTP服务器来接收启动报告。 211 func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeStartupJSON) { 212 var ( 213 ch = make(chan nodeStartupJSON, 1) 214 quitOnce sync.Once 215 srv http.Server 216 ) 217 l, err := net.Listen("tcp", "127.0.0.1:0") 218 if err != nil { 219 ch <- nodeStartupJSON{Err: err.Error()} 220 return "", ch 221 } 222 quit := func(status nodeStartupJSON) { 223 quitOnce.Do(func() { 224 l.Close() 225 ch <- status 226 }) 227 } 228 srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 229 var status nodeStartupJSON 230 if err := json.NewDecoder(r.Body).Decode(&status); err != nil { 231 status.Err = fmt.Sprintf("can't decode startup report: %v", err) 232 } 233 quit(status) 234 }) 235 //运行HTTP服务器,但不要一直等待并关闭它 236 //如果上下文被取消。 237 go srv.Serve(l) 238 go func() { 239 <-ctx.Done() 240 quit(nodeStartupJSON{Err: "didn't get startup report"}) 241 }() 242 243 url := "http://“+l.addr().string()” 244 return url, ch 245 } 246 247 //exec command返回一个命令,该命令通过exeng在本地运行节点 248 //当前二进制文件,但将argv[0]设置为“p2p node”,以便子级 249 //运行execp2pnode 250 func (n *ExecNode) execCommand() *exec.Cmd { 251 return &exec.Cmd{ 252 Path: reexec.Self(), 253 Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()}, 254 } 255 } 256 257 //stop首先发送sigterm,然后在节点 258 //在5秒内没有停止 259 func (n *ExecNode) Stop() error { 260 if n.Cmd == nil { 261 return nil 262 } 263 defer func() { 264 n.Cmd = nil 265 }() 266 267 if n.client != nil { 268 n.client.Close() 269 n.client = nil 270 n.wsAddr = "" 271 n.Info = nil 272 } 273 274 if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil { 275 return n.Cmd.Process.Kill() 276 } 277 waitErr := make(chan error) 278 go func() { 279 waitErr <- n.Cmd.Wait() 280 }() 281 select { 282 case err := <-waitErr: 283 return err 284 case <-time.After(5 * time.Second): 285 return n.Cmd.Process.Kill() 286 } 287 } 288 289 //nodeinfo返回有关节点的信息 290 func (n *ExecNode) NodeInfo() *p2p.NodeInfo { 291 info := &p2p.NodeInfo{ 292 ID: n.ID.String(), 293 } 294 if n.client != nil { 295 n.client.Call(&info, "admin_nodeInfo") 296 } 297 return info 298 } 299 300 //serverpc通过拨 301 //节点的WebSocket地址和连接两个连接 302 func (n *ExecNode) ServeRPC(clientConn net.Conn) error { 303 conn, err := websocket.Dial(n.wsAddr, "", "http://“本地主机” 304 if err != nil { 305 return err 306 } 307 var wg sync.WaitGroup 308 wg.Add(2) 309 join := func(src, dst net.Conn) { 310 defer wg.Done() 311 io.Copy(dst, src) 312 //关闭目标连接的写入端 313 if cw, ok := dst.(interface { 314 CloseWrite() error 315 }); ok { 316 cw.CloseWrite() 317 } else { 318 dst.Close() 319 } 320 } 321 go join(conn, clientConn) 322 go join(clientConn, conn) 323 wg.Wait() 324 return nil 325 } 326 327 //快照通过调用 328 //模拟快照RPC方法 329 func (n *ExecNode) Snapshots() (map[string][]byte, error) { 330 if n.client == nil { 331 return nil, errors.New("RPC not started") 332 } 333 var snapshots map[string][]byte 334 return snapshots, n.client.Call(&snapshots, "simulation_snapshot") 335 } 336 337 //ExecOnDeconfig用于序列化节点配置,以便 338 //作为JSON编码的环境变量传递给子进程 339 type execNodeConfig struct { 340 Stack node.Config `json:"stack"` 341 Node *NodeConfig `json:"node"` 342 Snapshots map[string][]byte `json:"snapshots,omitempty"` 343 PeerAddrs map[string]string `json:"peer_addrs,omitempty"` 344 } 345 346 //执行当前二进制文件时,execp2pnode启动模拟节点 347 //argv[0]为“p2p节点”,从argv[1]/argv[2]读取服务/id 348 //以及环境变量中的节点配置。 349 func execP2PNode() { 350 glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat())) 351 glogger.Verbosity(log.LvlInfo) 352 log.Root().SetHandler(glogger) 353 statusURL := os.Getenv(envStatusURL) 354 if statusURL == "" { 355 log.Crit("missing " + envStatusURL) 356 } 357 358 //启动节点并收集启动报告。 359 var status nodeStartupJSON 360 stack, stackErr := startExecNodeStack() 361 if stackErr != nil { 362 status.Err = stackErr.Error() 363 } else { 364 status.WSEndpoint = "ws://“+stack.wsendpoint()” 365 status.NodeInfo = stack.Server().NodeInfo() 366 } 367 368 //向主机发送状态。 369 statusJSON, _ := json.Marshal(status) 370 if _, err := http.Post(statusURL, "application/json", bytes.NewReader(statusJSON)); err != nil { 371 log.Crit("Can't post startup info", "url", statusURL, "err", err) 372 } 373 if stackErr != nil { 374 os.Exit(1) 375 } 376 377 //如果我们得到一个sigterm信号,就停止堆栈。 378 go func() { 379 sigc := make(chan os.Signal, 1) 380 signal.Notify(sigc, syscall.SIGTERM) 381 defer signal.Stop(sigc) 382 <-sigc 383 log.Info("Received SIGTERM, shutting down...") 384 stack.Stop() 385 }() 386 stack.Wait() //等待堆栈退出。 387 } 388 389 func startExecNodeStack() (*node.Node, error) { 390 //从argv读取服务 391 serviceNames := strings.Split(os.Args[1], ",") 392 393 //解码配置 394 confEnv := os.Getenv(envNodeConfig) 395 if confEnv == "" { 396 return nil, fmt.Errorf("missing " + envNodeConfig) 397 } 398 var conf execNodeConfig 399 if err := json.Unmarshal([]byte(confEnv), &conf); err != nil { 400 return nil, fmt.Errorf("error decoding %s: %v", envNodeConfig, err) 401 } 402 conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey 403 conf.Stack.Logger = log.New("node.id", conf.Node.ID.String()) 404 405 //初始化devp2p堆栈 406 stack, err := node.New(&conf.Stack) 407 if err != nil { 408 return nil, fmt.Errorf("error creating node stack: %v", err) 409 } 410 411 //注册服务,将它们收集到地图中,以便我们可以包装 412 //它们在快照服务中 413 services := make(map[string]node.Service, len(serviceNames)) 414 for _, name := range serviceNames { 415 serviceFunc, exists := serviceFuncs[name] 416 if !exists { 417 return nil, fmt.Errorf("unknown node service %q", err) 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 return stack, fmt.Errorf("error registering service %q: %v", name, err) 437 } 438 } 439 440 //注册快照服务 441 err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 442 return &snapshotService{services}, nil 443 }) 444 if err != nil { 445 return stack, fmt.Errorf("error starting snapshot service: %v", err) 446 } 447 448 //启动堆栈 449 if err = stack.Start(); err != nil { 450 err = fmt.Errorf("error starting stack: %v", err) 451 } 452 return stack, err 453 } 454 455 const ( 456 envStatusURL = "_P2P_STATUS_URL" 457 envNodeConfig = "_P2P_NODE_CONFIG" 458 ) 459 460 //启动后将nodestartupjson发送到仿真主机。 461 type nodeStartupJSON struct { 462 Err string 463 WSEndpoint string 464 NodeInfo *p2p.NodeInfo 465 } 466 467 //SnapshotService是一个node.service,它包装了服务列表和 468 //公开API以生成这些服务的快照 469 type snapshotService struct { 470 services map[string]node.Service 471 } 472 473 func (s *snapshotService) APIs() []rpc.API { 474 return []rpc.API{{ 475 Namespace: "simulation", 476 Version: "1.0", 477 Service: SnapshotAPI{s.services}, 478 }} 479 } 480 481 func (s *snapshotService) Protocols() []p2p.Protocol { 482 return nil 483 } 484 485 func (s *snapshotService) Start(*p2p.Server) error { 486 return nil 487 } 488 489 func (s *snapshotService) Stop() error { 490 return nil 491 } 492 493 //Snapshotapi提供了一个RPC方法来创建服务的快照 494 type SnapshotAPI struct { 495 services map[string]node.Service 496 } 497 498 func (api SnapshotAPI) Snapshot() (map[string][]byte, error) { 499 snapshots := make(map[string][]byte) 500 for name, service := range api.services { 501 if s, ok := service.(interface { 502 Snapshot() ([]byte, error) 503 }); ok { 504 snap, err := s.Snapshot() 505 if err != nil { 506 return nil, err 507 } 508 snapshots[name] = snap 509 } 510 } 511 return snapshots, nil 512 } 513 514 type wsRPCDialer struct { 515 addrs map[string]string 516 } 517 518 //DialRPC通过创建WebSocket RPC来实现RpcDialer接口 519 //给定节点的客户端 520 func (w *wsRPCDialer) DialRPC(id enode.ID) (*rpc.Client, error) { 521 addr, ok := w.addrs[id.String()] 522 if !ok { 523 return nil, fmt.Errorf("unknown node: %s", id) 524 } 525 return rpc.DialWebsocket(context.Background(), addr, "http://“本地主机” 526 } 527