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