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