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