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