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