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