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 }