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