github.com/core-coin/go-core/v2@v2.1.9/cmd/p2psim/main.go (about) 1 // Copyright 2017 by the Authors 2 // This file is part of go-core. 3 // 4 // go-core 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-core 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-core. 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 "gopkg.in/urfave/cli.v1" 48 49 "github.com/core-coin/go-core/v2/crypto" 50 "github.com/core-coin/go-core/v2/p2p" 51 "github.com/core-coin/go-core/v2/p2p/enode" 52 "github.com/core-coin/go-core/v2/p2p/simulations" 53 "github.com/core-coin/go-core/v2/p2p/simulations/adapters" 54 "github.com/core-coin/go-core/v2/rpc" 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.UnmarshalPrivateKeyHex(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.Lifecycles = 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 }