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.

423 lines
9.2 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. Flags: []cli.Flag{
  120. cli.StringFlag{
  121. Name: "path",
  122. Value: "/store",
  123. Usage: "Path to prefix the query with",
  124. },
  125. cli.IntFlag{
  126. Name: "height",
  127. Value: 0,
  128. Usage: "Height to query the blockchain at",
  129. },
  130. cli.BoolFlag{
  131. Name: "prove",
  132. Usage: "Whether or not to return a merkle proof of the query result",
  133. },
  134. },
  135. },
  136. }
  137. app.Before = before
  138. err := app.Run(os.Args)
  139. if err != nil {
  140. logger.Error(err.Error())
  141. os.Exit(1)
  142. }
  143. }
  144. func before(c *cli.Context) error {
  145. if logger == nil {
  146. logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), log.AllowError())
  147. }
  148. if client == nil {
  149. var err error
  150. client, err = abcicli.NewClient(c.GlobalString("address"), c.GlobalString("abci"), false)
  151. if err != nil {
  152. logger.Error(err.Error())
  153. os.Exit(1)
  154. }
  155. client.SetLogger(logger.With("module", "abci-client"))
  156. if _, err := client.Start(); err != nil {
  157. return err
  158. }
  159. }
  160. return nil
  161. }
  162. // badCmd is called when we invoke with an invalid first argument (just for console for now)
  163. func badCmd(c *cli.Context, cmd string) {
  164. fmt.Println("Unknown command:", cmd)
  165. fmt.Println("Please try one of the following:")
  166. fmt.Println("")
  167. cli.DefaultAppComplete(c)
  168. }
  169. //Generates new Args array based off of previous call args to maintain flag persistence
  170. func persistentArgs(line []byte) []string {
  171. //generate the arguments to run from orginal os.Args
  172. // to maintain flag arguments
  173. args := os.Args
  174. args = args[:len(args)-1] // remove the previous command argument
  175. if len(line) > 0 { //prevents introduction of extra space leading to argument parse errors
  176. args = append(args, strings.Split(string(line), " ")...)
  177. }
  178. return args
  179. }
  180. //--------------------------------------------------------------------------------
  181. func cmdBatch(app *cli.App, c *cli.Context) error {
  182. bufReader := bufio.NewReader(os.Stdin)
  183. for {
  184. line, more, err := bufReader.ReadLine()
  185. if more {
  186. return errors.New("Input line is too long")
  187. } else if err == io.EOF {
  188. break
  189. } else if len(line) == 0 {
  190. continue
  191. } else if err != nil {
  192. return err
  193. }
  194. args := persistentArgs(line)
  195. app.Run(args) //cli prints error within its func call
  196. }
  197. return nil
  198. }
  199. func cmdConsole(app *cli.App, c *cli.Context) error {
  200. // don't hard exit on mistyped commands (eg. check vs check_tx)
  201. app.CommandNotFound = badCmd
  202. for {
  203. fmt.Printf("\n> ")
  204. bufReader := bufio.NewReader(os.Stdin)
  205. line, more, err := bufReader.ReadLine()
  206. if more {
  207. return errors.New("Input is too long")
  208. } else if err != nil {
  209. return err
  210. }
  211. args := persistentArgs(line)
  212. app.Run(args) //cli prints error within its func call
  213. }
  214. }
  215. // Have the application echo a message
  216. func cmdEcho(c *cli.Context) error {
  217. args := c.Args()
  218. if len(args) != 1 {
  219. return errors.New("Command echo takes 1 argument")
  220. }
  221. resEcho := client.EchoSync(args[0])
  222. printResponse(c, response{
  223. Data: resEcho.Data,
  224. })
  225. return nil
  226. }
  227. // Get some info from the application
  228. func cmdInfo(c *cli.Context) error {
  229. resInfo, err := client.InfoSync()
  230. if err != nil {
  231. return err
  232. }
  233. printResponse(c, response{
  234. Data: []byte(resInfo.Data),
  235. })
  236. return nil
  237. }
  238. // Set an option on the application
  239. func cmdSetOption(c *cli.Context) error {
  240. args := c.Args()
  241. if len(args) != 2 {
  242. return errors.New("Command set_option takes 2 arguments (key, value)")
  243. }
  244. resSetOption := client.SetOptionSync(args[0], args[1])
  245. printResponse(c, response{
  246. Log: resSetOption.Log,
  247. })
  248. return nil
  249. }
  250. // Append a new tx to application
  251. func cmdDeliverTx(c *cli.Context) error {
  252. args := c.Args()
  253. if len(args) != 1 {
  254. return errors.New("Command deliver_tx takes 1 argument")
  255. }
  256. txBytes, err := stringOrHexToBytes(c.Args()[0])
  257. if err != nil {
  258. return err
  259. }
  260. res := client.DeliverTxSync(txBytes)
  261. printResponse(c, response{
  262. Code: res.Code,
  263. Data: res.Data,
  264. Log: res.Log,
  265. })
  266. return nil
  267. }
  268. // Validate a tx
  269. func cmdCheckTx(c *cli.Context) error {
  270. args := c.Args()
  271. if len(args) != 1 {
  272. return errors.New("Command check_tx takes 1 argument")
  273. }
  274. txBytes, err := stringOrHexToBytes(c.Args()[0])
  275. if err != nil {
  276. return err
  277. }
  278. res := client.CheckTxSync(txBytes)
  279. printResponse(c, response{
  280. Code: res.Code,
  281. Data: res.Data,
  282. Log: res.Log,
  283. })
  284. return nil
  285. }
  286. // Get application Merkle root hash
  287. func cmdCommit(c *cli.Context) error {
  288. res := client.CommitSync()
  289. printResponse(c, response{
  290. Code: res.Code,
  291. Data: res.Data,
  292. Log: res.Log,
  293. })
  294. return nil
  295. }
  296. // Query application state
  297. func cmdQuery(c *cli.Context) error {
  298. args := c.Args()
  299. if len(args) != 1 {
  300. return errors.New("Command query takes 1 argument, the query bytes")
  301. }
  302. queryBytes, err := stringOrHexToBytes(args[0])
  303. if err != nil {
  304. return err
  305. }
  306. path := c.String("path")
  307. height := c.Int("height")
  308. prove := c.Bool("prove")
  309. resQuery, err := client.QuerySync(types.RequestQuery{
  310. Data: queryBytes,
  311. Path: path,
  312. Height: uint64(height),
  313. Prove: prove,
  314. })
  315. if err != nil {
  316. return err
  317. }
  318. printResponse(c, response{
  319. Code: resQuery.Code,
  320. Log: resQuery.Log,
  321. Query: &queryResponse{
  322. Key: resQuery.Key,
  323. Value: resQuery.Value,
  324. Height: resQuery.Height,
  325. Proof: resQuery.Proof,
  326. },
  327. })
  328. return nil
  329. }
  330. //--------------------------------------------------------------------------------
  331. func printResponse(c *cli.Context, rsp response) {
  332. verbose := c.GlobalBool("verbose")
  333. if verbose {
  334. fmt.Println(">", c.Command.Name, strings.Join(c.Args(), " "))
  335. }
  336. if !rsp.Code.IsOK() {
  337. fmt.Printf("-> code: %s\n", rsp.Code.String())
  338. }
  339. if len(rsp.Data) != 0 {
  340. fmt.Printf("-> data: %s\n", rsp.Data)
  341. fmt.Printf("-> data.hex: %X\n", rsp.Data)
  342. }
  343. if rsp.Log != "" {
  344. fmt.Printf("-> log: %s\n", rsp.Log)
  345. }
  346. if rsp.Query != nil {
  347. fmt.Printf("-> height: %d\n", rsp.Query.Height)
  348. if rsp.Query.Key != nil {
  349. fmt.Printf("-> key: %s\n", rsp.Query.Key)
  350. fmt.Printf("-> key.hex: %X\n", rsp.Query.Key)
  351. }
  352. if rsp.Query.Value != nil {
  353. fmt.Printf("-> value: %s\n", rsp.Query.Value)
  354. fmt.Printf("-> value.hex: %X\n", rsp.Query.Value)
  355. }
  356. if rsp.Query.Proof != nil {
  357. fmt.Printf("-> proof: %X\n", rsp.Query.Proof)
  358. }
  359. }
  360. if verbose {
  361. fmt.Println("")
  362. }
  363. }
  364. // NOTE: s is interpreted as a string unless prefixed with 0x
  365. func stringOrHexToBytes(s string) ([]byte, error) {
  366. if len(s) > 2 && strings.ToLower(s[:2]) == "0x" {
  367. b, err := hex.DecodeString(s[2:])
  368. if err != nil {
  369. err = fmt.Errorf("Error decoding hex argument: %s", err.Error())
  370. return nil, err
  371. }
  372. return b, nil
  373. }
  374. if !strings.HasPrefix(s, "\"") || !strings.HasSuffix(s, "\"") {
  375. err := fmt.Errorf("Invalid string arg: \"%s\". Must be quoted or a \"0x\"-prefixed hex string", s)
  376. return nil, err
  377. }
  378. return []byte(s[1 : len(s)-1]), nil
  379. }