github.com/luckypickle/go-ethereum-vet@v1.14.2/cmd/p2psim/main.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU 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  // go-ethereum 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 General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // p2psim provides a command-line client for a simulation HTTP API.
    18  //
    19  // Here is an example of creating a 2 node network with the first node
    20  // connected to the second:
    21  //
    22  //	$ p2psim node create
    23  //	Created node01
    24  //
    25  //	$ p2psim node start node01
    26  //	Started node01
    27  //
    28  //	$ p2psim node create
    29  //	Created node02
    30  //
    31  //	$ p2psim node start node02
    32  //	Started node02
    33  //
    34  //	$ p2psim node connect node01 node02
    35  //	Connected node01 to node02
    36  package main
    37  
    38  import (
    39  	"context"
    40  	"encoding/json"
    41  	"fmt"
    42  	"io"
    43  	"os"
    44  	"strings"
    45  	"text/tabwriter"
    46  
    47  	"github.com/luckypickle/go-ethereum-vet/crypto"
    48  	"github.com/luckypickle/go-ethereum-vet/p2p"
    49  	"github.com/luckypickle/go-ethereum-vet/p2p/discover"
    50  	"github.com/luckypickle/go-ethereum-vet/p2p/simulations"
    51  	"github.com/luckypickle/go-ethereum-vet/p2p/simulations/adapters"
    52  	"github.com/luckypickle/go-ethereum-vet/rpc"
    53  	"gopkg.in/urfave/cli.v1"
    54  )
    55  
    56  var client *simulations.Client
    57  
    58  func main() {
    59  	app := cli.NewApp()
    60  	app.Usage = "devp2p simulation command-line client"
    61  	app.Flags = []cli.Flag{
    62  		cli.StringFlag{
    63  			Name:   "api",
    64  			Value:  "http://localhost:8888",
    65  			Usage:  "simulation API URL",
    66  			EnvVar: "P2PSIM_API_URL",
    67  		},
    68  	}
    69  	app.Before = func(ctx *cli.Context) error {
    70  		client = simulations.NewClient(ctx.GlobalString("api"))
    71  		return nil
    72  	}
    73  	app.Commands = []cli.Command{
    74  		{
    75  			Name:   "show",
    76  			Usage:  "show network information",
    77  			Action: showNetwork,
    78  		},
    79  		{
    80  			Name:   "events",
    81  			Usage:  "stream network events",
    82  			Action: streamNetwork,
    83  			Flags: []cli.Flag{
    84  				cli.BoolFlag{
    85  					Name:  "current",
    86  					Usage: "get existing nodes and conns first",
    87  				},
    88  				cli.StringFlag{
    89  					Name:  "filter",
    90  					Value: "",
    91  					Usage: "message filter",
    92  				},
    93  			},
    94  		},
    95  		{
    96  			Name:   "snapshot",
    97  			Usage:  "create a network snapshot to stdout",
    98  			Action: createSnapshot,
    99  		},
   100  		{
   101  			Name:   "load",
   102  			Usage:  "load a network snapshot from stdin",
   103  			Action: loadSnapshot,
   104  		},
   105  		{
   106  			Name:   "node",
   107  			Usage:  "manage simulation nodes",
   108  			Action: listNodes,
   109  			Subcommands: []cli.Command{
   110  				{
   111  					Name:   "list",
   112  					Usage:  "list nodes",
   113  					Action: listNodes,
   114  				},
   115  				{
   116  					Name:   "create",
   117  					Usage:  "create a node",
   118  					Action: createNode,
   119  					Flags: []cli.Flag{
   120  						cli.StringFlag{
   121  							Name:  "name",
   122  							Value: "",
   123  							Usage: "node name",
   124  						},
   125  						cli.StringFlag{
   126  							Name:  "services",
   127  							Value: "",
   128  							Usage: "node services (comma separated)",
   129  						},
   130  						cli.StringFlag{
   131  							Name:  "key",
   132  							Value: "",
   133  							Usage: "node private key (hex encoded)",
   134  						},
   135  					},
   136  				},
   137  				{
   138  					Name:      "show",
   139  					ArgsUsage: "<node>",
   140  					Usage:     "show node information",
   141  					Action:    showNode,
   142  				},
   143  				{
   144  					Name:      "start",
   145  					ArgsUsage: "<node>",
   146  					Usage:     "start a node",
   147  					Action:    startNode,
   148  				},
   149  				{
   150  					Name:      "stop",
   151  					ArgsUsage: "<node>",
   152  					Usage:     "stop a node",
   153  					Action:    stopNode,
   154  				},
   155  				{
   156  					Name:      "connect",
   157  					ArgsUsage: "<node> <peer>",
   158  					Usage:     "connect a node to a peer node",
   159  					Action:    connectNode,
   160  				},
   161  				{
   162  					Name:      "disconnect",
   163  					ArgsUsage: "<node> <peer>",
   164  					Usage:     "disconnect a node from a peer node",
   165  					Action:    disconnectNode,
   166  				},
   167  				{
   168  					Name:      "rpc",
   169  					ArgsUsage: "<node> <method> [<args>]",
   170  					Usage:     "call a node RPC method",
   171  					Action:    rpcNode,
   172  					Flags: []cli.Flag{
   173  						cli.BoolFlag{
   174  							Name:  "subscribe",
   175  							Usage: "method is a subscription",
   176  						},
   177  					},
   178  				},
   179  			},
   180  		},
   181  	}
   182  	if err := app.Run(os.Args); err != nil {
   183  		fmt.Fprintln(os.Stderr, err)
   184  		os.Exit(1)
   185  	}
   186  }
   187  
   188  func showNetwork(ctx *cli.Context) error {
   189  	if len(ctx.Args()) != 0 {
   190  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   191  	}
   192  	network, err := client.GetNetwork()
   193  	if err != nil {
   194  		return err
   195  	}
   196  	w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0)
   197  	defer w.Flush()
   198  	fmt.Fprintf(w, "NODES\t%d\n", len(network.Nodes))
   199  	fmt.Fprintf(w, "CONNS\t%d\n", len(network.Conns))
   200  	return nil
   201  }
   202  
   203  func streamNetwork(ctx *cli.Context) error {
   204  	if len(ctx.Args()) != 0 {
   205  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   206  	}
   207  	events := make(chan *simulations.Event)
   208  	sub, err := client.SubscribeNetwork(events, simulations.SubscribeOpts{
   209  		Current: ctx.Bool("current"),
   210  		Filter:  ctx.String("filter"),
   211  	})
   212  	if err != nil {
   213  		return err
   214  	}
   215  	defer sub.Unsubscribe()
   216  	enc := json.NewEncoder(ctx.App.Writer)
   217  	for {
   218  		select {
   219  		case event := <-events:
   220  			if err := enc.Encode(event); err != nil {
   221  				return err
   222  			}
   223  		case err := <-sub.Err():
   224  			return err
   225  		}
   226  	}
   227  }
   228  
   229  func createSnapshot(ctx *cli.Context) error {
   230  	if len(ctx.Args()) != 0 {
   231  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   232  	}
   233  	snap, err := client.CreateSnapshot()
   234  	if err != nil {
   235  		return err
   236  	}
   237  	return json.NewEncoder(os.Stdout).Encode(snap)
   238  }
   239  
   240  func loadSnapshot(ctx *cli.Context) error {
   241  	if len(ctx.Args()) != 0 {
   242  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   243  	}
   244  	snap := &simulations.Snapshot{}
   245  	if err := json.NewDecoder(os.Stdin).Decode(snap); err != nil {
   246  		return err
   247  	}
   248  	return client.LoadSnapshot(snap)
   249  }
   250  
   251  func listNodes(ctx *cli.Context) error {
   252  	if len(ctx.Args()) != 0 {
   253  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   254  	}
   255  	nodes, err := client.GetNodes()
   256  	if err != nil {
   257  		return err
   258  	}
   259  	w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0)
   260  	defer w.Flush()
   261  	fmt.Fprintf(w, "NAME\tPROTOCOLS\tID\n")
   262  	for _, node := range nodes {
   263  		fmt.Fprintf(w, "%s\t%s\t%s\n", node.Name, strings.Join(protocolList(node), ","), node.ID)
   264  	}
   265  	return nil
   266  }
   267  
   268  func protocolList(node *p2p.NodeInfo) []string {
   269  	protos := make([]string, 0, len(node.Protocols))
   270  	for name := range node.Protocols {
   271  		protos = append(protos, name)
   272  	}
   273  	return protos
   274  }
   275  
   276  func createNode(ctx *cli.Context) error {
   277  	if len(ctx.Args()) != 0 {
   278  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   279  	}
   280  	config := adapters.RandomNodeConfig()
   281  	config.Name = ctx.String("name")
   282  	if key := ctx.String("key"); key != "" {
   283  		privKey, err := crypto.HexToECDSA(key)
   284  		if err != nil {
   285  			return err
   286  		}
   287  		config.ID = discover.PubkeyID(&privKey.PublicKey)
   288  		config.PrivateKey = privKey
   289  	}
   290  	if services := ctx.String("services"); services != "" {
   291  		config.Services = strings.Split(services, ",")
   292  	}
   293  	node, err := client.CreateNode(config)
   294  	if err != nil {
   295  		return err
   296  	}
   297  	fmt.Fprintln(ctx.App.Writer, "Created", node.Name)
   298  	return nil
   299  }
   300  
   301  func showNode(ctx *cli.Context) error {
   302  	args := ctx.Args()
   303  	if len(args) != 1 {
   304  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   305  	}
   306  	nodeName := args[0]
   307  	node, err := client.GetNode(nodeName)
   308  	if err != nil {
   309  		return err
   310  	}
   311  	w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0)
   312  	defer w.Flush()
   313  	fmt.Fprintf(w, "NAME\t%s\n", node.Name)
   314  	fmt.Fprintf(w, "PROTOCOLS\t%s\n", strings.Join(protocolList(node), ","))
   315  	fmt.Fprintf(w, "ID\t%s\n", node.ID)
   316  	fmt.Fprintf(w, "ENODE\t%s\n", node.Enode)
   317  	for name, proto := range node.Protocols {
   318  		fmt.Fprintln(w)
   319  		fmt.Fprintf(w, "--- PROTOCOL INFO: %s\n", name)
   320  		fmt.Fprintf(w, "%v\n", proto)
   321  		fmt.Fprintf(w, "---\n")
   322  	}
   323  	return nil
   324  }
   325  
   326  func startNode(ctx *cli.Context) error {
   327  	args := ctx.Args()
   328  	if len(args) != 1 {
   329  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   330  	}
   331  	nodeName := args[0]
   332  	if err := client.StartNode(nodeName); err != nil {
   333  		return err
   334  	}
   335  	fmt.Fprintln(ctx.App.Writer, "Started", nodeName)
   336  	return nil
   337  }
   338  
   339  func stopNode(ctx *cli.Context) error {
   340  	args := ctx.Args()
   341  	if len(args) != 1 {
   342  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   343  	}
   344  	nodeName := args[0]
   345  	if err := client.StopNode(nodeName); err != nil {
   346  		return err
   347  	}
   348  	fmt.Fprintln(ctx.App.Writer, "Stopped", nodeName)
   349  	return nil
   350  }
   351  
   352  func connectNode(ctx *cli.Context) error {
   353  	args := ctx.Args()
   354  	if len(args) != 2 {
   355  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   356  	}
   357  	nodeName := args[0]
   358  	peerName := args[1]
   359  	if err := client.ConnectNode(nodeName, peerName); err != nil {
   360  		return err
   361  	}
   362  	fmt.Fprintln(ctx.App.Writer, "Connected", nodeName, "to", peerName)
   363  	return nil
   364  }
   365  
   366  func disconnectNode(ctx *cli.Context) error {
   367  	args := ctx.Args()
   368  	if len(args) != 2 {
   369  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   370  	}
   371  	nodeName := args[0]
   372  	peerName := args[1]
   373  	if err := client.DisconnectNode(nodeName, peerName); err != nil {
   374  		return err
   375  	}
   376  	fmt.Fprintln(ctx.App.Writer, "Disconnected", nodeName, "from", peerName)
   377  	return nil
   378  }
   379  
   380  func rpcNode(ctx *cli.Context) error {
   381  	args := ctx.Args()
   382  	if len(args) < 2 {
   383  		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
   384  	}
   385  	nodeName := args[0]
   386  	method := args[1]
   387  	rpcClient, err := client.RPCClient(context.Background(), nodeName)
   388  	if err != nil {
   389  		return err
   390  	}
   391  	if ctx.Bool("subscribe") {
   392  		return rpcSubscribe(rpcClient, ctx.App.Writer, method, args[3:]...)
   393  	}
   394  	var result interface{}
   395  	params := make([]interface{}, len(args[3:]))
   396  	for i, v := range args[3:] {
   397  		params[i] = v
   398  	}
   399  	if err := rpcClient.Call(&result, method, params...); err != nil {
   400  		return err
   401  	}
   402  	return json.NewEncoder(ctx.App.Writer).Encode(result)
   403  }
   404  
   405  func rpcSubscribe(client *rpc.Client, out io.Writer, method string, args ...string) error {
   406  	parts := strings.SplitN(method, "_", 2)
   407  	namespace := parts[0]
   408  	method = parts[1]
   409  	ch := make(chan interface{})
   410  	subArgs := make([]interface{}, len(args)+1)
   411  	subArgs[0] = method
   412  	for i, v := range args {
   413  		subArgs[i+1] = v
   414  	}
   415  	sub, err := client.Subscribe(context.Background(), namespace, ch, subArgs...)
   416  	if err != nil {
   417  		return err
   418  	}
   419  	defer sub.Unsubscribe()
   420  	enc := json.NewEncoder(out)
   421  	for {
   422  		select {
   423  		case v := <-ch:
   424  			if err := enc.Encode(v); err != nil {
   425  				return err
   426  			}
   427  		case err := <-sub.Err():
   428  			return err
   429  		}
   430  	}
   431  }