Merge pull request #521 from tendermint/json-rpc-patch

updated json response to match spec by @davebryson
This commit is contained in:
Ethan Buchman
2017-09-18 16:37:34 -04:00
committed by GitHub
9 changed files with 167 additions and 85 deletions
+28 -27
View File
@@ -10,34 +10,35 @@ BREAKING CHANGES:
FEATURES: FEATURES:
- Peer reputation management - Peer reputation management
- Use the chain as its own CA for nodes and validators - Use the chain as its own CA for nodes and validators
- Tooling to run multiple blockchains/apps, possibly in a single process - Tooling to run multiple blockchains/apps, possibly in a single process
- State syncing (without transaction replay) - State syncing (without transaction replay)
- Improved support for querying history and state - Improved support for querying history and state
- Add authentication and rate-limitting to the RPC - Add authentication and rate-limitting to the RPC
IMPROVEMENTS: IMPROVEMENTS:
- Improve subtleties around mempool caching and logic - Improve subtleties around mempool caching and logic
- Consensus optimizations: - Consensus optimizations:
- cache block parts for faster agreement after round changes - cache block parts for faster agreement after round changes
- propagate block parts rarest first - propagate block parts rarest first
- Better testing of the consensus state machine (ie. use a DSL) - Better testing of the consensus state machine (ie. use a DSL)
- Auto compiled serialization/deserialization code instead of go-wire reflection - Auto compiled serialization/deserialization code instead of go-wire reflection
BUG FIXES: BUG FIXES:
- Graceful handling/recovery for apps that have non-determinism or fail to halt - Graceful handling/recovery for apps that have non-determinism or fail to halt
- Graceful handling/recovery for violations of safety, or liveness - Graceful handling/recovery for violations of safety, or liveness
## 0.11.0 (Date) ## 0.11.0 (Date)
IMPROVEMENTS: IMPROVEMENTS:
- docs: Added documentation from the tools repo to Read The Docs pipeline - docs: Added documentation from the tools repo to Read The Docs pipeline
- rpc: updated json response to match http://www.jsonrpc.org/specification spec
## 0.10.4 (September 5, 2017) ## 0.10.4 (September 5, 2017)
IMPROVEMENTS: IMPROVEMENTS:
- docs: Added Slate docs to each rpc function (see rpc/core) - docs: Added Slate docs to each rpc function (see rpc/core)
- docs: Ported all website docs to Read The Docs - docs: Ported all website docs to Read The Docs
- config: expose some p2p params to tweak performance: RecvRate, SendRate, and MaxMsgPacketPayloadSize - config: expose some p2p params to tweak performance: RecvRate, SendRate, and MaxMsgPacketPayloadSize
- rpc: Upgrade the websocket client and server, including improved auto reconnect, and proper ping/pong - rpc: Upgrade the websocket client and server, including improved auto reconnect, and proper ping/pong
@@ -77,7 +78,7 @@ IMPROVEMENTS:
FEATURES: FEATURES:
- Use `--trace` to get stack traces for logged errors - Use `--trace` to get stack traces for logged errors
- types: GenesisDoc.ValidatorHash returns the hash of the genesis validator set - types: GenesisDoc.ValidatorHash returns the hash of the genesis validator set
- types: GenesisDocFromFile parses a GenesiDoc from a JSON file - types: GenesisDocFromFile parses a GenesiDoc from a JSON file
IMPROVEMENTS: IMPROVEMENTS:
@@ -101,7 +102,7 @@ Also includes the Grand Repo-Merge of 2017.
BREAKING CHANGES: BREAKING CHANGES:
- Config and Flags: - Config and Flags:
- The `config` map is replaced with a [`Config` struct](https://github.com/tendermint/tendermint/blob/master/config/config.go#L11), - The `config` map is replaced with a [`Config` struct](https://github.com/tendermint/tendermint/blob/master/config/config.go#L11),
containing substructs: `BaseConfig`, `P2PConfig`, `MempoolConfig`, `ConsensusConfig`, `RPCConfig` containing substructs: `BaseConfig`, `P2PConfig`, `MempoolConfig`, `ConsensusConfig`, `RPCConfig`
- This affects the following flags: - This affects the following flags:
- `--seeds` is now `--p2p.seeds` - `--seeds` is now `--p2p.seeds`
@@ -114,16 +115,16 @@ containing substructs: `BaseConfig`, `P2PConfig`, `MempoolConfig`, `ConsensusCon
``` ```
[p2p] [p2p]
laddr="tcp://1.2.3.4:46656" laddr="tcp://1.2.3.4:46656"
[consensus] [consensus]
timeout_propose=1000 timeout_propose=1000
``` ```
- Use viper and `DefaultConfig() / TestConfig()` functions to handle defaults, and remove `config/tendermint` and `config/tendermint_test` - Use viper and `DefaultConfig() / TestConfig()` functions to handle defaults, and remove `config/tendermint` and `config/tendermint_test`
- Change some function and method signatures to - Change some function and method signatures to
- Change some [function and method signatures](https://gist.github.com/ebuchman/640d5fc6c2605f73497992fe107ebe0b) accomodate new config - Change some [function and method signatures](https://gist.github.com/ebuchman/640d5fc6c2605f73497992fe107ebe0b) accomodate new config
- Logger - Logger
- Replace static `log15` logger with a simple interface, and provide a new implementation using `go-kit`. - Replace static `log15` logger with a simple interface, and provide a new implementation using `go-kit`.
See our new [logging library](https://github.com/tendermint/tmlibs/log) and [blog post](https://tendermint.com/blog/abstracting-the-logger-interface-in-go) for more details See our new [logging library](https://github.com/tendermint/tmlibs/log) and [blog post](https://tendermint.com/blog/abstracting-the-logger-interface-in-go) for more details
- Levels `warn` and `notice` are removed (you may need to change them in your `config.toml`!) - Levels `warn` and `notice` are removed (you may need to change them in your `config.toml`!)
- Change some [function and method signatures](https://gist.github.com/ebuchman/640d5fc6c2605f73497992fe107ebe0b) to accept a logger - Change some [function and method signatures](https://gist.github.com/ebuchman/640d5fc6c2605f73497992fe107ebe0b) to accept a logger
@@ -166,7 +167,7 @@ IMPROVEMENTS:
- Limit `/blockchain_info` call to return a maximum of 20 blocks - Limit `/blockchain_info` call to return a maximum of 20 blocks
- Use `.Wrap()` and `.Unwrap()` instead of eg. `PubKeyS` for `go-crypto` types - Use `.Wrap()` and `.Unwrap()` instead of eg. `PubKeyS` for `go-crypto` types
- RPC JSON responses use pretty printing (via `json.MarshalIndent`) - RPC JSON responses use pretty printing (via `json.MarshalIndent`)
- Color code different instances of the consensus for tests - Color code different instances of the consensus for tests
- Isolate viper to `cmd/tendermint/commands` and do not read config from file for tests - Isolate viper to `cmd/tendermint/commands` and do not read config from file for tests
@@ -194,7 +195,7 @@ IMPROVEMENTS:
- WAL uses #ENDHEIGHT instead of #HEIGHT (#HEIGHT will stop working in 0.10.0) - WAL uses #ENDHEIGHT instead of #HEIGHT (#HEIGHT will stop working in 0.10.0)
- Peers included via `--seeds`, under `seeds` in the config, or in `/dial_seeds` are now persistent, and will be reconnected to if the connection breaks - Peers included via `--seeds`, under `seeds` in the config, or in `/dial_seeds` are now persistent, and will be reconnected to if the connection breaks
BUG FIXES: BUG FIXES:
- Fix bug in fast-sync where we stop syncing after a peer is removed, even if they're re-added later - Fix bug in fast-sync where we stop syncing after a peer is removed, even if they're re-added later
- Fix handshake replay to handle validator set changes and results of DeliverTx when we crash after app.Commit but before state.Save() - Fix handshake replay to handle validator set changes and results of DeliverTx when we crash after app.Commit but before state.Save()
@@ -210,7 +211,7 @@ message RequestQuery{
bytes data = 1; bytes data = 1;
string path = 2; string path = 2;
uint64 height = 3; uint64 height = 3;
bool prove = 4; bool prove = 4;
} }
message ResponseQuery{ message ResponseQuery{
@@ -234,7 +235,7 @@ type BlockMeta struct {
} }
``` ```
- `ValidatorSet.Proposer` is exposed as a field and persisted with the `State`. Use `GetProposer()` to initialize or update after validator-set changes. - `ValidatorSet.Proposer` is exposed as a field and persisted with the `State`. Use `GetProposer()` to initialize or update after validator-set changes.
- `tendermint gen_validator` command output is now pure JSON - `tendermint gen_validator` command output is now pure JSON
@@ -277,7 +278,7 @@ type BlockID struct {
} }
``` ```
- `Vote` data type now includes validator address and index: - `Vote` data type now includes validator address and index:
``` ```
type Vote struct { type Vote struct {
@@ -297,7 +298,7 @@ type Vote struct {
FEATURES: FEATURES:
- New message type on the ConsensusReactor, `Maj23Msg`, for peers to alert others they've seen a Maj23, - New message type on the ConsensusReactor, `Maj23Msg`, for peers to alert others they've seen a Maj23,
in order to track and handle conflicting votes intelligently to prevent Byzantine faults from causing halts: in order to track and handle conflicting votes intelligently to prevent Byzantine faults from causing halts:
``` ```
@@ -320,7 +321,7 @@ IMPROVEMENTS:
- Less verbose logging - Less verbose logging
- Better test coverage (37% -> 49%) - Better test coverage (37% -> 49%)
- Canonical SignBytes for signable types - Canonical SignBytes for signable types
- Write-Ahead Log for Mempool and Consensus via tmlibs/autofile - Write-Ahead Log for Mempool and Consensus via tmlibs/autofile
- Better in-process testing for the consensus reactor and byzantine faults - Better in-process testing for the consensus reactor and byzantine faults
- Better crash/restart testing for individual nodes at preset failure points, and of networks at arbitrary points - Better crash/restart testing for individual nodes at preset failure points, and of networks at arbitrary points
- Better abstraction over timeout mechanics - Better abstraction over timeout mechanics
@@ -400,7 +401,7 @@ FEATURES:
- TMSP and RPC support TCP and UNIX sockets - TMSP and RPC support TCP and UNIX sockets
- Addition config options including block size and consensus parameters - Addition config options including block size and consensus parameters
- New WAL mode `cswal_light`; logs only the validator's own votes - New WAL mode `cswal_light`; logs only the validator's own votes
- New RPC endpoints: - New RPC endpoints:
- for starting/stopping profilers, and for updating config - for starting/stopping profilers, and for updating config
- `/broadcast_tx_commit`, returns when tx is included in a block, else an error - `/broadcast_tx_commit`, returns when tx is included in a block, else an error
- `/unsafe_flush_mempool`, empties the mempool - `/unsafe_flush_mempool`, empties the mempool
@@ -421,14 +422,14 @@ BUG FIXES:
Strict versioning only began with the release of v0.7.0, in late summer 2016. Strict versioning only began with the release of v0.7.0, in late summer 2016.
The project itself began in early summer 2014 and was workable decentralized cryptocurrency software by the end of that year. The project itself began in early summer 2014 and was workable decentralized cryptocurrency software by the end of that year.
Through the course of 2015, in collaboration with Eris Industries (now Monax Indsutries), Through the course of 2015, in collaboration with Eris Industries (now Monax Indsutries),
many additional features were integrated, including an implementation from scratch of the Ethereum Virtual Machine. many additional features were integrated, including an implementation from scratch of the Ethereum Virtual Machine.
That implementation now forms the heart of [Burrow](https://github.com/hyperledger/burrow). That implementation now forms the heart of [Burrow](https://github.com/hyperledger/burrow).
In the later half of 2015, the consensus algorithm was upgraded with a more asynchronous design and a more deterministic and robust implementation. In the later half of 2015, the consensus algorithm was upgraded with a more asynchronous design and a more deterministic and robust implementation.
By late 2015, frustration with the difficulty of forking a large monolithic stack to create alternative cryptocurrency designs led to the By late 2015, frustration with the difficulty of forking a large monolithic stack to create alternative cryptocurrency designs led to the
invention of the Application Blockchain Interface (ABCI), then called the Tendermint Socket Protocol (TMSP). invention of the Application Blockchain Interface (ABCI), then called the Tendermint Socket Protocol (TMSP).
The Ethereum Virtual Machine and various other transaction features were removed, and Tendermint was whittled down to a core consensus engine The Ethereum Virtual Machine and various other transaction features were removed, and Tendermint was whittled down to a core consensus engine
driving an application running in another process. driving an application running in another process.
The ABCI interface and implementation were iterated on and improved over the course of 2016, The ABCI interface and implementation were iterated on and improved over the course of 2016,
until versioned history kicked in with v0.7.0. until versioned history kicked in with v0.7.0.
+2 -2
View File
@@ -2,7 +2,7 @@ package core
import ( import (
ctypes "github.com/tendermint/tendermint/rpc/core/types" ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/rpc/lib/types" rpctypes "github.com/tendermint/tendermint/rpc/lib/types"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
) )
@@ -39,7 +39,7 @@ func Subscribe(wsCtx rpctypes.WSRPCContext, event string) (*ctypes.ResultSubscri
// NOTE: EventSwitch callbacks must be nonblocking // NOTE: EventSwitch callbacks must be nonblocking
// NOTE: RPCResponses of subscribed events have id suffix "#event" // NOTE: RPCResponses of subscribed events have id suffix "#event"
tmResult := &ctypes.ResultEvent{event, msg} tmResult := &ctypes.ResultEvent{event, msg}
wsCtx.TryWriteRPCResponse(rpctypes.NewRPCResponse(wsCtx.Request.ID+"#event", tmResult, "")) wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Request.ID+"#event", tmResult))
}) })
return &ctypes.ResultSubscribe{}, nil return &ctypes.ResultSubscribe{}, nil
} }
+3 -4
View File
@@ -75,7 +75,7 @@ func NewJSONRPCClient(remote string) *JSONRPCClient {
} }
func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
request, err := types.MapToRequest("", method, params) request, err := types.MapToRequest("jsonrpc-client", method, params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -146,9 +146,8 @@ func unmarshalResponseBytes(responseBytes []byte, result interface{}) (interface
if err != nil { if err != nil {
return nil, errors.Errorf("Error unmarshalling rpc response: %v", err) return nil, errors.Errorf("Error unmarshalling rpc response: %v", err)
} }
errorStr := response.Error if response.Error != nil {
if errorStr != "" { return nil, errors.Errorf("Response error: %v", response.Error.Message)
return nil, errors.Errorf("Response error: %v", errorStr)
} }
// unmarshal the RawMessage into the result // unmarshal the RawMessage into the result
err = json.Unmarshal(*response.Result, result) err = json.Unmarshal(*response.Result, result)
+4 -4
View File
@@ -195,7 +195,7 @@ func (c *WSClient) Send(ctx context.Context, request types.RPCRequest) error {
// Call the given method. See Send description. // Call the given method. See Send description.
func (c *WSClient) Call(ctx context.Context, method string, params map[string]interface{}) error { func (c *WSClient) Call(ctx context.Context, method string, params map[string]interface{}) error {
request, err := types.MapToRequest("", method, params) request, err := types.MapToRequest("ws-client", method, params)
if err != nil { if err != nil {
return err return err
} }
@@ -205,7 +205,7 @@ func (c *WSClient) Call(ctx context.Context, method string, params map[string]in
// CallWithArrayParams the given method with params in a form of array. See // CallWithArrayParams the given method with params in a form of array. See
// Send description. // Send description.
func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, params []interface{}) error { func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, params []interface{}) error {
request, err := types.ArrayToRequest("", method, params) request, err := types.ArrayToRequest("ws-client", method, params)
if err != nil { if err != nil {
return err return err
} }
@@ -422,8 +422,8 @@ func (c *WSClient) readRoutine() {
c.ErrorsCh <- err c.ErrorsCh <- err
continue continue
} }
if response.Error != "" { if response.Error != nil {
c.ErrorsCh <- errors.Errorf(response.Error) c.ErrorsCh <- errors.New(response.Error.Message)
continue continue
} }
c.Logger.Info("got response", "resp", response.Result) c.Logger.Info("got response", "resp", response.Result)
+27 -21
View File
@@ -110,35 +110,36 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han
var request types.RPCRequest var request types.RPCRequest
err := json.Unmarshal(b, &request) err := json.Unmarshal(b, &request)
if err != nil { if err != nil {
WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse("", nil, fmt.Sprintf("Error unmarshalling request: %v", err.Error()))) WriteRPCResponseHTTP(w, types.RPCParseError("", errors.Wrap(err, "Error unmarshalling request")))
return
}
// A Notification is a Request object without an "id" member.
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
if request.ID == "" {
return return
} }
if len(r.URL.Path) > 1 { if len(r.URL.Path) > 1 {
WriteRPCResponseHTTPError(w, http.StatusNotFound, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path))) WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(request.ID, errors.Errorf("Path %s is invalid", r.URL.Path)))
return return
} }
rpcFunc := funcMap[request.Method] rpcFunc := funcMap[request.Method]
if rpcFunc == nil { if rpcFunc == nil || rpcFunc.ws {
WriteRPCResponseHTTPError(w, http.StatusNotFound, types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(request.ID))
return
}
if rpcFunc.ws {
WriteRPCResponseHTTPError(w, http.StatusMethodNotAllowed, types.NewRPCResponse(request.ID, nil, "RPC method is only for websockets: "+request.Method))
return return
} }
args, err := jsonParamsToArgsRPC(rpcFunc, request.Params) args, err := jsonParamsToArgsRPC(rpcFunc, request.Params)
if err != nil { if err != nil {
WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Error converting json params to arguments: %v", err.Error()))) WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(request.ID, errors.Wrap(err, "Error converting json params to arguments")))
return return
} }
returns := rpcFunc.f.Call(args) returns := rpcFunc.f.Call(args)
logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
result, err := unreflectResult(returns) result, err := unreflectResult(returns)
if err != nil { if err != nil {
WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.NewRPCResponse(request.ID, result, err.Error())) WriteRPCResponseHTTP(w, types.RPCInternalError(request.ID, err))
return return
} }
WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, "")) WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(request.ID, result))
} }
} }
@@ -229,7 +230,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit
// Exception for websocket endpoints // Exception for websocket endpoints
if rpcFunc.ws { if rpcFunc.ws {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
WriteRPCResponseHTTPError(w, http.StatusMethodNotAllowed, types.NewRPCResponse("", nil, "This RPC method is only for websockets")) WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(""))
} }
} }
// All other endpoints // All other endpoints
@@ -237,17 +238,17 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit
logger.Debug("HTTP HANDLER", "req", r) logger.Debug("HTTP HANDLER", "req", r)
args, err := httpParamsToArgs(rpcFunc, r) args, err := httpParamsToArgs(rpcFunc, r)
if err != nil { if err != nil {
WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse("", nil, fmt.Sprintf("Error converting http params to args: %v", err.Error()))) WriteRPCResponseHTTP(w, types.RPCInvalidParamsError("", errors.Wrap(err, "Error converting http params to arguments")))
return return
} }
returns := rpcFunc.f.Call(args) returns := rpcFunc.f.Call(args)
logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns) logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
result, err := unreflectResult(returns) result, err := unreflectResult(returns)
if err != nil { if err != nil {
WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.NewRPCResponse("", nil, err.Error())) WriteRPCResponseHTTP(w, types.RPCInternalError("", err))
return return
} }
WriteRPCResponseHTTP(w, types.NewRPCResponse("", result, "")) WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse("", result))
} }
} }
@@ -509,8 +510,13 @@ func (wsc *wsConnection) readRoutine() {
var request types.RPCRequest var request types.RPCRequest
err = json.Unmarshal(in, &request) err = json.Unmarshal(in, &request)
if err != nil { if err != nil {
errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error()) wsc.WriteRPCResponse(types.RPCParseError("", errors.Wrap(err, "Error unmarshaling request")))
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, errStr)) continue
}
// A Notification is a Request object without an "id" member.
// The Server MUST NOT reply to a Notification, including those that are within a batch request.
if request.ID == "" {
continue continue
} }
@@ -518,7 +524,7 @@ func (wsc *wsConnection) readRoutine() {
rpcFunc := wsc.funcMap[request.Method] rpcFunc := wsc.funcMap[request.Method]
if rpcFunc == nil { if rpcFunc == nil {
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) wsc.WriteRPCResponse(types.RPCMethodNotFoundError(request.ID))
continue continue
} }
var args []reflect.Value var args []reflect.Value
@@ -529,7 +535,7 @@ func (wsc *wsConnection) readRoutine() {
args, err = jsonParamsToArgsRPC(rpcFunc, request.Params) args, err = jsonParamsToArgsRPC(rpcFunc, request.Params)
} }
if err != nil { if err != nil {
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) wsc.WriteRPCResponse(types.RPCInternalError(request.ID, errors.Wrap(err, "Error converting json params to arguments")))
continue continue
} }
returns := rpcFunc.f.Call(args) returns := rpcFunc.f.Call(args)
@@ -539,10 +545,10 @@ func (wsc *wsConnection) readRoutine() {
result, err := unreflectResult(returns) result, err := unreflectResult(returns)
if err != nil { if err != nil {
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err))
continue continue
} else { } else {
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, result, "")) wsc.WriteRPCResponse(types.NewRPCSuccessResponse(request.ID, result))
continue continue
} }
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
types "github.com/tendermint/tendermint/rpc/lib/types" types "github.com/tendermint/tendermint/rpc/lib/types"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
) )
@@ -99,7 +100,7 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler
// For the rest, // For the rest,
logger.Error("Panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack())) logger.Error("Panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack()))
rww.WriteHeader(http.StatusInternalServerError) rww.WriteHeader(http.StatusInternalServerError)
WriteRPCResponseHTTP(rww, types.NewRPCResponse("", nil, fmt.Sprintf("Internal Server Error: %v", e))) WriteRPCResponseHTTP(rww, types.RPCInternalError("", e.(error)))
} }
} }
+53 -13
View File
@@ -5,9 +5,14 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/pkg/errors"
events "github.com/tendermint/tmlibs/events" events "github.com/tendermint/tmlibs/events"
) )
//----------------------------------------
// REQUEST
type RPCRequest struct { type RPCRequest struct {
JSONRPC string `json:"jsonrpc"` JSONRPC string `json:"jsonrpc"`
ID string `json:"id"` ID string `json:"id"`
@@ -47,42 +52,77 @@ func ArrayToRequest(id string, method string, params []interface{}) (RPCRequest,
} }
//---------------------------------------- //----------------------------------------
// RESPONSE
type RPCError struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data,omitempty"`
}
type RPCResponse struct { type RPCResponse struct {
JSONRPC string `json:"jsonrpc"` JSONRPC string `json:"jsonrpc"`
ID string `json:"id"` ID string `json:"id"`
Result *json.RawMessage `json:"result"` Result *json.RawMessage `json:"result,omitempty"`
Error string `json:"error"` Error *RPCError `json:"error,omitempty"`
} }
func NewRPCResponse(id string, res interface{}, err string) RPCResponse { func NewRPCSuccessResponse(id string, res interface{}) RPCResponse {
var raw *json.RawMessage var raw *json.RawMessage
if res != nil { if res != nil {
var js []byte var js []byte
js, err2 := json.Marshal(res) js, err := json.Marshal(res)
if err2 == nil { if err != nil {
rawMsg := json.RawMessage(js) return RPCInternalError(id, errors.Wrap(err, "Error marshalling response"))
raw = &rawMsg
} else {
err = err2.Error()
} }
rawMsg := json.RawMessage(js)
raw = &rawMsg
} }
return RPCResponse{JSONRPC: "2.0", ID: id, Result: raw}
}
func NewRPCErrorResponse(id string, code int, msg string, data string) RPCResponse {
return RPCResponse{ return RPCResponse{
JSONRPC: "2.0", JSONRPC: "2.0",
ID: id, ID: id,
Result: raw, Error: &RPCError{Code: code, Message: msg, Data: data},
Error: err,
} }
} }
func (resp RPCResponse) String() string { func (resp RPCResponse) String() string {
if resp.Error == "" { if resp.Error == nil {
return fmt.Sprintf("[%s %v]", resp.ID, resp.Result) return fmt.Sprintf("[%s %v]", resp.ID, resp.Result)
} else { } else {
return fmt.Sprintf("[%s %s]", resp.ID, resp.Error) return fmt.Sprintf("[%s %s]", resp.ID, resp.Error)
} }
} }
func RPCParseError(id string, err error) RPCResponse {
return NewRPCErrorResponse(id, -32700, "Parse error. Invalid JSON", err.Error())
}
func RPCInvalidRequestError(id string, err error) RPCResponse {
return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error())
}
func RPCMethodNotFoundError(id string) RPCResponse {
return NewRPCErrorResponse(id, -32601, "Method not found", "")
}
func RPCInvalidParamsError(id string, err error) RPCResponse {
return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error())
}
func RPCInternalError(id string, err error) RPCResponse {
return NewRPCErrorResponse(id, -32603, "Internal error", err.Error())
}
func RPCServerError(id string, err error) RPCResponse {
return NewRPCErrorResponse(id, -32000, "Server error", err.Error())
}
//---------------------------------------- //----------------------------------------
// *wsConnection implements this interface. // *wsConnection implements this interface.
@@ -100,7 +140,7 @@ type WSRPCContext struct {
} }
//---------------------------------------- //----------------------------------------
// sockets // SOCKETS
// //
// Determine if its a unix or tcp socket. // Determine if its a unix or tcp socket.
// If tcp, must specify the port; `0.0.0.0` will return incorrectly as "unix" since there's no port // If tcp, must specify the port; `0.0.0.0` will return incorrectly as "unix" since there's no port
+32
View File
@@ -0,0 +1,32 @@
package rpctypes
import (
"encoding/json"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
type SampleResult struct {
Value string
}
func TestResponses(t *testing.T) {
assert := assert.New(t)
a := NewRPCSuccessResponse("1", &SampleResult{"hello"})
b, _ := json.Marshal(a)
s := `{"jsonrpc":"2.0","id":"1","result":{"Value":"hello"}}`
assert.Equal(string(s), string(b))
d := RPCParseError("1", errors.New("Hello world"))
e, _ := json.Marshal(d)
f := `{"jsonrpc":"2.0","id":"1","error":{"code":-32700,"message":"Parse error. Invalid JSON","data":"Hello world"}}`
assert.Equal(string(f), string(e))
g := RPCMethodNotFoundError("2")
h, _ := json.Marshal(g)
i := `{"jsonrpc":"2.0","id":"2","error":{"code":-32601,"message":"Method not found"}}`
assert.Equal(string(h), string(i))
}
+16 -13
View File
@@ -23,39 +23,42 @@ function getCode() {
else else
# this wont actually work if theres an error ... # this wont actually work if theres an error ...
echo "$R" | jq .code echo "$R" | jq .code
fi fi
} }
function sendTx() { function sendTx() {
TX=$1 TX=$1
if [[ "$GRPC_BROADCAST_TX" == "" ]]; then if [[ "$GRPC_BROADCAST_TX" == "" ]]; then
RESPONSE=`curl -s localhost:46657/broadcast_tx_commit?tx=0x$TX` RESPONSE=$(curl -s localhost:46657/broadcast_tx_commit?tx=0x"$TX")
ERROR=`echo $RESPONSE | jq .error` IS_ERR=$(echo "$RESPONSE" | jq 'has("error")')
ERROR=$(echo "$RESPONSE" | jq '.error')
ERROR=$(echo "$ERROR" | tr -d '"') # remove surrounding quotes ERROR=$(echo "$ERROR" | tr -d '"') # remove surrounding quotes
RESPONSE=`echo $RESPONSE | jq .result` RESPONSE=$(echo "$RESPONSE" | jq '.result')
else else
if [ -f grpc_client ]; then if [ -f grpc_client ]; then
rm grpc_client rm grpc_client
fi fi
echo "... building grpc_client" echo "... building grpc_client"
go build -o grpc_client grpc_client.go go build -o grpc_client grpc_client.go
RESPONSE=`./grpc_client $TX` RESPONSE=$(./grpc_client "$TX")
IS_ERR=false
ERROR="" ERROR=""
fi fi
echo "RESPONSE" echo "RESPONSE"
echo $RESPONSE echo "$RESPONSE"
echo $RESPONSE | jq . &> /dev/null echo "$RESPONSE" | jq . &> /dev/null
IS_JSON=$? IS_JSON=$?
if [[ "$IS_JSON" != "0" ]]; then if [[ "$IS_JSON" != "0" ]]; then
IS_ERR=true
ERROR="$RESPONSE" ERROR="$RESPONSE"
fi fi
APPEND_TX_RESPONSE=`echo $RESPONSE | jq .deliver_tx` APPEND_TX_RESPONSE=$(echo "$RESPONSE" | jq '.deliver_tx')
APPEND_TX_CODE=`getCode "$APPEND_TX_RESPONSE"` APPEND_TX_CODE=$(getCode "$APPEND_TX_RESPONSE")
CHECK_TX_RESPONSE=`echo $RESPONSE | jq .check_tx` CHECK_TX_RESPONSE=$(echo "$RESPONSE" | jq '.check_tx')
CHECK_TX_CODE=`getCode "$CHECK_TX_RESPONSE"` CHECK_TX_CODE=$(getCode "$CHECK_TX_RESPONSE")
echo "-------" echo "-------"
echo "TX $TX" echo "TX $TX"
@@ -63,7 +66,7 @@ function sendTx() {
echo "ERROR $ERROR" echo "ERROR $ERROR"
echo "----" echo "----"
if [[ "$ERROR" != "" ]]; then if $IS_ERR; then
echo "Unexpected error sending tx ($TX): $ERROR" echo "Unexpected error sending tx ($TX): $ERROR"
exit 1 exit 1
fi fi