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