github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/p2p/simulations/adapters/exec.go (about)

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