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