github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/p2p/simulations/adapters/exec.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package adapters
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/ecdsa"
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"net"
    28  	"net/http"
    29  	"os"
    30  	"os/exec"
    31  	"os/signal"
    32  	"path/filepath"
    33  	"strings"
    34  	"sync"
    35  	"syscall"
    36  	"time"
    37  
    38  	"github.com/docker/docker/pkg/reexec"
    39  	"github.com/ethereum/go-ethereum/log"
    40  	"github.com/ethereum/go-ethereum/node"
    41  	"github.com/ethereum/go-ethereum/p2p"
    42  	"github.com/ethereum/go-ethereum/p2p/enode"
    43  	"github.com/ethereum/go-ethereum/rpc"
    44  	"golang.org/x/net/websocket"
    45  )
    46  
    47  func init() {
    48  	// Register a reexec function to start a simulation node when the current binary is
    49  	// executed as "p2p-node" (rather than whatever the main() function would normally do).
    50  	reexec.Register("p2p-node", execP2PNode)
    51  }
    52  
    53  // ExecAdapter is a NodeAdapter which runs simulation nodes by executing the current binary
    54  // as a child process.
    55  type ExecAdapter struct {
    56  	// BaseDir is the directory under which the data directories for each
    57  	// simulation node are created.
    58  	BaseDir string
    59  
    60  	nodes map[enode.ID]*ExecNode
    61  }
    62  
    63  // NewExecAdapter returns an ExecAdapter which stores node data in
    64  // subdirectories of the given base directory
    65  func NewExecAdapter(baseDir string) *ExecAdapter {
    66  	return &ExecAdapter{
    67  		BaseDir: baseDir,
    68  		nodes:   make(map[enode.ID]*ExecNode),
    69  	}
    70  }
    71  
    72  // Name returns the name of the adapter for logging purposes
    73  func (e *ExecAdapter) Name() string {
    74  	return "exec-adapter"
    75  }
    76  
    77  // NewNode returns a new ExecNode using the given config
    78  func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
    79  	if len(config.Services) == 0 {
    80  		return nil, errors.New("node must have at least one service")
    81  	}
    82  	for _, service := range config.Services {
    83  		if _, exists := serviceFuncs[service]; !exists {
    84  			return nil, fmt.Errorf("unknown node service %q", service)
    85  		}
    86  	}
    87  
    88  	// create the node directory using the first 12 characters of the ID
    89  	// as Unix socket paths cannot be longer than 256 characters
    90  	dir := filepath.Join(e.BaseDir, config.ID.String()[:12])
    91  	if err := os.Mkdir(dir, 0755); err != nil {
    92  		return nil, fmt.Errorf("error creating node directory: %s", err)
    93  	}
    94  
    95  	// generate the config
    96  	conf := &execNodeConfig{
    97  		Stack: node.DefaultConfig,
    98  		Node:  config,
    99  	}
   100  	if config.DataDir != "" {
   101  		conf.Stack.DataDir = config.DataDir
   102  	} else {
   103  		conf.Stack.DataDir = filepath.Join(dir, "data")
   104  	}
   105  	conf.Stack.WSHost = "127.0.0.1"
   106  	conf.Stack.WSPort = 0
   107  	conf.Stack.WSOrigins = []string{"*"}
   108  	conf.Stack.WSExposeAll = true
   109  	conf.Stack.P2P.EnableMsgEvents = false
   110  	conf.Stack.P2P.NoDiscovery = true
   111  	conf.Stack.P2P.NAT = nil
   112  	conf.Stack.NoUSB = true
   113  
   114  	// listen on a localhost port, which we set when we
   115  	// initialise NodeConfig (usually a random port)
   116  	conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port)
   117  
   118  	node := &ExecNode{
   119  		ID:      config.ID,
   120  		Dir:     dir,
   121  		Config:  conf,
   122  		adapter: e,
   123  	}
   124  	node.newCmd = node.execCommand
   125  	e.nodes[node.ID] = node
   126  	return node, nil
   127  }
   128  
   129  // ExecNode starts a simulation node by exec'ing the current binary and
   130  // running the configured services
   131  type ExecNode struct {
   132  	ID     enode.ID
   133  	Dir    string
   134  	Config *execNodeConfig
   135  	Cmd    *exec.Cmd
   136  	Info   *p2p.NodeInfo
   137  
   138  	adapter *ExecAdapter
   139  	client  *rpc.Client
   140  	wsAddr  string
   141  	newCmd  func() *exec.Cmd
   142  	key     *ecdsa.PrivateKey
   143  }
   144  
   145  // Addr returns the node's enode URL
   146  func (n *ExecNode) Addr() []byte {
   147  	if n.Info == nil {
   148  		return nil
   149  	}
   150  	return []byte(n.Info.Enode)
   151  }
   152  
   153  // Client returns an rpc.Client which can be used to communicate with the
   154  // underlying services (it is set once the node has started)
   155  func (n *ExecNode) Client() (*rpc.Client, error) {
   156  	return n.client, nil
   157  }
   158  
   159  // Start exec's the node passing the ID and service as command line arguments
   160  // and the node config encoded as JSON in an environment variable.
   161  func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
   162  	if n.Cmd != nil {
   163  		return errors.New("already started")
   164  	}
   165  	defer func() {
   166  		if err != nil {
   167  			n.Stop()
   168  		}
   169  	}()
   170  
   171  	// encode a copy of the config containing the snapshot
   172  	confCopy := *n.Config
   173  	confCopy.Snapshots = snapshots
   174  	confCopy.PeerAddrs = make(map[string]string)
   175  	for id, node := range n.adapter.nodes {
   176  		confCopy.PeerAddrs[id.String()] = node.wsAddr
   177  	}
   178  	confData, err := json.Marshal(confCopy)
   179  	if err != nil {
   180  		return fmt.Errorf("error generating node config: %s", err)
   181  	}
   182  
   183  	// start the one-shot server that waits for startup information
   184  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   185  	defer cancel()
   186  	statusURL, statusC := n.waitForStartupJSON(ctx)
   187  
   188  	// start the node
   189  	cmd := n.newCmd()
   190  	cmd.Stdout = os.Stdout
   191  	cmd.Stderr = os.Stderr
   192  	cmd.Env = append(os.Environ(),
   193  		envStatusURL+"="+statusURL,
   194  		envNodeConfig+"="+string(confData),
   195  	)
   196  	if err := cmd.Start(); err != nil {
   197  		return fmt.Errorf("error starting node: %s", err)
   198  	}
   199  	n.Cmd = cmd
   200  
   201  	// read the WebSocket address from the stderr logs
   202  	status := <-statusC
   203  	if status.Err != "" {
   204  		return errors.New(status.Err)
   205  	}
   206  	client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "http://localhost")
   207  	if err != nil {
   208  		return fmt.Errorf("can't connect to RPC server: %v", err)
   209  	}
   210  
   211  	// node ready :)
   212  	n.client = client
   213  	n.wsAddr = status.WSEndpoint
   214  	n.Info = status.NodeInfo
   215  	return nil
   216  }
   217  
   218  // waitForStartupJSON runs a one-shot HTTP server to receive a startup report.
   219  func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeStartupJSON) {
   220  	var (
   221  		ch       = make(chan nodeStartupJSON, 1)
   222  		quitOnce sync.Once
   223  		srv      http.Server
   224  	)
   225  	l, err := net.Listen("tcp", "127.0.0.1:0")
   226  	if err != nil {
   227  		ch <- nodeStartupJSON{Err: err.Error()}
   228  		return "", ch
   229  	}
   230  	quit := func(status nodeStartupJSON) {
   231  		quitOnce.Do(func() {
   232  			l.Close()
   233  			ch <- status
   234  		})
   235  	}
   236  	srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   237  		var status nodeStartupJSON
   238  		if err := json.NewDecoder(r.Body).Decode(&status); err != nil {
   239  			status.Err = fmt.Sprintf("can't decode startup report: %v", err)
   240  		}
   241  		quit(status)
   242  	})
   243  	// Run the HTTP server, but don't wait forever and shut it down
   244  	// if the context is canceled.
   245  	go srv.Serve(l)
   246  	go func() {
   247  		<-ctx.Done()
   248  		quit(nodeStartupJSON{Err: "didn't get startup report"})
   249  	}()
   250  
   251  	url := "http://" + l.Addr().String()
   252  	return url, ch
   253  }
   254  
   255  // execCommand returns a command which runs the node locally by exec'ing
   256  // the current binary but setting argv[0] to "p2p-node" so that the child
   257  // runs execP2PNode
   258  func (n *ExecNode) execCommand() *exec.Cmd {
   259  	return &exec.Cmd{
   260  		Path: reexec.Self(),
   261  		Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()},
   262  	}
   263  }
   264  
   265  // Stop stops the node by first sending SIGTERM and then SIGKILL if the node
   266  // doesn't stop within 5s
   267  func (n *ExecNode) Stop() error {
   268  	if n.Cmd == nil {
   269  		return nil
   270  	}
   271  	defer func() {
   272  		n.Cmd = nil
   273  	}()
   274  
   275  	if n.client != nil {
   276  		n.client.Close()
   277  		n.client = nil
   278  		n.wsAddr = ""
   279  		n.Info = nil
   280  	}
   281  
   282  	if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
   283  		return n.Cmd.Process.Kill()
   284  	}
   285  	waitErr := make(chan error)
   286  	go func() {
   287  		waitErr <- n.Cmd.Wait()
   288  	}()
   289  	select {
   290  	case err := <-waitErr:
   291  		return err
   292  	case <-time.After(5 * time.Second):
   293  		return n.Cmd.Process.Kill()
   294  	}
   295  }
   296  
   297  // NodeInfo returns information about the node
   298  func (n *ExecNode) NodeInfo() *p2p.NodeInfo {
   299  	info := &p2p.NodeInfo{
   300  		ID: n.ID.String(),
   301  	}
   302  	if n.client != nil {
   303  		n.client.Call(&info, "admin_nodeInfo")
   304  	}
   305  	return info
   306  }
   307  
   308  // ServeRPC serves RPC requests over the given connection by dialling the
   309  // node's WebSocket address and joining the two connections
   310  func (n *ExecNode) ServeRPC(clientConn net.Conn) error {
   311  	conn, err := websocket.Dial(n.wsAddr, "", "http://localhost")
   312  	if err != nil {
   313  		return err
   314  	}
   315  	var wg sync.WaitGroup
   316  	wg.Add(2)
   317  	join := func(src, dst net.Conn) {
   318  		defer wg.Done()
   319  		io.Copy(dst, src)
   320  		// close the write end of the destination connection
   321  		if cw, ok := dst.(interface {
   322  			CloseWrite() error
   323  		}); ok {
   324  			cw.CloseWrite()
   325  		} else {
   326  			dst.Close()
   327  		}
   328  	}
   329  	go join(conn, clientConn)
   330  	go join(clientConn, conn)
   331  	wg.Wait()
   332  	return nil
   333  }
   334  
   335  // Snapshots creates snapshots of the services by calling the
   336  // simulation_snapshot RPC method
   337  func (n *ExecNode) Snapshots() (map[string][]byte, error) {
   338  	if n.client == nil {
   339  		return nil, errors.New("RPC not started")
   340  	}
   341  	var snapshots map[string][]byte
   342  	return snapshots, n.client.Call(&snapshots, "simulation_snapshot")
   343  }
   344  
   345  // execNodeConfig is used to serialize the node configuration so it can be
   346  // passed to the child process as a JSON encoded environment variable
   347  type execNodeConfig struct {
   348  	Stack     node.Config       `json:"stack"`
   349  	Node      *NodeConfig       `json:"node"`
   350  	Snapshots map[string][]byte `json:"snapshots,omitempty"`
   351  	PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
   352  }
   353  
   354  // execP2PNode starts a simulation node when the current binary is executed with
   355  // argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
   356  // and the node config from an environment variable.
   357  func execP2PNode() {
   358  	glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
   359  	glogger.Verbosity(log.LvlInfo)
   360  	log.Root().SetHandler(glogger)
   361  	statusURL := os.Getenv(envStatusURL)
   362  	if statusURL == "" {
   363  		log.Crit("missing " + envStatusURL)
   364  	}
   365  
   366  	// Start the node and gather startup report.
   367  	var status nodeStartupJSON
   368  	stack, stackErr := startExecNodeStack()
   369  	if stackErr != nil {
   370  		status.Err = stackErr.Error()
   371  	} else {
   372  		status.WSEndpoint = "ws://" + stack.WSEndpoint()
   373  		status.NodeInfo = stack.Server().NodeInfo()
   374  	}
   375  
   376  	// Send status to the host.
   377  	statusJSON, _ := json.Marshal(status)
   378  	if _, err := http.Post(statusURL, "application/json", bytes.NewReader(statusJSON)); err != nil {
   379  		log.Crit("Can't post startup info", "url", statusURL, "err", err)
   380  	}
   381  	if stackErr != nil {
   382  		os.Exit(1)
   383  	}
   384  
   385  	// Stop the stack if we get a SIGTERM signal.
   386  	go func() {
   387  		sigc := make(chan os.Signal, 1)
   388  		signal.Notify(sigc, syscall.SIGTERM)
   389  		defer signal.Stop(sigc)
   390  		<-sigc
   391  		log.Info("Received SIGTERM, shutting down...")
   392  		stack.Stop()
   393  	}()
   394  	stack.Wait() // Wait for the stack to exit.
   395  }
   396  
   397  func startExecNodeStack() (*node.Node, error) {
   398  	// read the services from argv
   399  	serviceNames := strings.Split(os.Args[1], ",")
   400  
   401  	// decode the config
   402  	confEnv := os.Getenv(envNodeConfig)
   403  	if confEnv == "" {
   404  		return nil, fmt.Errorf("missing " + envNodeConfig)
   405  	}
   406  	var conf execNodeConfig
   407  	if err := json.Unmarshal([]byte(confEnv), &conf); err != nil {
   408  		return nil, fmt.Errorf("error decoding %s: %v", envNodeConfig, err)
   409  	}
   410  	conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey
   411  	conf.Stack.Logger = log.New("node.id", conf.Node.ID.String())
   412  
   413  	// initialize the devp2p stack
   414  	stack, err := node.New(&conf.Stack)
   415  	if err != nil {
   416  		return nil, fmt.Errorf("error creating node stack: %v", err)
   417  	}
   418  
   419  	// register the services, collecting them into a map so we can wrap
   420  	// them in a snapshot service
   421  	services := make(map[string]node.Service, len(serviceNames))
   422  	for _, name := range serviceNames {
   423  		serviceFunc, exists := serviceFuncs[name]
   424  		if !exists {
   425  			return nil, fmt.Errorf("unknown node service %q", err)
   426  		}
   427  		constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) {
   428  			ctx := &ServiceContext{
   429  				RPCDialer:   &wsRPCDialer{addrs: conf.PeerAddrs},
   430  				NodeContext: nodeCtx,
   431  				Config:      conf.Node,
   432  			}
   433  			if conf.Snapshots != nil {
   434  				ctx.Snapshot = conf.Snapshots[name]
   435  			}
   436  			service, err := serviceFunc(ctx)
   437  			if err != nil {
   438  				return nil, err
   439  			}
   440  			services[name] = service
   441  			return service, nil
   442  		}
   443  		if err := stack.Register(constructor); err != nil {
   444  			return stack, fmt.Errorf("error registering service %q: %v", name, err)
   445  		}
   446  	}
   447  
   448  	// register the snapshot service
   449  	err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
   450  		return &snapshotService{services}, nil
   451  	})
   452  	if err != nil {
   453  		return stack, fmt.Errorf("error starting snapshot service: %v", err)
   454  	}
   455  
   456  	// start the stack
   457  	if err = stack.Start(); err != nil {
   458  		err = fmt.Errorf("error starting stack: %v", err)
   459  	}
   460  	return stack, err
   461  }
   462  
   463  const (
   464  	envStatusURL  = "_P2P_STATUS_URL"
   465  	envNodeConfig = "_P2P_NODE_CONFIG"
   466  )
   467  
   468  // nodeStartupJSON is sent to the simulation host after startup.
   469  type nodeStartupJSON struct {
   470  	Err        string
   471  	WSEndpoint string
   472  	NodeInfo   *p2p.NodeInfo
   473  }
   474  
   475  // snapshotService is a node.Service which wraps a list of services and
   476  // exposes an API to generate a snapshot of those services
   477  type snapshotService struct {
   478  	services map[string]node.Service
   479  }
   480  
   481  func (s *snapshotService) APIs() []rpc.API {
   482  	return []rpc.API{{
   483  		Namespace: "simulation",
   484  		Version:   "1.0",
   485  		Service:   SnapshotAPI{s.services},
   486  	}}
   487  }
   488  
   489  func (s *snapshotService) Protocols() []p2p.Protocol {
   490  	return nil
   491  }
   492  
   493  func (s *snapshotService) Start(*p2p.Server) error {
   494  	return nil
   495  }
   496  
   497  func (s *snapshotService) Stop() error {
   498  	return nil
   499  }
   500  
   501  // SnapshotAPI provides an RPC method to create snapshots of services
   502  type SnapshotAPI struct {
   503  	services map[string]node.Service
   504  }
   505  
   506  func (api SnapshotAPI) Snapshot() (map[string][]byte, error) {
   507  	snapshots := make(map[string][]byte)
   508  	for name, service := range api.services {
   509  		if s, ok := service.(interface {
   510  			Snapshot() ([]byte, error)
   511  		}); ok {
   512  			snap, err := s.Snapshot()
   513  			if err != nil {
   514  				return nil, err
   515  			}
   516  			snapshots[name] = snap
   517  		}
   518  	}
   519  	return snapshots, nil
   520  }
   521  
   522  type wsRPCDialer struct {
   523  	addrs map[string]string
   524  }
   525  
   526  // DialRPC implements the RPCDialer interface by creating a WebSocket RPC
   527  // client of the given node
   528  func (w *wsRPCDialer) DialRPC(id enode.ID) (*rpc.Client, error) {
   529  	addr, ok := w.addrs[id.String()]
   530  	if !ok {
   531  		return nil, fmt.Errorf("unknown node: %s", id)
   532  	}
   533  	return rpc.DialWebsocket(context.Background(), addr, "http://localhost")
   534  }