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