diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index fa9643239..9e1b5c890 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -15,7 +15,9 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [abci/counter] \#6684 Delete counter example app - [txResults] \#9175 Remove `gas_used` & `gas_wanted` from being merkelized in the lastresulthash in the header - [abci] \#5783 Make length delimiter encoding consistent (`uint64`) between ABCI and P2P wire-level protocols - - [abci] \##9145 Removes Response/Request `SetOption` from ABCI + - [abci] \#9145 Removes Response/Request `SetOption` from ABCI + - [abci] \#8656 Added cli command for `PrepareProposal`. (@jmalicevic) + - [abci] \#8901 Added cli command for `ProcessProposal`. (@hvanz) - P2P Protocol diff --git a/abci/cmd/abci-cli/abci-cli.go b/abci/cmd/abci-cli/abci-cli.go index 5cca33421..3888b3637 100644 --- a/abci/cmd/abci-cli/abci-cli.go +++ b/abci/cmd/abci-cli/abci-cli.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "bytes" "encoding/hex" "errors" "fmt" @@ -83,10 +84,11 @@ var RootCmd = &cobra.Command{ // Structure for data passed to print response. type response struct { // generic abci response - Data []byte - Code uint32 - Info string - Log string + Data []byte + Code uint32 + Info string + Log string + Status int32 Query *queryResponse } @@ -143,6 +145,8 @@ func addCommands() { RootCmd.AddCommand(commitCmd) RootCmd.AddCommand(versionCmd) RootCmd.AddCommand(testCmd) + RootCmd.AddCommand(prepareProposalCmd) + RootCmd.AddCommand(processProposalCmd) addQueryFlags() RootCmd.AddCommand(queryCmd) @@ -184,7 +188,7 @@ This command opens an interactive console for running any of the other commands without opening a new connection each time `, Args: cobra.ExactArgs(0), - ValidArgs: []string{"echo", "info", "deliver_tx", "check_tx", "commit", "query"}, + ValidArgs: []string{"echo", "info", "deliver_tx", "check_tx", "prepare_proposal", "process_proposal", "commit", "query"}, RunE: cmdConsole, } @@ -238,6 +242,22 @@ var versionCmd = &cobra.Command{ }, } +var prepareProposalCmd = &cobra.Command{ + Use: "prepare_proposal", + Short: "prepare proposal", + Long: "prepare proposal", + Args: cobra.MinimumNArgs(0), + RunE: cmdPrepareProposal, +} + +var processProposalCmd = &cobra.Command{ + Use: "process_proposal", + Short: "process proposal", + Long: "process proposal", + Args: cobra.MinimumNArgs(0), + RunE: cmdProcessProposal, +} + var queryCmd = &cobra.Command{ Use: "query", Short: "query the application state", @@ -309,6 +329,18 @@ func cmdTest(cmd *cobra.Command, args []string) error { return servertest.DeliverTx(client, []byte{0x00, 0x00, 0x06}, code.CodeTypeBadNonce, nil) }, func() error { return servertest.Commit(client, []byte{0, 0, 0, 0, 0, 0, 0, 5}) }, + func() error { + return servertest.PrepareProposal(client, [][]byte{ + {0x01}, + }, []types.TxRecord_TxAction{ + types.TxRecord_UNMODIFIED, + }, nil) + }, + func() error { + return servertest.ProcessProposal(client, [][]byte{ + {0x01}, + }, types.ResponseProcessProposal_ACCEPT) + }, }) } @@ -409,6 +441,10 @@ func muxOnCommands(cmd *cobra.Command, pArgs []string) error { return cmdInfo(cmd, actualArgs) case "query": return cmdQuery(cmd, actualArgs) + case "prepare_proposal": + return cmdPrepareProposal(cmd, actualArgs) + case "process_proposal": + return cmdProcessProposal(cmd, actualArgs) default: return cmdUnimplemented(cmd, pArgs) } @@ -573,17 +609,95 @@ func cmdQuery(cmd *cobra.Command, args []string) error { return nil } +func inTxArray(txByteArray [][]byte, tx []byte) bool { + for _, txTmp := range txByteArray { + if bytes.Equal(txTmp, tx) { + return true + } + + } + return false +} + +func cmdPrepareProposal(cmd *cobra.Command, args []string) error { + txsBytesArray := make([][]byte, len(args)) + + for i, arg := range args { + txBytes, err := stringOrHexToBytes(arg) + if err != nil { + return err + } + txsBytesArray[i] = txBytes + } + + res, err := client.PrepareProposalSync(types.RequestPrepareProposal{ + Txs: txsBytesArray, + // kvstore has to have this parameter in order not to reject a tx as the default value is 0 + MaxTxBytes: 65536, + }) + if err != nil { + return err + } + resps := make([]response, 0, len(res.TxRecords)+1) + for _, tx := range res.TxRecords { + existingTx := inTxArray(txsBytesArray, tx.Tx) + if tx.Action == types.TxRecord_UNKNOWN || + (existingTx && tx.Action == types.TxRecord_ADDED) || + (!existingTx && (tx.Action == types.TxRecord_UNMODIFIED || tx.Action == types.TxRecord_REMOVED)) { + resps = append(resps, response{ + Code: codeBad, + Log: "Failed. Tx: " + string(tx.GetTx()) + " action: " + tx.Action.String(), + }) + } else { + resps = append(resps, response{ + Code: code.CodeTypeOK, + Log: "Succeeded. Tx: " + string(tx.Tx) + " action: " + tx.Action.String(), + }) + } + } + + printResponse(cmd, args, resps...) + return nil +} + +func cmdProcessProposal(cmd *cobra.Command, args []string) error { + txsBytesArray := make([][]byte, len(args)) + + for i, arg := range args { + txBytes, err := stringOrHexToBytes(arg) + if err != nil { + return err + } + txsBytesArray[i] = txBytes + } + + res, err := client.ProcessProposalSync(types.RequestProcessProposal{ + Txs: txsBytesArray, + }) + if err != nil { + return err + } + + printResponse(cmd, args, response{ + Status: int32(res.Status), + }) + return nil +} + func cmdKVStore(cmd *cobra.Command, args []string) error { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) // Create the application - in memory or persisted to disk var app types.Application if flagPersist == "" { - app = kvstore.NewApplication() - } else { - app = kvstore.NewPersistentKVStoreApplication(flagPersist) - app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) + var err error + flagPersist, err = os.MkdirTemp("", "persistent_kvstore_tmp") + if err != nil { + return err + } } + app = kvstore.NewPersistentKVStoreApplication(flagPersist) + app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) // Start the listener srv, err := server.NewServer(flagAddress, flagAbci, app) @@ -609,44 +723,49 @@ func cmdKVStore(cmd *cobra.Command, args []string) error { //-------------------------------------------------------------------------------- -func printResponse(cmd *cobra.Command, args []string, rsp response) { +func printResponse(cmd *cobra.Command, args []string, rsps ...response) { if flagVerbose { fmt.Println(">", cmd.Use, strings.Join(args, " ")) } - // Always print the status code. - if rsp.Code == types.CodeTypeOK { - fmt.Printf("-> code: OK\n") - } else { - fmt.Printf("-> code: %d\n", rsp.Code) + for _, rsp := range rsps { + // Always print the status code. + if rsp.Code == types.CodeTypeOK { + fmt.Printf("-> code: OK\n") + } else { + fmt.Printf("-> code: %d\n", rsp.Code) - } + } - if len(rsp.Data) != 0 { - // Do no print this line when using the commit command - // because the string comes out as gibberish - if cmd.Use != "commit" { - fmt.Printf("-> data: %s\n", rsp.Data) + if len(rsp.Data) != 0 { + // Do no print this line when using the commit command + // because the string comes out as gibberish + if cmd.Use != "commit" { + fmt.Printf("-> data: %s\n", rsp.Data) + } + fmt.Printf("-> data.hex: 0x%X\n", rsp.Data) + } + if rsp.Log != "" { + fmt.Printf("-> log: %s\n", rsp.Log) + } + if cmd.Use == "process_proposal" { + fmt.Printf("-> status: %s\n", types.ResponseProcessProposal_ProposalStatus_name[rsp.Status]) } - fmt.Printf("-> data.hex: 0x%X\n", rsp.Data) - } - if rsp.Log != "" { - fmt.Printf("-> log: %s\n", rsp.Log) - } - if rsp.Query != nil { - fmt.Printf("-> height: %d\n", rsp.Query.Height) - if rsp.Query.Key != nil { - fmt.Printf("-> key: %s\n", rsp.Query.Key) - fmt.Printf("-> key.hex: %X\n", rsp.Query.Key) - } - if rsp.Query.Value != nil { - fmt.Printf("-> value: %s\n", rsp.Query.Value) - fmt.Printf("-> value.hex: %X\n", rsp.Query.Value) - } - if rsp.Query.ProofOps != nil { - fmt.Printf("-> proof: %#v\n", rsp.Query.ProofOps) + if rsp.Query != nil { + fmt.Printf("-> height: %d\n", rsp.Query.Height) + if rsp.Query.Key != nil { + fmt.Printf("-> key: %s\n", rsp.Query.Key) + fmt.Printf("-> key.hex: %X\n", rsp.Query.Key) + } + if rsp.Query.Value != nil { + fmt.Printf("-> value: %s\n", rsp.Query.Value) + fmt.Printf("-> value.hex: %X\n", rsp.Query.Value) + } + if rsp.Query.ProofOps != nil { + fmt.Printf("-> proof: %#v\n", rsp.Query.ProofOps) + } } } } diff --git a/abci/example/kvstore/persistent_kvstore.go b/abci/example/kvstore/persistent_kvstore.go index d8c932201..5e2d35d3a 100644 --- a/abci/example/kvstore/persistent_kvstore.go +++ b/abci/example/kvstore/persistent_kvstore.go @@ -178,7 +178,7 @@ func (app *PersistentKVStoreApplication) PrepareProposal( func (app *PersistentKVStoreApplication) ProcessProposal( req types.RequestProcessProposal) types.ResponseProcessProposal { for _, tx := range req.Txs { - if len(tx) == 0 { + if len(tx) == 0 || isPrepareTx(tx) { return types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT} } } diff --git a/abci/tests/server/client.go b/abci/tests/server/client.go index 1a11a9380..0de84471a 100644 --- a/abci/tests/server/client.go +++ b/abci/tests/server/client.go @@ -65,6 +65,32 @@ func DeliverTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp [] return nil } +func PrepareProposal(client abcicli.Client, txBytes [][]byte, codeExp []types.TxRecord_TxAction, dataExp []byte) error { + res, _ := client.PrepareProposalSync(types.RequestPrepareProposal{Txs: txBytes}) + for i, tx := range res.TxRecords { + if tx.Action != codeExp[i] { + fmt.Println("Failed test: PrepareProposal") + fmt.Printf("PrepareProposal response code was unexpected. Got %v expected %v.", + tx.Action, codeExp) + return errors.New("PrepareProposal error") + } + } + fmt.Println("Passed test: PrepareProposal") + return nil +} + +func ProcessProposal(client abcicli.Client, txBytes [][]byte, statusExp types.ResponseProcessProposal_ProposalStatus) error { + res, _ := client.ProcessProposalSync(types.RequestProcessProposal{Txs: txBytes}) + if res.Status != statusExp { + fmt.Println("Failed test: ProcessProposal") + fmt.Printf("ProcessProposal response status was unexpected. Got %v expected %v.", + res.Status, statusExp) + return errors.New("ProcessProposal error") + } + fmt.Println("Passed test: ProcessProposal") + return nil +} + func CheckTx(client abcicli.Client, txBytes []byte, codeExp uint32, dataExp []byte) error { res, _ := client.CheckTxSync(types.RequestCheckTx{Tx: txBytes}) code, data, log := res.Code, res.Data, res.Log diff --git a/abci/tests/test_cli/ex1.abci b/abci/tests/test_cli/ex1.abci index e909266ec..f9817928a 100644 --- a/abci/tests/test_cli/ex1.abci +++ b/abci/tests/test_cli/ex1.abci @@ -1,5 +1,7 @@ echo hello info +prepare_proposal "abc" +process_proposal "abc" commit deliver_tx "abc" info @@ -8,3 +10,9 @@ query "abc" deliver_tx "def=xyz" commit query "def" +prepare_proposal "preparedef" +process_proposal "def" +process_proposal "preparedef" +prepare_proposal +process_proposal +commit diff --git a/abci/tests/test_cli/ex1.abci.out b/abci/tests/test_cli/ex1.abci.out index 9e702b5ce..2cc1ad7c1 100644 --- a/abci/tests/test_cli/ex1.abci.out +++ b/abci/tests/test_cli/ex1.abci.out @@ -8,6 +8,14 @@ -> data: {"size":0} -> data.hex: 0x7B2273697A65223A307D +> prepare_proposal "abc" +-> code: OK +-> log: Succeeded. Tx: abc action: UNMODIFIED + +> process_proposal "abc" +-> code: OK +-> status: ACCEPT + > commit -> code: OK -> data.hex: 0x0000000000000000 @@ -49,3 +57,27 @@ -> value: xyz -> value.hex: 78797A +> prepare_proposal "preparedef" +-> code: OK +-> log: Succeeded. Tx: def action: ADDED +-> code: OK +-> log: Succeeded. Tx: preparedef action: REMOVED + +> process_proposal "def" +-> code: OK +-> status: ACCEPT + +> process_proposal "preparedef" +-> code: OK +-> status: REJECT + +> prepare_proposal + +> process_proposal +-> code: OK +-> status: ACCEPT + +> commit +-> code: OK +-> data.hex: 0x0400000000000000 + diff --git a/abci/tests/test_cli/ex2.abci b/abci/tests/test_cli/ex2.abci index 3b435f22a..965ca842c 100644 --- a/abci/tests/test_cli/ex2.abci +++ b/abci/tests/test_cli/ex2.abci @@ -1,4 +1,3 @@ -set_option serial on check_tx 0x00 check_tx 0xff deliver_tx 0x00 diff --git a/abci/tests/test_cli/ex2.abci.out b/abci/tests/test_cli/ex2.abci.out index af6589158..a2f03ed6c 100644 --- a/abci/tests/test_cli/ex2.abci.out +++ b/abci/tests/test_cli/ex2.abci.out @@ -1,5 +1,3 @@ -> set_option serial on - > check_tx 0x00 -> code: OK @@ -10,18 +8,16 @@ -> code: OK > check_tx 0x00 --> code: 2 --> log: Invalid nonce. Expected >= 1, got 0 +-> code: OK > deliver_tx 0x01 -> code: OK > deliver_tx 0x04 --> code: 2 --> log: Invalid nonce. Expected 2, got 4 +-> code: OK > info -> code: OK --> data: {"hashes":0,"txs":2} --> data.hex: 0x7B22686173686573223A302C22747873223A327D +-> data: {"size":3} +-> data.hex: 0x7B2273697A65223A337D diff --git a/abci/tests/test_cli/test.sh b/abci/tests/test_cli/test.sh index 9c02ce6f5..078d645af 100755 --- a/abci/tests/test_cli/test.sh +++ b/abci/tests/test_cli/test.sh @@ -30,6 +30,8 @@ function testExample() { cat "${INPUT}.out.new" echo "Expected:" cat "${INPUT}.out" + echo "Diff:" + diff "${INPUT}.out" "${INPUT}.out.new" exit 1 fi @@ -37,6 +39,7 @@ function testExample() { } testExample 1 tests/test_cli/ex1.abci abci-cli kvstore +testExample 2 tests/test_cli/ex2.abci abci-cli kvstore echo "" echo "PASS" diff --git a/docs/app-dev/abci-cli.md b/docs/app-dev/abci-cli.md index 39edea3d2..04dbf92a3 100644 --- a/docs/app-dev/abci-cli.md +++ b/docs/app-dev/abci-cli.md @@ -168,6 +168,14 @@ Try running these commands: -> data: {"size":0} -> data.hex: 0x7B2273697A65223A307D +> prepare_proposal "abc" +-> code: OK +-> log: Succeeded. Tx: abc action: UNMODIFIED + +> process_proposal "abc" +-> code: OK +-> status: ACCEPT + > commit -> code: OK -> data.hex: 0x0000000000000000 @@ -188,6 +196,8 @@ Try running these commands: -> code: OK -> log: exists -> height: 2 +-> key: abc +-> key.hex: 616263 -> value: abc -> value.hex: 616263 @@ -202,8 +212,34 @@ Try running these commands: -> code: OK -> log: exists -> height: 3 +-> key: def +-> key.hex: 646566 -> value: xyz -> value.hex: 78797A + +> prepare_proposal "preparedef" +-> code: OK +-> log: Succeeded. Tx: def action: ADDED +-> code: OK +-> log: Succeeded. Tx: preparedef action: REMOVED + +> process_proposal "def" +-> code: OK +-> status: ACCEPT + +> process_proposal "preparedef" +-> code: OK +-> status: REJECT + +> prepare_proposal + +> process_proposal +-> code: OK +-> status: ACCEPT + +> commit +-> code: OK +-> data.hex: 0x0400000000000000 ``` Note that if we do `deliver_tx "abc"` it will store `(abc, abc)`, but if