main.go 11 KB

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