Skip to content

Commit

Permalink
Merge PR #4996: Expose precheck errors from tx
Browse files Browse the repository at this point in the history
  • Loading branch information
tnachen authored and alexanderbez committed Sep 11, 2019
1 parent 849e2fb commit 5bcab79
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ longer panics if the store to load contains substores that we didn't explicitly
* [\#4979](https://github.com/cosmos/cosmos-sdk/issues/4979) Introduce a new `halt-time` config and
CLI option to the `start` command. When provided, an application will halt during `Commit` when the
block time is >= the `halt-time`.
* [\#4972](https://github.com/cosmos/cosmos-sdk/issues/4972) A `TxResponse` with a corresponding code
and tx hash will be returned for specific Tendermint errors:
* `CodeTxInMempoolCache`
* `CodeMempoolIsFull`
* `CodeTxTooLarge`

### Improvements

Expand Down
56 changes: 56 additions & 0 deletions client/context/broadcast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package context

import (
"fmt"
"strings"

"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/mempool"

"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -29,6 +33,46 @@ func (ctx CLIContext) BroadcastTx(txBytes []byte) (res sdk.TxResponse, err error
return res, err
}

// CheckTendermintError checks if the error returned from BroadcastTx is a
// Tendermint error that is returned before the tx is submitted due to
// precondition checks that failed. If an Tendermint error is detected, this
// function returns the correct code back in TxResponse.
//
// TODO: Avoid brittle string matching in favor of error matching. This requires
// a change to Tendermint's RPCError type to allow retrieval or matching against
// a concrete error type.
func CheckTendermintError(err error, txBytes []byte) *sdk.TxResponse {
if err == nil {
return nil
}

errStr := strings.ToLower(err.Error())
txHash := fmt.Sprintf("%X", tmhash.Sum(txBytes))

switch {
case strings.Contains(errStr, strings.ToLower(mempool.ErrTxInCache.Error())):
return &sdk.TxResponse{
Code: uint32(sdk.CodeTxInMempoolCache),
TxHash: txHash,
}

case strings.Contains(errStr, "mempool is full"):
return &sdk.TxResponse{
Code: uint32(sdk.CodeMempoolIsFull),
TxHash: txHash,
}

case strings.Contains(errStr, "tx too large"):
return &sdk.TxResponse{
Code: uint32(sdk.CodeTxTooLarge),
TxHash: txHash,
}

default:
return nil
}
}

// BroadcastTxCommit broadcasts transaction bytes to a Tendermint node and
// waits for a commit. An error is only returned if there is no RPC node
// connection or if broadcasting fails.
Expand All @@ -44,6 +88,10 @@ func (ctx CLIContext) BroadcastTxCommit(txBytes []byte) (sdk.TxResponse, error)

res, err := node.BroadcastTxCommit(txBytes)
if err != nil {
if errRes := CheckTendermintError(err, txBytes); errRes != nil {
return *errRes, nil
}

return sdk.NewResponseFormatBroadcastTxCommit(res), err
}

Expand All @@ -67,6 +115,10 @@ func (ctx CLIContext) BroadcastTxSync(txBytes []byte) (sdk.TxResponse, error) {
}

res, err := node.BroadcastTxSync(txBytes)
if errRes := CheckTendermintError(err, txBytes); errRes != nil {
return *errRes, nil
}

return sdk.NewResponseFormatBroadcastTx(res), err
}

Expand All @@ -79,5 +131,9 @@ func (ctx CLIContext) BroadcastTxAsync(txBytes []byte) (sdk.TxResponse, error) {
}

res, err := node.BroadcastTxAsync(txBytes)
if errRes := CheckTendermintError(err, txBytes); errRes != nil {
return *errRes, nil
}

return sdk.NewResponseFormatBroadcastTx(res), err
}
70 changes: 70 additions & 0 deletions client/context/broadcast_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package context

import (
"fmt"
"testing"

"github.com/tendermint/tendermint/crypto/tmhash"

"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/mempool"
"github.com/tendermint/tendermint/rpc/client/mock"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
tmtypes "github.com/tendermint/tendermint/types"

"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/types"
)

type MockClient struct {
mock.Client
err error
}

func (c MockClient) BroadcastTxCommit(tx tmtypes.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
return nil, c.err
}

func (c MockClient) BroadcastTxAsync(tx tmtypes.Tx) (*ctypes.ResultBroadcastTx, error) {
return nil, c.err
}

func (c MockClient) BroadcastTxSync(tx tmtypes.Tx) (*ctypes.ResultBroadcastTx, error) {
return nil, c.err
}

func CreateContextWithErrorAndMode(err error, mode string) CLIContext {
return CLIContext{
Client: MockClient{err: err},
BroadcastMode: mode,
}
}

// Test the correct code is returned when
func TestBroadcastError(t *testing.T) {
errors := map[error]uint32{
mempool.ErrTxInCache: uint32(types.CodeTxInMempoolCache),
mempool.ErrTxTooLarge{}: uint32(types.CodeTxTooLarge),
mempool.ErrMempoolIsFull{}: uint32(types.CodeMempoolIsFull),
}

modes := []string{
flags.BroadcastAsync,
flags.BroadcastBlock,
flags.BroadcastSync,
}

txBytes := []byte{0xA, 0xB}
txHash := fmt.Sprintf("%X", tmhash.Sum(txBytes))

for _, mode := range modes {
for err, code := range errors {
ctx := CreateContextWithErrorAndMode(err, mode)
resp, returnedErr := ctx.BroadcastTx(txBytes)
require.NoError(t, returnedErr)
require.Equal(t, code, resp.Code)
require.Equal(t, txHash, resp.TxHash)
}
}

}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/tendermint/iavl v0.12.4
github.com/tendermint/tendermint v0.32.3
github.com/tendermint/tm-db v0.1.1
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
gopkg.in/yaml.v2 v2.2.2
)

Expand Down
8 changes: 2 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
Expand All @@ -98,8 +96,6 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
Expand Down Expand Up @@ -214,8 +210,6 @@ github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoM
github.com/tendermint/iavl v0.12.4 h1:hd1woxUGISKkfUWBA4mmmTwOua6PQZTJM/F0FDrmMV8=
github.com/tendermint/iavl v0.12.4/go.mod h1:8LHakzt8/0G3/I8FUU0ReNx98S/EP6eyPJkAUvEXT/o=
github.com/tendermint/tendermint v0.32.1/go.mod h1:jmPDAKuNkev9793/ivn/fTBnfpA9mGBww8MPRNPNxnU=
github.com/tendermint/tendermint v0.32.2 h1:FvZWdksfDg/65vKKr5Lgo57keARFnmhrUEXHwyrV1QY=
github.com/tendermint/tendermint v0.32.2/go.mod h1:NwMyx58S8VJ7tEpFKqRVlVWKO9N9zjTHu+Dx96VsnOE=
github.com/tendermint/tendermint v0.32.3 h1:GEnWpGQ795h5oTFNbfBLsY0LW/CW2j6p6HtiYNfxsgg=
github.com/tendermint/tendermint v0.32.3/go.mod h1:ZK2c29jl1QRYznIRyRWRDsmm1yvtPzBRT00x4t1JToY=
github.com/tendermint/tm-db v0.1.1 h1:G3Xezy3sOk9+ekhjZ/kjArYIs1SmwV+1OUgNkj7RgV0=
Expand Down Expand Up @@ -274,6 +268,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
Expand Down
3 changes: 3 additions & 0 deletions types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const (
CodeTooManySignatures CodeType = 15
CodeGasOverflow CodeType = 16
CodeNoSignatures CodeType = 17
CodeTxInMempoolCache CodeType = 18
CodeMempoolIsFull CodeType = 19
CodeTxTooLarge CodeType = 20

// CodespaceRoot is a codespace for error codes in this file only.
// Notice that 0 is an "unset" codespace, which can be overridden with
Expand Down

0 comments on commit 5bcab79

Please sign in to comment.