gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/p2p/simulations/adapters/exec.go (about)

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