github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/cmd/p2psim/main.go (about)

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