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 }