github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/cmd/p2psim/main.go (about)

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