github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/abci/cmd/abci-cli/abci-cli.go (about)

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