main.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  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/discover"
  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. app.Run(os.Args)
  179. }
  180. func showNetwork(ctx *cli.Context) error {
  181. if len(ctx.Args()) != 0 {
  182. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  183. }
  184. network, err := client.GetNetwork()
  185. if err != nil {
  186. return err
  187. }
  188. w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0)
  189. defer w.Flush()
  190. fmt.Fprintf(w, "NODES\t%d\n", len(network.Nodes))
  191. fmt.Fprintf(w, "CONNS\t%d\n", len(network.Conns))
  192. return nil
  193. }
  194. func streamNetwork(ctx *cli.Context) error {
  195. if len(ctx.Args()) != 0 {
  196. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  197. }
  198. events := make(chan *simulations.Event)
  199. sub, err := client.SubscribeNetwork(events, simulations.SubscribeOpts{
  200. Current: ctx.Bool("current"),
  201. Filter: ctx.String("filter"),
  202. })
  203. if err != nil {
  204. return err
  205. }
  206. defer sub.Unsubscribe()
  207. enc := json.NewEncoder(ctx.App.Writer)
  208. for {
  209. select {
  210. case event := <-events:
  211. if err := enc.Encode(event); err != nil {
  212. return err
  213. }
  214. case err := <-sub.Err():
  215. return err
  216. }
  217. }
  218. }
  219. func createSnapshot(ctx *cli.Context) error {
  220. if len(ctx.Args()) != 0 {
  221. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  222. }
  223. snap, err := client.CreateSnapshot()
  224. if err != nil {
  225. return err
  226. }
  227. return json.NewEncoder(os.Stdout).Encode(snap)
  228. }
  229. func loadSnapshot(ctx *cli.Context) error {
  230. if len(ctx.Args()) != 0 {
  231. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  232. }
  233. snap := &simulations.Snapshot{}
  234. if err := json.NewDecoder(os.Stdin).Decode(snap); err != nil {
  235. return err
  236. }
  237. return client.LoadSnapshot(snap)
  238. }
  239. func listNodes(ctx *cli.Context) error {
  240. if len(ctx.Args()) != 0 {
  241. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  242. }
  243. nodes, err := client.GetNodes()
  244. if err != nil {
  245. return err
  246. }
  247. w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0)
  248. defer w.Flush()
  249. fmt.Fprintf(w, "NAME\tPROTOCOLS\tID\n")
  250. for _, node := range nodes {
  251. fmt.Fprintf(w, "%s\t%s\t%s\n", node.Name, strings.Join(protocolList(node), ","), node.ID)
  252. }
  253. return nil
  254. }
  255. func protocolList(node *p2p.NodeInfo) []string {
  256. protos := make([]string, 0, len(node.Protocols))
  257. for name := range node.Protocols {
  258. protos = append(protos, name)
  259. }
  260. return protos
  261. }
  262. func createNode(ctx *cli.Context) error {
  263. if len(ctx.Args()) != 0 {
  264. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  265. }
  266. config := &adapters.NodeConfig{
  267. Name: ctx.String("name"),
  268. }
  269. if key := ctx.String("key"); key != "" {
  270. privKey, err := crypto.HexToECDSA(key)
  271. if err != nil {
  272. return err
  273. }
  274. config.ID = discover.PubkeyID(&privKey.PublicKey)
  275. config.PrivateKey = privKey
  276. }
  277. if services := ctx.String("services"); services != "" {
  278. config.Services = strings.Split(services, ",")
  279. }
  280. node, err := client.CreateNode(config)
  281. if err != nil {
  282. return err
  283. }
  284. fmt.Fprintln(ctx.App.Writer, "Created", node.Name)
  285. return nil
  286. }
  287. func showNode(ctx *cli.Context) error {
  288. args := ctx.Args()
  289. if len(args) != 1 {
  290. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  291. }
  292. nodeName := args[0]
  293. node, err := client.GetNode(nodeName)
  294. if err != nil {
  295. return err
  296. }
  297. w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0)
  298. defer w.Flush()
  299. fmt.Fprintf(w, "NAME\t%s\n", node.Name)
  300. fmt.Fprintf(w, "PROTOCOLS\t%s\n", strings.Join(protocolList(node), ","))
  301. fmt.Fprintf(w, "ID\t%s\n", node.ID)
  302. fmt.Fprintf(w, "ENODE\t%s\n", node.Enode)
  303. for name, proto := range node.Protocols {
  304. fmt.Fprintln(w)
  305. fmt.Fprintf(w, "--- PROTOCOL INFO: %s\n", name)
  306. fmt.Fprintf(w, "%v\n", proto)
  307. fmt.Fprintf(w, "---\n")
  308. }
  309. return nil
  310. }
  311. func startNode(ctx *cli.Context) error {
  312. args := ctx.Args()
  313. if len(args) != 1 {
  314. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  315. }
  316. nodeName := args[0]
  317. if err := client.StartNode(nodeName); err != nil {
  318. return err
  319. }
  320. fmt.Fprintln(ctx.App.Writer, "Started", nodeName)
  321. return nil
  322. }
  323. func stopNode(ctx *cli.Context) error {
  324. args := ctx.Args()
  325. if len(args) != 1 {
  326. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  327. }
  328. nodeName := args[0]
  329. if err := client.StopNode(nodeName); err != nil {
  330. return err
  331. }
  332. fmt.Fprintln(ctx.App.Writer, "Stopped", nodeName)
  333. return nil
  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. func disconnectNode(ctx *cli.Context) error {
  349. args := ctx.Args()
  350. if len(args) != 2 {
  351. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  352. }
  353. nodeName := args[0]
  354. peerName := args[1]
  355. if err := client.DisconnectNode(nodeName, peerName); err != nil {
  356. return err
  357. }
  358. fmt.Fprintln(ctx.App.Writer, "Disconnected", nodeName, "from", peerName)
  359. return nil
  360. }
  361. func rpcNode(ctx *cli.Context) error {
  362. args := ctx.Args()
  363. if len(args) < 2 {
  364. return cli.ShowCommandHelp(ctx, ctx.Command.Name)
  365. }
  366. nodeName := args[0]
  367. method := args[1]
  368. rpcClient, err := client.RPCClient(context.Background(), nodeName)
  369. if err != nil {
  370. return err
  371. }
  372. if ctx.Bool("subscribe") {
  373. return rpcSubscribe(rpcClient, ctx.App.Writer, method, args[3:]...)
  374. }
  375. var result interface{}
  376. params := make([]interface{}, len(args[3:]))
  377. for i, v := range args[3:] {
  378. params[i] = v
  379. }
  380. if err := rpcClient.Call(&result, method, params...); err != nil {
  381. return err
  382. }
  383. return json.NewEncoder(ctx.App.Writer).Encode(result)
  384. }
  385. func rpcSubscribe(client *rpc.Client, out io.Writer, method string, args ...string) error {
  386. parts := strings.SplitN(method, "_", 2)
  387. namespace := parts[0]
  388. method = parts[1]
  389. ch := make(chan interface{})
  390. subArgs := make([]interface{}, len(args)+1)
  391. subArgs[0] = method
  392. for i, v := range args {
  393. subArgs[i+1] = v
  394. }
  395. sub, err := client.Subscribe(context.Background(), namespace, ch, subArgs...)
  396. if err != nil {
  397. return err
  398. }
  399. defer sub.Unsubscribe()
  400. enc := json.NewEncoder(out)
  401. for {
  402. select {
  403. case v := <-ch:
  404. if err := enc.Encode(v); err != nil {
  405. return err
  406. }
  407. case err := <-sub.Err():
  408. return err
  409. }
  410. }
  411. }