github.com/klaytn/klaytn@v1.12.1/networks/p2p/simulations/adapters/exec.go (about)

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