You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
7.9 KiB

9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
9 years ago
8 years ago
8 years ago
9 years ago
9 years ago
8 years ago
9 years ago
9 years ago
8 years ago
9 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. package main
  2. import (
  3. "bufio"
  4. "encoding/hex"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "os"
  9. "strings"
  10. "github.com/tendermint/abci/client"
  11. "github.com/tendermint/abci/types"
  12. . "github.com/tendermint/go-common"
  13. "github.com/urfave/cli"
  14. )
  15. //structure for data passed to print response
  16. // variables must be exposed for JSON to read
  17. type response struct {
  18. Res types.Result
  19. Data string
  20. PrintCode bool
  21. Code string
  22. }
  23. func newResponse(res types.Result, data string, printCode bool) *response {
  24. rsp := &response{
  25. Res: res,
  26. Data: data,
  27. PrintCode: printCode,
  28. Code: "",
  29. }
  30. if printCode {
  31. rsp.Code = res.Code.String()
  32. }
  33. return rsp
  34. }
  35. // client is a global variable so it can be reused by the console
  36. var client abcicli.Client
  37. func main() {
  38. //workaround for the cli library (https://github.com/urfave/cli/issues/565)
  39. cli.OsExiter = func(_ int) {}
  40. app := cli.NewApp()
  41. app.Name = "abci-cli"
  42. app.Usage = "abci-cli [command] [args...]"
  43. app.Version = "0.3.0" // hex handling
  44. app.Flags = []cli.Flag{
  45. cli.StringFlag{
  46. Name: "address",
  47. Value: "tcp://127.0.0.1:46658",
  48. Usage: "address of application socket",
  49. },
  50. cli.StringFlag{
  51. Name: "abci",
  52. Value: "socket",
  53. Usage: "socket or grpc",
  54. },
  55. cli.BoolFlag{
  56. Name: "verbose",
  57. Usage: "print the command and results as if it were a console session",
  58. },
  59. }
  60. app.Commands = []cli.Command{
  61. {
  62. Name: "batch",
  63. Usage: "Run a batch of abci commands against an application",
  64. Action: func(c *cli.Context) error {
  65. return cmdBatch(app, c)
  66. },
  67. },
  68. {
  69. Name: "console",
  70. Usage: "Start an interactive abci console for multiple commands",
  71. Action: func(c *cli.Context) error {
  72. return cmdConsole(app, c)
  73. },
  74. },
  75. {
  76. Name: "echo",
  77. Usage: "Have the application echo a message",
  78. Action: func(c *cli.Context) error {
  79. return cmdEcho(c)
  80. },
  81. },
  82. {
  83. Name: "info",
  84. Usage: "Get some info about the application",
  85. Action: func(c *cli.Context) error {
  86. return cmdInfo(c)
  87. },
  88. },
  89. {
  90. Name: "set_option",
  91. Usage: "Set an option on the application",
  92. Action: func(c *cli.Context) error {
  93. return cmdSetOption(c)
  94. },
  95. },
  96. {
  97. Name: "deliver_tx",
  98. Usage: "Deliver a new tx to application",
  99. Action: func(c *cli.Context) error {
  100. return cmdDeliverTx(c)
  101. },
  102. },
  103. {
  104. Name: "check_tx",
  105. Usage: "Validate a tx",
  106. Action: func(c *cli.Context) error {
  107. return cmdCheckTx(c)
  108. },
  109. },
  110. {
  111. Name: "commit",
  112. Usage: "Commit the application state and return the Merkle root hash",
  113. Action: func(c *cli.Context) error {
  114. return cmdCommit(c)
  115. },
  116. },
  117. {
  118. Name: "query",
  119. Usage: "Query application state",
  120. Action: func(c *cli.Context) error {
  121. return cmdQuery(c)
  122. },
  123. },
  124. }
  125. app.Before = before
  126. err := app.Run(os.Args)
  127. if err != nil {
  128. Exit(err.Error())
  129. }
  130. }
  131. func before(c *cli.Context) error {
  132. if client == nil {
  133. var err error
  134. client, err = abcicli.NewClient(c.GlobalString("address"), c.GlobalString("abci"), false)
  135. if err != nil {
  136. Exit(err.Error())
  137. }
  138. }
  139. return nil
  140. }
  141. // badCmd is called when we invoke with an invalid first argument (just for console for now)
  142. func badCmd(c *cli.Context, cmd string) {
  143. fmt.Println("Unknown command:", cmd)
  144. fmt.Println("Please try one of the following:")
  145. fmt.Println("")
  146. cli.DefaultAppComplete(c)
  147. }
  148. //Generates new Args array based off of previous call args to maintain flag persistence
  149. func persistentArgs(line []byte) []string {
  150. //generate the arguments to run from orginal os.Args
  151. // to maintain flag arguments
  152. args := os.Args
  153. args = args[:len(args)-1] // remove the previous command argument
  154. if len(line) > 0 { //prevents introduction of extra space leading to argument parse errors
  155. args = append(args, strings.Split(string(line), " ")...)
  156. }
  157. return args
  158. }
  159. //--------------------------------------------------------------------------------
  160. func cmdBatch(app *cli.App, c *cli.Context) error {
  161. bufReader := bufio.NewReader(os.Stdin)
  162. for {
  163. line, more, err := bufReader.ReadLine()
  164. if more {
  165. return errors.New("Input line is too long")
  166. } else if err == io.EOF {
  167. break
  168. } else if len(line) == 0 {
  169. continue
  170. } else if err != nil {
  171. return err
  172. }
  173. args := persistentArgs(line)
  174. app.Run(args) //cli prints error within its func call
  175. }
  176. return nil
  177. }
  178. func cmdConsole(app *cli.App, c *cli.Context) error {
  179. // don't hard exit on mistyped commands (eg. check vs check_tx)
  180. app.CommandNotFound = badCmd
  181. for {
  182. fmt.Printf("\n> ")
  183. bufReader := bufio.NewReader(os.Stdin)
  184. line, more, err := bufReader.ReadLine()
  185. if more {
  186. return errors.New("Input is too long")
  187. } else if err != nil {
  188. return err
  189. }
  190. args := persistentArgs(line)
  191. app.Run(args) //cli prints error within its func call
  192. }
  193. }
  194. // Have the application echo a message
  195. func cmdEcho(c *cli.Context) error {
  196. args := c.Args()
  197. if len(args) != 1 {
  198. return errors.New("Command echo takes 1 argument")
  199. }
  200. res := client.EchoSync(args[0])
  201. rsp := newResponse(res, string(res.Data), false)
  202. printResponse(c, rsp)
  203. return nil
  204. }
  205. // Get some info from the application
  206. func cmdInfo(c *cli.Context) error {
  207. resInfo, err := client.InfoSync()
  208. if err != nil {
  209. return err
  210. }
  211. rsp := newResponse(types.Result{}, string(resInfo.Data), false)
  212. printResponse(c, rsp)
  213. return nil
  214. }
  215. // Set an option on the application
  216. func cmdSetOption(c *cli.Context) error {
  217. args := c.Args()
  218. if len(args) != 2 {
  219. return errors.New("Command set_option takes 2 arguments (key, value)")
  220. }
  221. res := client.SetOptionSync(args[0], args[1])
  222. rsp := newResponse(res, Fmt("%s=%s", args[0], args[1]), false)
  223. printResponse(c, rsp)
  224. return nil
  225. }
  226. // Append a new tx to application
  227. func cmdDeliverTx(c *cli.Context) error {
  228. args := c.Args()
  229. if len(args) != 1 {
  230. return errors.New("Command deliver_tx takes 1 argument")
  231. }
  232. txBytes, err := stringOrHexToBytes(c.Args()[0])
  233. if err != nil {
  234. return err
  235. }
  236. res := client.DeliverTxSync(txBytes)
  237. rsp := newResponse(res, string(res.Data), true)
  238. printResponse(c, rsp)
  239. return nil
  240. }
  241. // Validate a tx
  242. func cmdCheckTx(c *cli.Context) error {
  243. args := c.Args()
  244. if len(args) != 1 {
  245. return errors.New("Command check_tx takes 1 argument")
  246. }
  247. txBytes, err := stringOrHexToBytes(c.Args()[0])
  248. if err != nil {
  249. return err
  250. }
  251. res := client.CheckTxSync(txBytes)
  252. rsp := newResponse(res, string(res.Data), true)
  253. printResponse(c, rsp)
  254. return nil
  255. }
  256. // Get application Merkle root hash
  257. func cmdCommit(c *cli.Context) error {
  258. res := client.CommitSync()
  259. rsp := newResponse(res, Fmt("0x%X", res.Data), false)
  260. printResponse(c, rsp)
  261. return nil
  262. }
  263. // Query application state
  264. func cmdQuery(c *cli.Context) error {
  265. args := c.Args()
  266. if len(args) != 1 {
  267. return errors.New("Command query takes 1 argument")
  268. }
  269. queryBytes, err := stringOrHexToBytes(c.Args()[0])
  270. if err != nil {
  271. return err
  272. }
  273. res := client.QuerySync(queryBytes)
  274. rsp := newResponse(res, string(res.Data), true)
  275. printResponse(c, rsp)
  276. return nil
  277. }
  278. //--------------------------------------------------------------------------------
  279. func printResponse(c *cli.Context, rsp *response) {
  280. verbose := c.GlobalBool("verbose")
  281. if verbose {
  282. fmt.Println(">", c.Command.Name, strings.Join(c.Args(), " "))
  283. }
  284. if rsp.PrintCode {
  285. fmt.Printf("-> code: %s\n", rsp.Code)
  286. }
  287. //if pr.res.Error != "" {
  288. // fmt.Printf("-> error: %s\n", pr.res.Error)
  289. //}
  290. if rsp.Data != "" {
  291. fmt.Printf("-> data: %s\n", rsp.Data)
  292. }
  293. if rsp.Res.Log != "" {
  294. fmt.Printf("-> log: %s\n", rsp.Res.Log)
  295. }
  296. if verbose {
  297. fmt.Println("")
  298. }
  299. }
  300. // NOTE: s is interpreted as a string unless prefixed with 0x
  301. func stringOrHexToBytes(s string) ([]byte, error) {
  302. if len(s) > 2 && strings.ToLower(s[:2]) == "0x" {
  303. b, err := hex.DecodeString(s[2:])
  304. if err != nil {
  305. err = fmt.Errorf("Error decoding hex argument: %s", err.Error())
  306. return nil, err
  307. }
  308. return b, nil
  309. }
  310. if !strings.HasPrefix(s, "\"") || !strings.HasSuffix(s, "\"") {
  311. err := fmt.Errorf("Invalid string arg: \"%s\". Must be quoted or a \"0x\"-prefixed hex string", s)
  312. return nil, err
  313. }
  314. return []byte(s[1 : len(s)-1]), nil
  315. }