main.go 10 KB

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