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.

745 lines
18 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
8 years ago
7 years ago
9 years ago
7 years ago
7 years ago
9 years ago
9 years ago
7 years ago
7 years ago
7 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/spf13/cobra"
  11. cmn "github.com/tendermint/tendermint/libs/common"
  12. "github.com/tendermint/tendermint/libs/log"
  13. abcicli "github.com/tendermint/tendermint/abci/client"
  14. "github.com/tendermint/tendermint/abci/example/code"
  15. "github.com/tendermint/tendermint/abci/example/counter"
  16. "github.com/tendermint/tendermint/abci/example/kvstore"
  17. "github.com/tendermint/tendermint/abci/server"
  18. servertest "github.com/tendermint/tendermint/abci/tests/server"
  19. "github.com/tendermint/tendermint/abci/types"
  20. "github.com/tendermint/tendermint/abci/version"
  21. "github.com/tendermint/tendermint/crypto/merkle"
  22. )
  23. // client is a global variable so it can be reused by the console
  24. var (
  25. client abcicli.Client
  26. logger log.Logger
  27. )
  28. // flags
  29. var (
  30. // global
  31. flagAddress string
  32. flagAbci string
  33. flagVerbose bool // for the println output
  34. flagLogLevel string // for the logger
  35. // query
  36. flagPath string
  37. flagHeight int
  38. flagProve bool
  39. // counter
  40. flagSerial bool
  41. // kvstore
  42. flagPersist string
  43. )
  44. var RootCmd = &cobra.Command{
  45. Use: "abci-cli",
  46. Short: "the ABCI CLI tool wraps an ABCI client",
  47. Long: "the ABCI CLI tool wraps an ABCI client and is used for testing ABCI servers",
  48. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  49. switch cmd.Use {
  50. case "counter", "kvstore": // for the examples apps, don't pre-run
  51. return nil
  52. case "version": // skip running for version command
  53. return nil
  54. }
  55. if logger == nil {
  56. allowLevel, err := log.AllowLevel(flagLogLevel)
  57. if err != nil {
  58. return err
  59. }
  60. logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), allowLevel)
  61. }
  62. if client == nil {
  63. var err error
  64. client, err = abcicli.NewClient(flagAddress, flagAbci, false)
  65. if err != nil {
  66. return err
  67. }
  68. client.SetLogger(logger.With("module", "abci-client"))
  69. if err := client.Start(); err != nil {
  70. return err
  71. }
  72. }
  73. return nil
  74. },
  75. }
  76. // Structure for data passed to print response.
  77. type response struct {
  78. // generic abci response
  79. Data []byte
  80. Code uint32
  81. Info string
  82. Log string
  83. Query *queryResponse
  84. }
  85. type queryResponse struct {
  86. Key []byte
  87. Value []byte
  88. Height int64
  89. Proof *merkle.Proof
  90. }
  91. func Execute() error {
  92. addGlobalFlags()
  93. addCommands()
  94. return RootCmd.Execute()
  95. }
  96. func addGlobalFlags() {
  97. RootCmd.PersistentFlags().StringVarP(&flagAddress,
  98. "address",
  99. "",
  100. "tcp://0.0.0.0:26658",
  101. "address of application socket")
  102. RootCmd.PersistentFlags().StringVarP(&flagAbci, "abci", "", "socket", "either socket or grpc")
  103. RootCmd.PersistentFlags().BoolVarP(&flagVerbose,
  104. "verbose",
  105. "v",
  106. false,
  107. "print the command and results as if it were a console session")
  108. RootCmd.PersistentFlags().StringVarP(&flagLogLevel, "log_level", "", "debug", "set the logger level")
  109. }
  110. func addQueryFlags() {
  111. queryCmd.PersistentFlags().StringVarP(&flagPath, "path", "", "/store", "path to prefix query with")
  112. queryCmd.PersistentFlags().IntVarP(&flagHeight, "height", "", 0, "height to query the blockchain at")
  113. queryCmd.PersistentFlags().BoolVarP(&flagProve,
  114. "prove",
  115. "",
  116. false,
  117. "whether or not to return a merkle proof of the query result")
  118. }
  119. func addCounterFlags() {
  120. counterCmd.PersistentFlags().BoolVarP(&flagSerial, "serial", "", false, "enforce incrementing (serial) transactions")
  121. }
  122. func addKVStoreFlags() {
  123. kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database")
  124. }
  125. func addCommands() {
  126. RootCmd.AddCommand(batchCmd)
  127. RootCmd.AddCommand(consoleCmd)
  128. RootCmd.AddCommand(echoCmd)
  129. RootCmd.AddCommand(infoCmd)
  130. RootCmd.AddCommand(setOptionCmd)
  131. RootCmd.AddCommand(deliverTxCmd)
  132. RootCmd.AddCommand(checkTxCmd)
  133. RootCmd.AddCommand(commitCmd)
  134. RootCmd.AddCommand(versionCmd)
  135. RootCmd.AddCommand(testCmd)
  136. addQueryFlags()
  137. RootCmd.AddCommand(queryCmd)
  138. // examples
  139. addCounterFlags()
  140. RootCmd.AddCommand(counterCmd)
  141. addKVStoreFlags()
  142. RootCmd.AddCommand(kvstoreCmd)
  143. }
  144. var batchCmd = &cobra.Command{
  145. Use: "batch",
  146. Short: "run a batch of abci commands against an application",
  147. Long: `run a batch of abci commands against an application
  148. This command is run by piping in a file containing a series of commands
  149. you'd like to run:
  150. abci-cli batch < example.file
  151. where example.file looks something like:
  152. set_option serial on
  153. check_tx 0x00
  154. check_tx 0xff
  155. deliver_tx 0x00
  156. check_tx 0x00
  157. deliver_tx 0x01
  158. deliver_tx 0x04
  159. info
  160. `,
  161. Args: cobra.ExactArgs(0),
  162. RunE: cmdBatch,
  163. }
  164. var consoleCmd = &cobra.Command{
  165. Use: "console",
  166. Short: "start an interactive ABCI console for multiple commands",
  167. Long: `start an interactive ABCI console for multiple commands
  168. This command opens an interactive console for running any of the other commands
  169. without opening a new connection each time
  170. `,
  171. Args: cobra.ExactArgs(0),
  172. ValidArgs: []string{"echo", "info", "set_option", "deliver_tx", "check_tx", "commit", "query"},
  173. RunE: cmdConsole,
  174. }
  175. var echoCmd = &cobra.Command{
  176. Use: "echo",
  177. Short: "have the application echo a message",
  178. Long: "have the application echo a message",
  179. Args: cobra.ExactArgs(1),
  180. RunE: cmdEcho,
  181. }
  182. var infoCmd = &cobra.Command{
  183. Use: "info",
  184. Short: "get some info about the application",
  185. Long: "get some info about the application",
  186. Args: cobra.ExactArgs(0),
  187. RunE: cmdInfo,
  188. }
  189. var setOptionCmd = &cobra.Command{
  190. Use: "set_option",
  191. Short: "set an option on the application",
  192. Long: "set an option on the application",
  193. Args: cobra.ExactArgs(2),
  194. RunE: cmdSetOption,
  195. }
  196. var deliverTxCmd = &cobra.Command{
  197. Use: "deliver_tx",
  198. Short: "deliver a new transaction to the application",
  199. Long: "deliver a new transaction to the application",
  200. Args: cobra.ExactArgs(1),
  201. RunE: cmdDeliverTx,
  202. }
  203. var checkTxCmd = &cobra.Command{
  204. Use: "check_tx",
  205. Short: "validate a transaction",
  206. Long: "validate a transaction",
  207. Args: cobra.ExactArgs(1),
  208. RunE: cmdCheckTx,
  209. }
  210. var commitCmd = &cobra.Command{
  211. Use: "commit",
  212. Short: "commit the application state and return the Merkle root hash",
  213. Long: "commit the application state and return the Merkle root hash",
  214. Args: cobra.ExactArgs(0),
  215. RunE: cmdCommit,
  216. }
  217. var versionCmd = &cobra.Command{
  218. Use: "version",
  219. Short: "print ABCI console version",
  220. Long: "print ABCI console version",
  221. Args: cobra.ExactArgs(0),
  222. RunE: func(cmd *cobra.Command, args []string) error {
  223. fmt.Println(version.Version)
  224. return nil
  225. },
  226. }
  227. var queryCmd = &cobra.Command{
  228. Use: "query",
  229. Short: "query the application state",
  230. Long: "query the application state",
  231. Args: cobra.ExactArgs(1),
  232. RunE: cmdQuery,
  233. }
  234. var counterCmd = &cobra.Command{
  235. Use: "counter",
  236. Short: "ABCI demo example",
  237. Long: "ABCI demo example",
  238. Args: cobra.ExactArgs(0),
  239. RunE: cmdCounter,
  240. }
  241. var kvstoreCmd = &cobra.Command{
  242. Use: "kvstore",
  243. Short: "ABCI demo example",
  244. Long: "ABCI demo example",
  245. Args: cobra.ExactArgs(0),
  246. RunE: cmdKVStore,
  247. }
  248. var testCmd = &cobra.Command{
  249. Use: "test",
  250. Short: "run integration tests",
  251. Long: "run integration tests",
  252. Args: cobra.ExactArgs(0),
  253. RunE: cmdTest,
  254. }
  255. // Generates new Args array based off of previous call args to maintain flag persistence
  256. func persistentArgs(line []byte) []string {
  257. // generate the arguments to run from original os.Args
  258. // to maintain flag arguments
  259. args := os.Args
  260. args = args[:len(args)-1] // remove the previous command argument
  261. if len(line) > 0 { // prevents introduction of extra space leading to argument parse errors
  262. args = append(args, strings.Split(string(line), " ")...)
  263. }
  264. return args
  265. }
  266. //--------------------------------------------------------------------------------
  267. func compose(fs []func() error) error {
  268. if len(fs) == 0 {
  269. return nil
  270. } else {
  271. err := fs[0]()
  272. if err == nil {
  273. return compose(fs[1:])
  274. } else {
  275. return err
  276. }
  277. }
  278. }
  279. func cmdTest(cmd *cobra.Command, args []string) error {
  280. return compose(
  281. []func() error{
  282. func() error { return servertest.InitChain(client) },
  283. func() error { return servertest.SetOption(client, "serial", "on") },
  284. func() error { return servertest.Commit(client, nil) },
  285. func() error { return servertest.DeliverTx(client, []byte("abc"), code.CodeTypeBadNonce, nil) },
  286. func() error { return servertest.Commit(client, nil) },
  287. func() error { return servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeOK, nil) },
  288. func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 1}) },
  289. func() error { return servertest.DeliverTx(client, []byte{0x00}, code.CodeTypeBadNonce, nil) },
  290. func() error { return servertest.DeliverTx(client, []byte{0x01}, code.CodeTypeOK, nil) },
  291. func() error { return servertest.DeliverTx(client, []byte{0x00, 0x02}, code.CodeTypeOK, nil) },
  292. func() error { return servertest.DeliverTx(client, []byte{0x00, 0x03}, code.CodeTypeOK, nil) },
  293. func() error { return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x04}, code.CodeTypeOK, nil) },
  294. func() error {
  295. return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x06}, code.CodeTypeBadNonce, nil)
  296. },
  297. func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5}) },
  298. })
  299. }
  300. func cmdBatch(cmd *cobra.Command, args []string) error {
  301. bufReader := bufio.NewReader(os.Stdin)
  302. LOOP:
  303. for {
  304. line, more, err := bufReader.ReadLine()
  305. switch {
  306. case more:
  307. return errors.New("input line is too long")
  308. case err == io.EOF:
  309. break LOOP
  310. case len(line) == 0:
  311. continue
  312. case err != nil:
  313. return err
  314. }
  315. cmdArgs := persistentArgs(line)
  316. if err := muxOnCommands(cmd, cmdArgs); err != nil {
  317. return err
  318. }
  319. fmt.Println()
  320. }
  321. return nil
  322. }
  323. func cmdConsole(cmd *cobra.Command, args []string) error {
  324. for {
  325. fmt.Printf("> ")
  326. bufReader := bufio.NewReader(os.Stdin)
  327. line, more, err := bufReader.ReadLine()
  328. if more {
  329. return errors.New("input is too long")
  330. } else if err != nil {
  331. return err
  332. }
  333. pArgs := persistentArgs(line)
  334. if err := muxOnCommands(cmd, pArgs); err != nil {
  335. return err
  336. }
  337. }
  338. }
  339. func muxOnCommands(cmd *cobra.Command, pArgs []string) error {
  340. if len(pArgs) < 2 {
  341. return errors.New("expecting persistent args of the form: abci-cli [command] <...>")
  342. }
  343. // TODO: this parsing is fragile
  344. args := []string{}
  345. for i := 0; i < len(pArgs); i++ {
  346. arg := pArgs[i]
  347. // check for flags
  348. if strings.HasPrefix(arg, "-") {
  349. // if it has an equal, we can just skip
  350. if strings.Contains(arg, "=") {
  351. continue
  352. }
  353. // if its a boolean, we can just skip
  354. _, err := cmd.Flags().GetBool(strings.TrimLeft(arg, "-"))
  355. if err == nil {
  356. continue
  357. }
  358. // otherwise, we need to skip the next one too
  359. i++
  360. continue
  361. }
  362. // append the actual arg
  363. args = append(args, arg)
  364. }
  365. var subCommand string
  366. var actualArgs []string
  367. if len(args) > 1 {
  368. subCommand = args[1]
  369. }
  370. if len(args) > 2 {
  371. actualArgs = args[2:]
  372. }
  373. cmd.Use = subCommand // for later print statements ...
  374. switch strings.ToLower(subCommand) {
  375. case "check_tx":
  376. return cmdCheckTx(cmd, actualArgs)
  377. case "commit":
  378. return cmdCommit(cmd, actualArgs)
  379. case "deliver_tx":
  380. return cmdDeliverTx(cmd, actualArgs)
  381. case "echo":
  382. return cmdEcho(cmd, actualArgs)
  383. case "info":
  384. return cmdInfo(cmd, actualArgs)
  385. case "query":
  386. return cmdQuery(cmd, actualArgs)
  387. case "set_option":
  388. return cmdSetOption(cmd, actualArgs)
  389. default:
  390. return cmdUnimplemented(cmd, pArgs)
  391. }
  392. }
  393. func cmdUnimplemented(cmd *cobra.Command, args []string) error {
  394. msg := "unimplemented command"
  395. if len(args) > 0 {
  396. msg += fmt.Sprintf(" args: [%s]", strings.Join(args, " "))
  397. }
  398. printResponse(cmd, args, response{
  399. Code: codeBad,
  400. Log: msg,
  401. })
  402. fmt.Println("Available commands:")
  403. fmt.Printf("%s: %s\n", echoCmd.Use, echoCmd.Short)
  404. fmt.Printf("%s: %s\n", infoCmd.Use, infoCmd.Short)
  405. fmt.Printf("%s: %s\n", checkTxCmd.Use, checkTxCmd.Short)
  406. fmt.Printf("%s: %s\n", deliverTxCmd.Use, deliverTxCmd.Short)
  407. fmt.Printf("%s: %s\n", queryCmd.Use, queryCmd.Short)
  408. fmt.Printf("%s: %s\n", commitCmd.Use, commitCmd.Short)
  409. fmt.Printf("%s: %s\n", setOptionCmd.Use, setOptionCmd.Short)
  410. fmt.Println("Use \"[command] --help\" for more information about a command.")
  411. return nil
  412. }
  413. // Have the application echo a message
  414. func cmdEcho(cmd *cobra.Command, args []string) error {
  415. msg := ""
  416. if len(args) > 0 {
  417. msg = args[0]
  418. }
  419. res, err := client.EchoSync(msg)
  420. if err != nil {
  421. return err
  422. }
  423. printResponse(cmd, args, response{
  424. Data: []byte(res.Message),
  425. })
  426. return nil
  427. }
  428. // Get some info from the application
  429. func cmdInfo(cmd *cobra.Command, args []string) error {
  430. var version string
  431. if len(args) == 1 {
  432. version = args[0]
  433. }
  434. res, err := client.InfoSync(types.RequestInfo{Version: version})
  435. if err != nil {
  436. return err
  437. }
  438. printResponse(cmd, args, response{
  439. Data: []byte(res.Data),
  440. })
  441. return nil
  442. }
  443. const codeBad uint32 = 10
  444. // Set an option on the application
  445. func cmdSetOption(cmd *cobra.Command, args []string) error {
  446. if len(args) < 2 {
  447. printResponse(cmd, args, response{
  448. Code: codeBad,
  449. Log: "want at least arguments of the form: <key> <value>",
  450. })
  451. return nil
  452. }
  453. key, val := args[0], args[1]
  454. _, err := client.SetOptionSync(types.RequestSetOption{Key: key, Value: val})
  455. if err != nil {
  456. return err
  457. }
  458. printResponse(cmd, args, response{Log: "OK (SetOption doesn't return anything.)"}) // NOTE: Nothing to show...
  459. return nil
  460. }
  461. // Append a new tx to application
  462. func cmdDeliverTx(cmd *cobra.Command, args []string) error {
  463. if len(args) == 0 {
  464. printResponse(cmd, args, response{
  465. Code: codeBad,
  466. Log: "want the tx",
  467. })
  468. return nil
  469. }
  470. txBytes, err := stringOrHexToBytes(args[0])
  471. if err != nil {
  472. return err
  473. }
  474. res, err := client.DeliverTxSync(types.RequestDeliverTx{Tx: txBytes})
  475. if err != nil {
  476. return err
  477. }
  478. printResponse(cmd, args, response{
  479. Code: res.Code,
  480. Data: res.Data,
  481. Info: res.Info,
  482. Log: res.Log,
  483. })
  484. return nil
  485. }
  486. // Validate a tx
  487. func cmdCheckTx(cmd *cobra.Command, args []string) error {
  488. if len(args) == 0 {
  489. printResponse(cmd, args, response{
  490. Code: codeBad,
  491. Info: "want the tx",
  492. })
  493. return nil
  494. }
  495. txBytes, err := stringOrHexToBytes(args[0])
  496. if err != nil {
  497. return err
  498. }
  499. res, err := client.CheckTxSync(types.RequestCheckTx{Tx: txBytes})
  500. if err != nil {
  501. return err
  502. }
  503. printResponse(cmd, args, response{
  504. Code: res.Code,
  505. Data: res.Data,
  506. Info: res.Info,
  507. Log: res.Log,
  508. })
  509. return nil
  510. }
  511. // Get application Merkle root hash
  512. func cmdCommit(cmd *cobra.Command, args []string) error {
  513. res, err := client.CommitSync()
  514. if err != nil {
  515. return err
  516. }
  517. printResponse(cmd, args, response{
  518. Data: res.Data,
  519. })
  520. return nil
  521. }
  522. // Query application state
  523. func cmdQuery(cmd *cobra.Command, args []string) error {
  524. if len(args) == 0 {
  525. printResponse(cmd, args, response{
  526. Code: codeBad,
  527. Info: "want the query",
  528. Log: "",
  529. })
  530. return nil
  531. }
  532. queryBytes, err := stringOrHexToBytes(args[0])
  533. if err != nil {
  534. return err
  535. }
  536. resQuery, err := client.QuerySync(types.RequestQuery{
  537. Data: queryBytes,
  538. Path: flagPath,
  539. Height: int64(flagHeight),
  540. Prove: flagProve,
  541. })
  542. if err != nil {
  543. return err
  544. }
  545. printResponse(cmd, args, response{
  546. Code: resQuery.Code,
  547. Info: resQuery.Info,
  548. Log: resQuery.Log,
  549. Query: &queryResponse{
  550. Key: resQuery.Key,
  551. Value: resQuery.Value,
  552. Height: resQuery.Height,
  553. Proof: resQuery.Proof,
  554. },
  555. })
  556. return nil
  557. }
  558. func cmdCounter(cmd *cobra.Command, args []string) error {
  559. app := counter.NewCounterApplication(flagSerial)
  560. logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
  561. // Start the listener
  562. srv, err := server.NewServer(flagAddress, flagAbci, app)
  563. if err != nil {
  564. return err
  565. }
  566. srv.SetLogger(logger.With("module", "abci-server"))
  567. if err := srv.Start(); err != nil {
  568. return err
  569. }
  570. // Stop upon receiving SIGTERM or CTRL-C.
  571. cmn.TrapSignal(logger, func() {
  572. // Cleanup
  573. srv.Stop()
  574. })
  575. // Run forever.
  576. select {}
  577. }
  578. func cmdKVStore(cmd *cobra.Command, args []string) error {
  579. logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
  580. // Create the application - in memory or persisted to disk
  581. var app types.Application
  582. if flagPersist == "" {
  583. app = kvstore.NewKVStoreApplication()
  584. } else {
  585. app = kvstore.NewPersistentKVStoreApplication(flagPersist)
  586. app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore"))
  587. }
  588. // Start the listener
  589. srv, err := server.NewServer(flagAddress, flagAbci, app)
  590. if err != nil {
  591. return err
  592. }
  593. srv.SetLogger(logger.With("module", "abci-server"))
  594. if err := srv.Start(); err != nil {
  595. return err
  596. }
  597. // Stop upon receiving SIGTERM or CTRL-C.
  598. cmn.TrapSignal(logger, func() {
  599. // Cleanup
  600. srv.Stop()
  601. })
  602. // Run forever.
  603. select {}
  604. }
  605. //--------------------------------------------------------------------------------
  606. func printResponse(cmd *cobra.Command, args []string, rsp response) {
  607. if flagVerbose {
  608. fmt.Println(">", cmd.Use, strings.Join(args, " "))
  609. }
  610. // Always print the status code.
  611. if rsp.Code == types.CodeTypeOK {
  612. fmt.Printf("-> code: OK\n")
  613. } else {
  614. fmt.Printf("-> code: %d\n", rsp.Code)
  615. }
  616. if len(rsp.Data) != 0 {
  617. // Do no print this line when using the commit command
  618. // because the string comes out as gibberish
  619. if cmd.Use != "commit" {
  620. fmt.Printf("-> data: %s\n", rsp.Data)
  621. }
  622. fmt.Printf("-> data.hex: 0x%X\n", rsp.Data)
  623. }
  624. if rsp.Log != "" {
  625. fmt.Printf("-> log: %s\n", rsp.Log)
  626. }
  627. if rsp.Query != nil {
  628. fmt.Printf("-> height: %d\n", rsp.Query.Height)
  629. if rsp.Query.Key != nil {
  630. fmt.Printf("-> key: %s\n", rsp.Query.Key)
  631. fmt.Printf("-> key.hex: %X\n", rsp.Query.Key)
  632. }
  633. if rsp.Query.Value != nil {
  634. fmt.Printf("-> value: %s\n", rsp.Query.Value)
  635. fmt.Printf("-> value.hex: %X\n", rsp.Query.Value)
  636. }
  637. if rsp.Query.Proof != nil {
  638. fmt.Printf("-> proof: %#v\n", rsp.Query.Proof)
  639. }
  640. }
  641. }
  642. // NOTE: s is interpreted as a string unless prefixed with 0x
  643. func stringOrHexToBytes(s string) ([]byte, error) {
  644. if len(s) > 2 && strings.ToLower(s[:2]) == "0x" {
  645. b, err := hex.DecodeString(s[2:])
  646. if err != nil {
  647. err = fmt.Errorf("error decoding hex argument: %s", err.Error())
  648. return nil, err
  649. }
  650. return b, nil
  651. }
  652. if !strings.HasPrefix(s, "\"") || !strings.HasSuffix(s, "\"") {
  653. err := fmt.Errorf("invalid string arg: \"%s\". Must be quoted or a \"0x\"-prefixed hex string", s)
  654. return nil, err
  655. }
  656. return []byte(s[1 : len(s)-1]), nil
  657. }