cosmos-sdk/types/result.go

281 lines
7.4 KiB
Go

package types
import (
"encoding/hex"
"encoding/json"
"fmt"
"math"
"strings"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
// Result is the union of ResponseFormat and ResponseCheckTx.
type Result struct {
// Code is the response code, is stored back on the chain.
Code CodeType
// Codespace is the string referring to the domain of an error
Codespace CodespaceType
// Data is any data returned from the app.
// Data has to be length prefixed in order to separate
// results from multiple msgs executions
Data []byte
// Log contains the txs log information. NOTE: nondeterministic.
Log string
// GasWanted is the maximum units of work we allow this tx to perform.
GasWanted uint64
// GasUsed is the amount of gas actually consumed. NOTE: unimplemented
GasUsed uint64
// Events contains a slice of Event objects that were emitted during some
// execution.
Events Events
}
// TODO: In the future, more codes may be OK.
func (res Result) IsOK() bool {
return res.Code.IsOK()
}
// ABCIMessageLogs represents a slice of ABCIMessageLog.
type ABCIMessageLogs []ABCIMessageLog
// ABCIMessageLog defines a structure containing an indexed tx ABCI message log.
type ABCIMessageLog struct {
MsgIndex uint16 `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 string `json:"data,omitempty"`
RawLog string `json:"raw_log,omitempty"`
Logs ABCIMessageLogs `json:"logs,omitempty"`
Info string `json:"info,omitempty"`
GasWanted int64 `json:"gas_wanted,omitempty"`
GasUsed int64 `json:"gas_used,omitempty"`
Events StringEvents `json:"events,omitempty"`
Codespace string `json:"codespace,omitempty"`
Tx Tx `json:"tx,omitempty"`
Timestamp string `json:"timestamp,omitempty"`
}
// NewResponseResultTx returns a TxResponse given a ResultTx from tendermint
func NewResponseResultTx(res *ctypes.ResultTx, tx Tx, timestamp string) TxResponse {
if res == nil {
return TxResponse{}
}
parsedLogs, _ := ParseABCILogs(res.TxResult.Log)
return TxResponse{
TxHash: res.Hash.String(),
Height: res.Height,
Code: res.TxResult.Code,
Data: strings.ToUpper(hex.EncodeToString(res.TxResult.Data)),
RawLog: res.TxResult.Log,
Logs: parsedLogs,
Info: res.TxResult.Info,
GasWanted: res.TxResult.GasWanted,
GasUsed: res.TxResult.GasUsed,
Events: StringifyEvents(res.TxResult.Events),
Tx: tx,
Timestamp: timestamp,
}
}
// NewResponseFormatBroadcastTxCommit returns a TxResponse given a
// ResultBroadcastTxCommit from tendermint.
func NewResponseFormatBroadcastTxCommit(res *ctypes.ResultBroadcastTxCommit) TxResponse {
if res == nil {
return TxResponse{}
}
if !res.CheckTx.IsOK() {
return newTxResponseCheckTx(res)
}
return newTxResponseDeliverTx(res)
}
func newTxResponseCheckTx(res *ctypes.ResultBroadcastTxCommit) TxResponse {
if res == nil {
return TxResponse{}
}
var txHash string
if res.Hash != nil {
txHash = res.Hash.String()
}
parsedLogs, _ := ParseABCILogs(res.CheckTx.Log)
return TxResponse{
Height: res.Height,
TxHash: txHash,
Code: res.CheckTx.Code,
Data: strings.ToUpper(hex.EncodeToString(res.CheckTx.Data)),
RawLog: res.CheckTx.Log,
Logs: parsedLogs,
Info: res.CheckTx.Info,
GasWanted: res.CheckTx.GasWanted,
GasUsed: res.CheckTx.GasUsed,
Events: StringifyEvents(res.CheckTx.Events),
Codespace: res.CheckTx.Codespace,
}
}
func newTxResponseDeliverTx(res *ctypes.ResultBroadcastTxCommit) TxResponse {
if res == nil {
return TxResponse{}
}
var txHash string
if res.Hash != nil {
txHash = res.Hash.String()
}
parsedLogs, _ := ParseABCILogs(res.DeliverTx.Log)
return TxResponse{
Height: res.Height,
TxHash: txHash,
Code: res.DeliverTx.Code,
Data: strings.ToUpper(hex.EncodeToString(res.DeliverTx.Data)),
RawLog: res.DeliverTx.Log,
Logs: parsedLogs,
Info: res.DeliverTx.Info,
GasWanted: res.DeliverTx.GasWanted,
GasUsed: res.DeliverTx.GasUsed,
Events: StringifyEvents(res.DeliverTx.Events),
Codespace: res.DeliverTx.Codespace,
}
}
// NewResponseFormatBroadcastTx returns a TxResponse given a ResultBroadcastTx from tendermint
func NewResponseFormatBroadcastTx(res *ctypes.ResultBroadcastTx) TxResponse {
if res == nil {
return TxResponse{}
}
parsedLogs, _ := ParseABCILogs(res.Log)
return TxResponse{
Code: res.Code,
Data: res.Data.String(),
RawLog: res.Log,
Logs: parsedLogs,
TxHash: res.Hash.String(),
}
}
func (r TxResponse) String() string {
var sb strings.Builder
sb.WriteString("Response:\n")
if r.Height > 0 {
sb.WriteString(fmt.Sprintf(" Height: %d\n", r.Height))
}
if r.TxHash != "" {
sb.WriteString(fmt.Sprintf(" TxHash: %s\n", r.TxHash))
}
if r.Code > 0 {
sb.WriteString(fmt.Sprintf(" Code: %d\n", r.Code))
}
if r.Data != "" {
sb.WriteString(fmt.Sprintf(" Data: %s\n", r.Data))
}
if r.RawLog != "" {
sb.WriteString(fmt.Sprintf(" Raw Log: %s\n", r.RawLog))
}
if r.Logs != nil {
sb.WriteString(fmt.Sprintf(" Logs: %s\n", r.Logs))
}
if r.Info != "" {
sb.WriteString(fmt.Sprintf(" Info: %s\n", r.Info))
}
if r.GasWanted != 0 {
sb.WriteString(fmt.Sprintf(" GasWanted: %d\n", r.GasWanted))
}
if r.GasUsed != 0 {
sb.WriteString(fmt.Sprintf(" GasUsed: %d\n", r.GasUsed))
}
if r.Codespace != "" {
sb.WriteString(fmt.Sprintf(" Codespace: %s\n", r.Codespace))
}
if r.Timestamp != "" {
sb.WriteString(fmt.Sprintf(" Timestamp: %s\n", r.Timestamp))
}
if len(r.Events) > 0 {
sb.WriteString(fmt.Sprintf(" Events: \n%s\n", r.Events.String()))
}
return strings.TrimSpace(sb.String())
}
// Empty returns true if the response is empty
func (r TxResponse) Empty() bool {
return r.TxHash == "" && r.Logs == nil
}
// SearchTxsResult defines a structure for querying txs pageable
type SearchTxsResult struct {
TotalCount int `json:"total_count"` // Count of all txs
Count int `json:"count"` // Count of txs in current page
PageNumber int `json:"page_number"` // Index of current page, start from 1
PageTotal int `json:"page_total"` // Count of total pages
Limit int `json:"limit"` // Max count txs per page
Txs []TxResponse `json:"txs"` // List of txs in current page
}
func NewSearchTxsResult(totalCount, count, page, limit int, txs []TxResponse) SearchTxsResult {
return SearchTxsResult{
TotalCount: totalCount,
Count: count,
PageNumber: page,
PageTotal: int(math.Ceil(float64(totalCount) / float64(limit))),
Limit: limit,
Txs: txs,
}
}
// 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
}