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