github.com/aaa256/atlantis@v0.0.0-20210707112435-42ee889287a2/p2p/simulations/adapters/exec.go (about)

     1  // Copyright 2017 The go-athereum Authors
     2  // This file is part of the go-athereum library.
     3  //
     4  // The go-athereum 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-athereum 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-athereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package adapters
    18  
    19  import (
    20  	"context"
    21  	"crypto/ecdsa"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"net"
    27  	"os"
    28  	"os/exec"
    29  	"os/signal"
    30  	"path/filepath"
    31  	"strings"
    32  	"sync"
    33  	"syscall"
    34  	"time"
    35  
    36  	"github.com/docker/docker/pkg/reexec"
    37  	"github.com/athereum/go-athereum/log"
    38  	"github.com/athereum/go-athereum/node"
    39  	"github.com/athereum/go-athereum/p2p"
    40  	"github.com/athereum/go-athereum/p2p/discover"
    41  	"github.com/athereum/go-athereum/rpc"
    42  	"golang.org/x/net/websocket"
    43  )
    44  
    45  // ExecAdapter is a NodeAdapter which runs simulation nodes by executing the
    46  // current binary as a child process.
    47  //
    48  // An init hook is used so that the child process executes the node services
    49  // (rather than whataver the main() function would normally do), see the
    50  // execP2PNode function for more information.
    51  type ExecAdapter struct {
    52  	// BaseDir is the directory under which the data directories for each
    53  	// simulation node are created.
    54  	BaseDir string
    55  
    56  	nodes map[discover.NodeID]*ExecNode
    57  }
    58  
    59  // NewExecAdapter returns an ExecAdapter which stores node data in
    60  // subdirectories of the given base directory
    61  func NewExecAdapter(baseDir string) *ExecAdapter {
    62  	return &ExecAdapter{
    63  		BaseDir: baseDir,
    64  		nodes:   make(map[discover.NodeID]*ExecNode),
    65  	}
    66  }
    67  
    68  // Name returns the name of the adapter for logging purposes
    69  func (e *ExecAdapter) Name() string {
    70  	return "exec-adapter"
    71  }
    72  
    73  // NewNode returns a new ExecNode using the given config
    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  	// create the node directory using the first 12 characters of the ID
    85  	// as Unix socket paths cannot be longer than 256 characters
    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  	// generate the config
    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  	// listen on a random localhost port (we'll get the actual port after
   107  	// starting the node through the RPC admin.nodeInfo method)
   108  	conf.Stack.P2P.ListenAddr = "127.0.0.1:0"
   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  // ExecNode starts a simulation node by exec'ing the current binary and
   122  // running the configured services
   123  type ExecNode struct {
   124  	ID     discover.NodeID
   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 returns the node's 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  // Client returns an rpc.Client which can be used to communicate with the
   146  // underlying services (it is set once the node has started)
   147  func (n *ExecNode) Client() (*rpc.Client, error) {
   148  	return n.client, nil
   149  }
   150  
   151  // Start exec's the node passing the ID and service as command line arguments
   152  // and the node config encoded as JSON in the _P2P_NODE_CONFIG environment
   153  // variable
   154  func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
   155  	if n.Cmd != nil {
   156  		return errors.New("already started")
   157  	}
   158  	defer func() {
   159  		if err != nil {
   160  			log.Error("node failed to start", "err", err)
   161  			n.Stop()
   162  		}
   163  	}()
   164  
   165  	// encode a copy of the config containing the snapshot
   166  	confCopy := *n.Config
   167  	confCopy.Snapshots = snapshots
   168  	confCopy.PeerAddrs = make(map[string]string)
   169  	for id, node := range n.adapter.nodes {
   170  		confCopy.PeerAddrs[id.String()] = node.wsAddr
   171  	}
   172  	confData, err := json.Marshal(confCopy)
   173  	if err != nil {
   174  		return fmt.Errorf("error generating node config: %s", err)
   175  	}
   176  
   177  	// use a pipe for stderr so we can both copy the node's stderr to
   178  	// os.Stderr and read the WebSocket address from the logs
   179  	stderrR, stderrW := io.Pipe()
   180  	stderr := io.MultiWriter(os.Stderr, stderrW)
   181  
   182  	// start the node
   183  	cmd := n.newCmd()
   184  	cmd.Stdout = os.Stdout
   185  	cmd.Stderr = stderr
   186  	cmd.Env = append(os.Environ(), fmt.Sprintf("_P2P_NODE_CONFIG=%s", confData))
   187  	if err := cmd.Start(); err != nil {
   188  		return fmt.Errorf("error starting node: %s", err)
   189  	}
   190  	n.Cmd = cmd
   191  
   192  	// read the WebSocket address from the stderr logs
   193  	wsAddr, err := findWSAddr(stderrR, 10*time.Second)
   194  	if err != nil {
   195  		return fmt.Errorf("error getting WebSocket address: %s", err)
   196  	}
   197  
   198  	// create the RPC client and load the node info
   199  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   200  	defer cancel()
   201  	client, err := rpc.DialWebsocket(ctx, wsAddr, "")
   202  	if err != nil {
   203  		return fmt.Errorf("error dialing rpc websocket: %s", err)
   204  	}
   205  	var info p2p.NodeInfo
   206  	if err := client.CallContext(ctx, &info, "admin_nodeInfo"); err != nil {
   207  		return fmt.Errorf("error getting node info: %s", err)
   208  	}
   209  	n.client = client
   210  	n.wsAddr = wsAddr
   211  	n.Info = &info
   212  
   213  	return nil
   214  }
   215  
   216  // execCommand returns a command which runs the node locally by exec'ing
   217  // the current binary but setting argv[0] to "p2p-node" so that the child
   218  // runs execP2PNode
   219  func (n *ExecNode) execCommand() *exec.Cmd {
   220  	return &exec.Cmd{
   221  		Path: reexec.Self(),
   222  		Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()},
   223  	}
   224  }
   225  
   226  // Stop stops the node by first sending SIGTERM and then SIGKILL if the node
   227  // doesn't stop within 5s
   228  func (n *ExecNode) Stop() error {
   229  	if n.Cmd == nil {
   230  		return nil
   231  	}
   232  	defer func() {
   233  		n.Cmd = nil
   234  	}()
   235  
   236  	if n.client != nil {
   237  		n.client.Close()
   238  		n.client = nil
   239  		n.wsAddr = ""
   240  		n.Info = nil
   241  	}
   242  
   243  	if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
   244  		return n.Cmd.Process.Kill()
   245  	}
   246  	waitErr := make(chan error)
   247  	go func() {
   248  		waitErr <- n.Cmd.Wait()
   249  	}()
   250  	select {
   251  	case err := <-waitErr:
   252  		return err
   253  	case <-time.After(5 * time.Second):
   254  		return n.Cmd.Process.Kill()
   255  	}
   256  }
   257  
   258  // NodeInfo returns information about the node
   259  func (n *ExecNode) NodeInfo() *p2p.NodeInfo {
   260  	info := &p2p.NodeInfo{
   261  		ID: n.ID.String(),
   262  	}
   263  	if n.client != nil {
   264  		n.client.Call(&info, "admin_nodeInfo")
   265  	}
   266  	return info
   267  }
   268  
   269  // ServeRPC serves RPC requests over the given connection by dialling the
   270  // node's WebSocket address and joining the two connections
   271  func (n *ExecNode) ServeRPC(clientConn net.Conn) error {
   272  	conn, err := websocket.Dial(n.wsAddr, "", "http://localhost")
   273  	if err != nil {
   274  		return err
   275  	}
   276  	var wg sync.WaitGroup
   277  	wg.Add(2)
   278  	join := func(src, dst net.Conn) {
   279  		defer wg.Done()
   280  		io.Copy(dst, src)
   281  		// close the write end of the destination connection
   282  		if cw, ok := dst.(interface {
   283  			CloseWrite() error
   284  		}); ok {
   285  			cw.CloseWrite()
   286  		} else {
   287  			dst.Close()
   288  		}
   289  	}
   290  	go join(conn, clientConn)
   291  	go join(clientConn, conn)
   292  	wg.Wait()
   293  	return nil
   294  }
   295  
   296  // Snapshots creates snapshots of the services by calling the
   297  // simulation_snapshot RPC method
   298  func (n *ExecNode) Snapshots() (map[string][]byte, error) {
   299  	if n.client == nil {
   300  		return nil, errors.New("RPC not started")
   301  	}
   302  	var snapshots map[string][]byte
   303  	return snapshots, n.client.Call(&snapshots, "simulation_snapshot")
   304  }
   305  
   306  func init() {
   307  	// register a reexec function to start a devp2p node when the current
   308  	// binary is executed as "p2p-node"
   309  	reexec.Register("p2p-node", execP2PNode)
   310  }
   311  
   312  // execNodeConfig is used to serialize the node configuration so it can be
   313  // passed to the child process as a JSON encoded environment variable
   314  type execNodeConfig struct {
   315  	Stack     node.Config       `json:"stack"`
   316  	Node      *NodeConfig       `json:"node"`
   317  	Snapshots map[string][]byte `json:"snapshots,omitempty"`
   318  	PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
   319  }
   320  
   321  // execP2PNode starts a devp2p node when the current binary is executed with
   322  // argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
   323  // and the node config from the _P2P_NODE_CONFIG environment variable
   324  func execP2PNode() {
   325  	glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
   326  	glogger.Verbosity(log.LvlInfo)
   327  	log.Root().SetHandler(glogger)
   328  
   329  	// read the services from argv
   330  	serviceNames := strings.Split(os.Args[1], ",")
   331  
   332  	// decode the config
   333  	confEnv := os.Getenv("_P2P_NODE_CONFIG")
   334  	if confEnv == "" {
   335  		log.Crit("missing _P2P_NODE_CONFIG")
   336  	}
   337  	var conf execNodeConfig
   338  	if err := json.Unmarshal([]byte(confEnv), &conf); err != nil {
   339  		log.Crit("error decoding _P2P_NODE_CONFIG", "err", err)
   340  	}
   341  	conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey
   342  	conf.Stack.Logger = log.New("node.id", conf.Node.ID.String())
   343  
   344  	// use explicit IP address in ListenAddr so that Enode URL is usable
   345  	externalIP := func() string {
   346  		addrs, err := net.InterfaceAddrs()
   347  		if err != nil {
   348  			log.Crit("error getting IP address", "err", err)
   349  		}
   350  		for _, addr := range addrs {
   351  			if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
   352  				return ip.IP.String()
   353  			}
   354  		}
   355  		log.Crit("unable to determine explicit IP address")
   356  		return ""
   357  	}
   358  	if strings.HasPrefix(conf.Stack.P2P.ListenAddr, ":") {
   359  		conf.Stack.P2P.ListenAddr = externalIP() + conf.Stack.P2P.ListenAddr
   360  	}
   361  	if conf.Stack.WSHost == "0.0.0.0" {
   362  		conf.Stack.WSHost = externalIP()
   363  	}
   364  
   365  	// initialize the devp2p stack
   366  	stack, err := node.New(&conf.Stack)
   367  	if err != nil {
   368  		log.Crit("error creating node stack", "err", err)
   369  	}
   370  
   371  	// register the services, collecting them into a map so we can wrap
   372  	// them in a snapshot service
   373  	services := make(map[string]node.Service, len(serviceNames))
   374  	for _, name := range serviceNames {
   375  		serviceFunc, exists := serviceFuncs[name]
   376  		if !exists {
   377  			log.Crit("unknown node service", "name", name)
   378  		}
   379  		constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) {
   380  			ctx := &ServiceContext{
   381  				RPCDialer:   &wsRPCDialer{addrs: conf.PeerAddrs},
   382  				NodeContext: nodeCtx,
   383  				Config:      conf.Node,
   384  			}
   385  			if conf.Snapshots != nil {
   386  				ctx.Snapshot = conf.Snapshots[name]
   387  			}
   388  			service, err := serviceFunc(ctx)
   389  			if err != nil {
   390  				return nil, err
   391  			}
   392  			services[name] = service
   393  			return service, nil
   394  		}
   395  		if err := stack.Register(constructor); err != nil {
   396  			log.Crit("error starting service", "name", name, "err", err)
   397  		}
   398  	}
   399  
   400  	// register the snapshot service
   401  	if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
   402  		return &snapshotService{services}, nil
   403  	}); err != nil {
   404  		log.Crit("error starting snapshot service", "err", err)
   405  	}
   406  
   407  	// start the stack
   408  	if err := stack.Start(); err != nil {
   409  		log.Crit("error stating node stack", "err", err)
   410  	}
   411  
   412  	// stop the stack if we get a SIGTERM signal
   413  	go func() {
   414  		sigc := make(chan os.Signal, 1)
   415  		signal.Notify(sigc, syscall.SIGTERM)
   416  		defer signal.Stop(sigc)
   417  		<-sigc
   418  		log.Info("Received SIGTERM, shutting down...")
   419  		stack.Stop()
   420  	}()
   421  
   422  	// wait for the stack to exit
   423  	stack.Wait()
   424  }
   425  
   426  // snapshotService is a node.Service which wraps a list of services and
   427  // exposes an API to generate a snapshot of those services
   428  type snapshotService struct {
   429  	services map[string]node.Service
   430  }
   431  
   432  func (s *snapshotService) APIs() []rpc.API {
   433  	return []rpc.API{{
   434  		Namespace: "simulation",
   435  		Version:   "1.0",
   436  		Service:   SnapshotAPI{s.services},
   437  	}}
   438  }
   439  
   440  func (s *snapshotService) Protocols() []p2p.Protocol {
   441  	return nil
   442  }
   443  
   444  func (s *snapshotService) Start(*p2p.Server) error {
   445  	return nil
   446  }
   447  
   448  func (s *snapshotService) Stop() error {
   449  	return nil
   450  }
   451  
   452  // SnapshotAPI provides an RPC method to create snapshots of services
   453  type SnapshotAPI struct {
   454  	services map[string]node.Service
   455  }
   456  
   457  func (api SnapshotAPI) Snapshot() (map[string][]byte, error) {
   458  	snapshots := make(map[string][]byte)
   459  	for name, service := range api.services {
   460  		if s, ok := service.(interface {
   461  			Snapshot() ([]byte, error)
   462  		}); ok {
   463  			snap, err := s.Snapshot()
   464  			if err != nil {
   465  				return nil, err
   466  			}
   467  			snapshots[name] = snap
   468  		}
   469  	}
   470  	return snapshots, nil
   471  }
   472  
   473  type wsRPCDialer struct {
   474  	addrs map[string]string
   475  }
   476  
   477  // DialRPC implements the RPCDialer interface by creating a WebSocket RPC
   478  // client of the given node
   479  func (w *wsRPCDialer) DialRPC(id discover.NodeID) (*rpc.Client, error) {
   480  	addr, ok := w.addrs[id.String()]
   481  	if !ok {
   482  		return nil, fmt.Errorf("unknown node: %s", id)
   483  	}
   484  	return rpc.DialWebsocket(context.Background(), addr, "http://localhost")
   485  }