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