From 0611d2eda2473d4781ec83dbdb5576dfdd062112 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Fri, 22 Feb 2019 06:54:31 -0500 Subject: [PATCH] Merge PR #3688: JSON Decode Log in REST Client --- PENDING.md | 3 ++ baseapp/baseapp.go | 10 ++---- client/context/context.go | 1 + types/rest/rest.go | 5 +-- types/result.go | 76 ++++++++++++++++++++++++++++----------- types/result_test.go | 11 ++++++ 6 files changed, 76 insertions(+), 30 deletions(-) diff --git a/PENDING.md b/PENDING.md index 92e126bdc0..60fafbdc16 100644 --- a/PENDING.md +++ b/PENDING.md @@ -39,6 +39,9 @@ ### Gaia REST API +* Update the `TxResponse` type allowing for the `Logs` result to be JSON +decoded automatically. + ### Gaia CLI ### Gaia diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 76a7c25c1d..b6694a5032 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -629,15 +629,9 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) (ctx sdk.Con return } -type indexedABCILog struct { - MsgIndex int `json:"msg_index"` - Success bool `json:"success"` - Log string `json:"log"` -} - // runMsgs iterates through all the messages and executes them. func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) { - idxlogs := make([]indexedABCILog, 0, len(msgs)) // a list of JSON-encoded logs with msg index + idxlogs := make([]sdk.ABCIMessageLog, 0, len(msgs)) // a list of JSON-encoded logs with msg index var data []byte // NOTE: we just append them all (?!) var tags sdk.Tags // also just append them all @@ -666,7 +660,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re tags = append(tags, sdk.MakeTag(sdk.TagAction, msg.Type())) tags = append(tags, msgResult.Tags...) - idxLog := indexedABCILog{MsgIndex: msgIdx, Log: msgResult.Log} + idxLog := sdk.ABCIMessageLog{MsgIndex: msgIdx, Log: msgResult.Log} // stop execution and return on first failed message if !msgResult.IsOK() { diff --git a/client/context/context.go b/client/context/context.go index 7364adbffb..05ae3455d5 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -254,6 +254,7 @@ func (ctx CLIContext) PrintOutput(toPrint fmt.Stringer) (err error) { switch ctx.OutputFormat { case "text": out = []byte(toPrint.String()) + case "json": if ctx.Indent { out, err = ctx.Codec.MarshalJSONIndent(toPrint, "", " ") diff --git a/types/rest/rest.go b/types/rest/rest.go index 1cf2b8bb59..d24e8e3d70 100644 --- a/types/rest/rest.go +++ b/types/rest/rest.go @@ -192,6 +192,9 @@ func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response inter var output []byte switch response.(type) { + case []byte: + output = response.([]byte) + default: var err error if indent { @@ -203,8 +206,6 @@ func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response inter WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - case []byte: - output = response.([]byte) } w.Header().Set("Content-Type", "application/json") diff --git a/types/result.go b/types/result.go index e4be53ec5e..8ff873bfcf 100644 --- a/types/result.go +++ b/types/result.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "fmt" "strings" @@ -9,7 +10,6 @@ import ( // Result is the union of ResponseFormat and ResponseCheckTx. type Result struct { - // Code is the response code, is stored back on the chain. Code CodeType @@ -21,7 +21,7 @@ type Result struct { // results from multiple msgs executions Data []byte - // Log is just debug information. NOTE: nondeterministic. + // Log contains the txs log information. NOTE: nondeterministic. Log string // GasWanted is the maximum units of work we allow this tx to perform. @@ -39,19 +39,42 @@ func (res Result) IsOK() bool { return res.Code.IsOK() } -// Is a version of TxResponse where the tags are StringTags rather than []byte tags +// ABCIMessageLogs represents a slice of ABCIMessageLog. +type ABCIMessageLogs []ABCIMessageLog + +// ABCIMessageLog defines a structure containing an indexed tx ABCI message log. +type ABCIMessageLog struct { + MsgIndex int `json:"msg_index"` + Success bool `json:"success"` + Log string `json:"log"` +} + +// String implements the fmt.Stringer interface for the ABCIMessageLogs type. +func (logs ABCIMessageLogs) String() (str string) { + if logs != nil { + raw, err := json.Marshal(logs) + if err == nil { + str = string(raw) + } + } + + return str +} + +// TxResponse defines a structure containing relevant tx data and metadata. The +// tags are stringified and the log is JSON decoded. type TxResponse struct { - Height int64 `json:"height"` - TxHash string `json:"txhash"` - Code uint32 `json:"code,omitempty"` - Data []byte `json:"data,omitempty"` - Log string `json:"log,omitempty"` - Info string `json:"info,omitempty"` - GasWanted int64 `json:"gas_wanted,omitempty"` - GasUsed int64 `json:"gas_used,omitempty"` - Tags StringTags `json:"tags,omitempty"` - Codespace string `json:"codespace,omitempty"` - Tx Tx `json:"tx,omitempty"` + Height int64 `json:"height"` + TxHash string `json:"txhash"` + Code uint32 `json:"code,omitempty"` + Data []byte `json:"data,omitempty"` + Logs ABCIMessageLogs `json:"logs,omitempty"` + Info string `json:"info,omitempty"` + GasWanted int64 `json:"gas_wanted,omitempty"` + GasUsed int64 `json:"gas_used,omitempty"` + Tags StringTags `json:"tags,omitempty"` + Codespace string `json:"codespace,omitempty"` + Tx Tx `json:"tx,omitempty"` } // NewResponseResultTx returns a TxResponse given a ResultTx from tendermint @@ -60,12 +83,14 @@ func NewResponseResultTx(res *ctypes.ResultTx, tx Tx) TxResponse { return TxResponse{} } + parsedLogs, _ := ParseABCILogs(res.TxResult.Log) + return TxResponse{ TxHash: res.Hash.String(), Height: res.Height, Code: res.TxResult.Code, Data: res.TxResult.Data, - Log: res.TxResult.Log, + Logs: parsedLogs, Info: res.TxResult.Info, GasWanted: res.TxResult.GasWanted, GasUsed: res.TxResult.GasUsed, @@ -85,12 +110,14 @@ func NewResponseFormatBroadcastTxCommit(res *ctypes.ResultBroadcastTxCommit) TxR txHash = res.Hash.String() } + parsedLogs, _ := ParseABCILogs(res.DeliverTx.Log) + return TxResponse{ Height: res.Height, TxHash: txHash, Code: res.DeliverTx.Code, Data: res.DeliverTx.Data, - Log: res.DeliverTx.Log, + Logs: parsedLogs, Info: res.DeliverTx.Info, GasWanted: res.DeliverTx.GasWanted, GasUsed: res.DeliverTx.GasUsed, @@ -106,10 +133,12 @@ func NewResponseFormatBroadcastTx(res *ctypes.ResultBroadcastTx) TxResponse { return TxResponse{} } + parsedLogs, _ := ParseABCILogs(res.Log) + return TxResponse{ Code: res.Code, Data: res.Data.Bytes(), - Log: res.Log, + Logs: parsedLogs, TxHash: res.Hash.String(), } } @@ -134,8 +163,8 @@ func (r TxResponse) String() string { sb.WriteString(fmt.Sprintf(" Data: %s\n", string(r.Data))) } - if r.Log != "" { - sb.WriteString(fmt.Sprintf(" Log: %s\n", r.Log)) + if r.Logs != nil { + sb.WriteString(fmt.Sprintf(" Logs: %s\n", r.Logs)) } if r.Info != "" { @@ -163,5 +192,12 @@ func (r TxResponse) String() string { // Empty returns true if the response is empty func (r TxResponse) Empty() bool { - return r.TxHash == "" && r.Log == "" + return r.TxHash == "" && r.Logs == nil +} + +// ParseABCILogs attempts to parse a stringified ABCI tx log into a slice of +// ABCIMessageLog types. It returns an error upon JSON decoding failure. +func ParseABCILogs(logs string) (res ABCIMessageLogs, err error) { + err = json.Unmarshal([]byte(logs), &res) + return res, err } diff --git a/types/result_test.go b/types/result_test.go index 9765933d98..0b126bbd78 100644 --- a/types/result_test.go +++ b/types/result_test.go @@ -16,3 +16,14 @@ func TestResult(t *testing.T) { res.Code = CodeType(1) require.False(t, res.IsOK()) } + +func TestParseABCILog(t *testing.T) { + logs := `[{"log":"","msg_index":1,"success":true}]` + + res, err := ParseABCILogs(logs) + require.NoError(t, err) + require.Len(t, res, 1) + require.Equal(t, res[0].Log, "") + require.Equal(t, res[0].MsgIndex, 1) + require.True(t, res[0].Success) +}