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.

398 lines
8.7 KiB

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