2016-01-12 16:50:06 -05:00
|
|
|
package rpcserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2017-12-09 23:40:51 -06:00
|
|
|
"context"
|
2017-01-02 09:50:20 -08:00
|
|
|
"encoding/hex"
|
2016-01-12 16:50:06 -05:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"reflect"
|
2017-10-10 13:48:56 +04:00
|
|
|
"runtime/debug"
|
2016-01-12 16:50:06 -05:00
|
|
|
"sort"
|
2016-01-12 18:29:31 -05:00
|
|
|
"strings"
|
2016-01-12 16:50:06 -05:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gorilla/websocket"
|
2017-03-09 19:00:05 +04:00
|
|
|
"github.com/pkg/errors"
|
2017-05-03 16:42:30 +02:00
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
"github.com/tendermint/go-amino"
|
2017-04-26 19:57:33 -04:00
|
|
|
types "github.com/tendermint/tendermint/rpc/lib/types"
|
2017-04-21 17:51:11 -04:00
|
|
|
cmn "github.com/tendermint/tmlibs/common"
|
2017-05-02 11:53:32 +04:00
|
|
|
"github.com/tendermint/tmlibs/log"
|
2016-01-12 16:50:06 -05:00
|
|
|
)
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// RegisterRPCFuncs adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions.
|
2016-01-13 18:37:35 -05:00
|
|
|
// "result" is the interface on which the result objects are registered, and is popualted with every RPCResponse
|
2018-04-05 15:45:11 -07:00
|
|
|
func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, cdc *amino.Codec, logger log.Logger) {
|
2016-01-12 16:50:06 -05:00
|
|
|
// HTTP endpoints
|
|
|
|
for funcName, rpcFunc := range funcMap {
|
2018-04-05 15:45:11 -07:00
|
|
|
mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc, cdc, logger))
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// JSONRPC endpoints
|
2018-04-05 15:45:11 -07:00
|
|
|
mux.HandleFunc("/", makeJSONRPCHandler(funcMap, cdc, logger))
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
// function introspection
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// RPCFunc contains the introspected type information for a function
|
2016-01-12 16:50:06 -05:00
|
|
|
type RPCFunc struct {
|
|
|
|
f reflect.Value // underlying rpc function
|
|
|
|
args []reflect.Type // type of each function arg
|
|
|
|
returns []reflect.Type // type of each return arg
|
|
|
|
argNames []string // name of each argument
|
|
|
|
ws bool // websocket only
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// NewRPCFunc wraps a function for introspection.
|
2016-01-12 18:29:31 -05:00
|
|
|
// f is the function, args are comma separated argument names
|
|
|
|
func NewRPCFunc(f interface{}, args string) *RPCFunc {
|
|
|
|
return newRPCFunc(f, args, false)
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// NewWSRPCFunc wraps a function for introspection and use in the websockets.
|
2016-01-12 18:29:31 -05:00
|
|
|
func NewWSRPCFunc(f interface{}, args string) *RPCFunc {
|
|
|
|
return newRPCFunc(f, args, true)
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
2016-01-12 18:29:31 -05:00
|
|
|
func newRPCFunc(f interface{}, args string, ws bool) *RPCFunc {
|
|
|
|
var argNames []string
|
|
|
|
if args != "" {
|
|
|
|
argNames = strings.Split(args, ",")
|
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
return &RPCFunc{
|
|
|
|
f: reflect.ValueOf(f),
|
|
|
|
args: funcArgTypes(f),
|
|
|
|
returns: funcReturnTypes(f),
|
2016-01-12 18:29:31 -05:00
|
|
|
argNames: argNames,
|
|
|
|
ws: ws,
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return a function's argument types
|
|
|
|
func funcArgTypes(f interface{}) []reflect.Type {
|
|
|
|
t := reflect.TypeOf(f)
|
|
|
|
n := t.NumIn()
|
|
|
|
typez := make([]reflect.Type, n)
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
typez[i] = t.In(i)
|
|
|
|
}
|
|
|
|
return typez
|
|
|
|
}
|
|
|
|
|
|
|
|
// return a function's return types
|
|
|
|
func funcReturnTypes(f interface{}) []reflect.Type {
|
|
|
|
t := reflect.TypeOf(f)
|
|
|
|
n := t.NumOut()
|
|
|
|
typez := make([]reflect.Type, n)
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
typez[i] = t.Out(i)
|
|
|
|
}
|
|
|
|
return typez
|
|
|
|
}
|
|
|
|
|
|
|
|
// function introspection
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// rpc.json
|
|
|
|
|
|
|
|
// jsonrpc calls grab the given method's function info and runs reflect.Call
|
2018-04-05 15:45:11 -07:00
|
|
|
func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger log.Logger) http.HandlerFunc {
|
2016-01-12 16:50:06 -05:00
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2017-11-27 22:05:55 +00:00
|
|
|
b, err := ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
WriteRPCResponseHTTP(w, types.RPCInvalidRequestError("", errors.Wrap(err, "Error reading request body")))
|
|
|
|
return
|
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
// if its an empty request (like from a browser),
|
|
|
|
// just display a list of functions
|
|
|
|
if len(b) == 0 {
|
|
|
|
writeListOfEndpoints(w, r, funcMap)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-03-07 18:34:54 +04:00
|
|
|
var request types.RPCRequest
|
2017-11-27 22:05:55 +00:00
|
|
|
err = json.Unmarshal(b, &request)
|
2016-01-12 16:50:06 -05:00
|
|
|
if err != nil {
|
2017-05-26 17:45:09 +02:00
|
|
|
WriteRPCResponseHTTP(w, types.RPCParseError("", errors.Wrap(err, "Error unmarshalling request")))
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
}
|
2017-09-18 12:02:15 -07:00
|
|
|
// 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 == "" {
|
2017-10-10 13:01:25 +04:00
|
|
|
logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
|
2017-09-18 12:02:15 -07:00
|
|
|
return
|
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
if len(r.URL.Path) > 1 {
|
2017-05-26 17:45:09 +02:00
|
|
|
WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(request.ID, errors.Errorf("Path %s is invalid", r.URL.Path)))
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
rpcFunc := funcMap[request.Method]
|
2017-09-18 12:26:26 -07:00
|
|
|
if rpcFunc == nil || rpcFunc.ws {
|
2017-05-26 14:46:33 +02:00
|
|
|
WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(request.ID))
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
}
|
2017-10-10 13:50:06 +04:00
|
|
|
var args []reflect.Value
|
|
|
|
if len(request.Params) > 0 {
|
2018-04-05 15:45:11 -07:00
|
|
|
args, err = jsonParamsToArgsRPC(rpcFunc, cdc, request.Params)
|
2017-10-10 13:50:06 +04:00
|
|
|
if err != nil {
|
|
|
|
WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(request.ID, errors.Wrap(err, "Error converting json params to arguments")))
|
|
|
|
return
|
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
returns := rpcFunc.f.Call(args)
|
2017-05-02 11:53:32 +04:00
|
|
|
logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
|
2016-01-12 16:50:06 -05:00
|
|
|
result, err := unreflectResult(returns)
|
|
|
|
if err != nil {
|
2017-05-26 17:45:09 +02:00
|
|
|
WriteRPCResponseHTTP(w, types.RPCInternalError(request.ID, err))
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
}
|
2018-04-05 15:45:11 -07:00
|
|
|
WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, request.ID, result))
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
func mapParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, params map[string]json.RawMessage, argsOffset int) ([]reflect.Value, error) {
|
2017-05-03 16:42:30 +02:00
|
|
|
values := make([]reflect.Value, len(rpcFunc.argNames))
|
|
|
|
for i, argName := range rpcFunc.argNames {
|
|
|
|
argType := rpcFunc.args[i+argsOffset]
|
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
if p, ok := params[argName]; ok && p != nil && len(p) > 0 {
|
2017-05-03 16:42:30 +02:00
|
|
|
val := reflect.New(argType)
|
2018-04-05 15:45:11 -07:00
|
|
|
err := cdc.UnmarshalJSON(p, val.Interface())
|
2017-05-03 16:42:30 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
values[i] = val.Elem()
|
|
|
|
} else { // use default for that type
|
|
|
|
values[i] = reflect.Zero(argType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return values, nil
|
|
|
|
}
|
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
func arrayParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, params []json.RawMessage, argsOffset int) ([]reflect.Value, error) {
|
2017-05-03 16:42:30 +02:00
|
|
|
if len(rpcFunc.argNames) != len(params) {
|
|
|
|
return nil, errors.Errorf("Expected %v parameters (%v), got %v (%v)",
|
|
|
|
len(rpcFunc.argNames), rpcFunc.argNames, len(params), params)
|
|
|
|
}
|
|
|
|
|
|
|
|
values := make([]reflect.Value, len(params))
|
|
|
|
for i, p := range params {
|
|
|
|
argType := rpcFunc.args[i+argsOffset]
|
|
|
|
val := reflect.New(argType)
|
2018-04-05 15:45:11 -07:00
|
|
|
err := cdc.UnmarshalJSON(p, val.Interface())
|
2017-05-03 16:42:30 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
values[i] = val.Elem()
|
|
|
|
}
|
|
|
|
return values, nil
|
|
|
|
}
|
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
// `raw` is unparsed json (from json.RawMessage) encoding either a map or an array.
|
|
|
|
// `argsOffset` should be 0 for RPC calls, and 1 for WS requests, where len(rpcFunc.args) != len(rpcFunc.argNames).
|
2017-03-09 18:30:55 +04:00
|
|
|
//
|
|
|
|
// Example:
|
|
|
|
// rpcFunc.args = [rpctypes.WSRPCContext string]
|
|
|
|
// rpcFunc.argNames = ["arg"]
|
2018-04-05 15:45:11 -07:00
|
|
|
func jsonParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, raw []byte, argsOffset int) ([]reflect.Value, error) {
|
|
|
|
|
|
|
|
// TODO: Make more efficient, perhaps by checking the first character for '{' or '['?
|
|
|
|
// First, try to get the map.
|
|
|
|
var m map[string]json.RawMessage
|
2017-05-03 16:42:30 +02:00
|
|
|
err := json.Unmarshal(raw, &m)
|
|
|
|
if err == nil {
|
2018-04-05 15:45:11 -07:00
|
|
|
return mapParamsToArgs(rpcFunc, cdc, m, argsOffset)
|
2017-05-03 16:13:58 +02:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:46:40 -07:00
|
|
|
// Otherwise, try an array.
|
2018-04-05 15:45:11 -07:00
|
|
|
var a []json.RawMessage
|
2017-05-03 16:42:30 +02:00
|
|
|
err = json.Unmarshal(raw, &a)
|
|
|
|
if err == nil {
|
2018-04-05 15:45:11 -07:00
|
|
|
return arrayParamsToArgs(rpcFunc, cdc, a, argsOffset)
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
2017-05-03 16:42:30 +02:00
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
// Otherwise, bad format, we cannot parse
|
2017-05-03 16:42:30 +02:00
|
|
|
return nil, errors.Errorf("Unknown type for JSON params: %v. Expected map or array", err)
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
2017-04-21 12:18:21 -04:00
|
|
|
// Convert a []interface{} OR a map[string]interface{} to properly typed values
|
2018-04-05 15:45:11 -07:00
|
|
|
func jsonParamsToArgsRPC(rpcFunc *RPCFunc, cdc *amino.Codec, params json.RawMessage) ([]reflect.Value, error) {
|
|
|
|
return jsonParamsToArgs(rpcFunc, cdc, params, 0)
|
2017-04-21 12:18:21 -04:00
|
|
|
}
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
// Same as above, but with the first param the websocket connection
|
2018-04-05 15:45:11 -07:00
|
|
|
func jsonParamsToArgsWS(rpcFunc *RPCFunc, cdc *amino.Codec, params json.RawMessage, wsCtx types.WSRPCContext) ([]reflect.Value, error) {
|
|
|
|
values, err := jsonParamsToArgs(rpcFunc, cdc, params, 1)
|
2017-03-09 12:23:21 +04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
2017-03-09 12:23:21 +04:00
|
|
|
return append([]reflect.Value{reflect.ValueOf(wsCtx)}, values...), nil
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// rpc.json
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// rpc.http
|
|
|
|
|
|
|
|
// convert from a function name to the http handler
|
2018-04-05 15:45:11 -07:00
|
|
|
func makeHTTPHandler(rpcFunc *RPCFunc, cdc *amino.Codec, logger log.Logger) func(http.ResponseWriter, *http.Request) {
|
2016-01-12 16:50:06 -05:00
|
|
|
// Exception for websocket endpoints
|
|
|
|
if rpcFunc.ws {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2017-09-18 12:26:26 -07:00
|
|
|
WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(""))
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// All other endpoints
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2017-05-02 11:53:32 +04:00
|
|
|
logger.Debug("HTTP HANDLER", "req", r)
|
2018-04-05 15:45:11 -07:00
|
|
|
args, err := httpParamsToArgs(rpcFunc, cdc, r)
|
2016-01-12 16:50:06 -05:00
|
|
|
if err != nil {
|
2017-05-26 17:45:09 +02:00
|
|
|
WriteRPCResponseHTTP(w, types.RPCInvalidParamsError("", errors.Wrap(err, "Error converting http params to arguments")))
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
returns := rpcFunc.f.Call(args)
|
2017-05-02 11:53:32 +04:00
|
|
|
logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
|
2016-01-12 16:50:06 -05:00
|
|
|
result, err := unreflectResult(returns)
|
|
|
|
if err != nil {
|
2017-05-26 17:45:09 +02:00
|
|
|
WriteRPCResponseHTTP(w, types.RPCInternalError("", err))
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
}
|
2018-04-05 15:45:11 -07:00
|
|
|
WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, "", result))
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Covert an http query to a list of properly typed values.
|
|
|
|
// To be properly decoded the arg must be a concrete type from tendermint (if its an interface).
|
2018-04-05 15:45:11 -07:00
|
|
|
func httpParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, r *http.Request) ([]reflect.Value, error) {
|
2017-03-08 17:16:01 +04:00
|
|
|
values := make([]reflect.Value, len(rpcFunc.args))
|
2016-01-12 16:50:06 -05:00
|
|
|
|
2017-03-08 17:16:01 +04:00
|
|
|
for i, name := range rpcFunc.argNames {
|
|
|
|
argType := rpcFunc.args[i]
|
2017-03-09 12:23:21 +04:00
|
|
|
|
|
|
|
values[i] = reflect.Zero(argType) // set default for that type
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
arg := GetParam(r, name)
|
2017-03-08 17:16:01 +04:00
|
|
|
// log.Notice("param to arg", "argType", argType, "name", name, "arg", arg)
|
|
|
|
|
|
|
|
if "" == arg {
|
|
|
|
continue
|
|
|
|
}
|
2017-01-02 09:50:20 -08:00
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
v, err, ok := nonJSONToArg(cdc, argType, arg)
|
2017-01-07 14:21:10 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-01-02 09:50:20 -08:00
|
|
|
}
|
2017-01-07 14:21:10 -08:00
|
|
|
if ok {
|
|
|
|
values[i] = v
|
2017-01-02 09:50:20 -08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
values[i], err = _jsonStringToArg(cdc, argType, arg)
|
2016-01-12 16:50:06 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2017-03-08 17:16:01 +04:00
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
return values, nil
|
|
|
|
}
|
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
func _jsonStringToArg(cdc *amino.Codec, ty reflect.Type, arg string) (reflect.Value, error) {
|
2016-01-12 16:50:06 -05:00
|
|
|
v := reflect.New(ty)
|
2018-04-05 15:45:11 -07:00
|
|
|
err := cdc.UnmarshalJSON([]byte(arg), v.Interface())
|
2016-01-12 16:50:06 -05:00
|
|
|
if err != nil {
|
|
|
|
return v, err
|
|
|
|
}
|
|
|
|
v = v.Elem()
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
|
2018-04-05 15:45:11 -07:00
|
|
|
func nonJSONToArg(cdc *amino.Codec, ty reflect.Type, arg string) (reflect.Value, error, bool) {
|
2017-01-07 14:21:10 -08:00
|
|
|
isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`)
|
|
|
|
isHexString := strings.HasPrefix(strings.ToLower(arg), "0x")
|
|
|
|
expectingString := ty.Kind() == reflect.String
|
|
|
|
expectingByteSlice := ty.Kind() == reflect.Slice && ty.Elem().Kind() == reflect.Uint8
|
|
|
|
|
|
|
|
if isHexString {
|
|
|
|
if !expectingString && !expectingByteSlice {
|
2017-03-09 19:00:05 +04:00
|
|
|
err := errors.Errorf("Got a hex string arg, but expected '%s'",
|
2017-01-07 14:21:10 -08:00
|
|
|
ty.Kind().String())
|
|
|
|
return reflect.ValueOf(nil), err, false
|
|
|
|
}
|
|
|
|
|
|
|
|
var value []byte
|
|
|
|
value, err := hex.DecodeString(arg[2:])
|
|
|
|
if err != nil {
|
|
|
|
return reflect.ValueOf(nil), err, false
|
|
|
|
}
|
|
|
|
if ty.Kind() == reflect.String {
|
|
|
|
return reflect.ValueOf(string(value)), nil, true
|
|
|
|
}
|
|
|
|
return reflect.ValueOf([]byte(value)), nil, true
|
|
|
|
}
|
|
|
|
|
|
|
|
if isQuotedString && expectingByteSlice {
|
|
|
|
v := reflect.New(reflect.TypeOf(""))
|
2018-04-05 15:45:11 -07:00
|
|
|
err := cdc.UnmarshalJSON([]byte(arg), v.Interface())
|
2017-01-07 14:21:10 -08:00
|
|
|
if err != nil {
|
|
|
|
return reflect.ValueOf(nil), err, false
|
|
|
|
}
|
|
|
|
v = v.Elem()
|
|
|
|
return reflect.ValueOf([]byte(v.String())), nil, true
|
|
|
|
}
|
|
|
|
|
|
|
|
return reflect.ValueOf(nil), nil, false
|
|
|
|
}
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
// rpc.http
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// rpc.websocket
|
|
|
|
|
|
|
|
const (
|
2017-08-10 17:39:38 -04:00
|
|
|
defaultWSWriteChanCapacity = 1000
|
|
|
|
defaultWSWriteWait = 10 * time.Second
|
|
|
|
defaultWSReadWait = 30 * time.Second
|
|
|
|
defaultWSPingPeriod = (defaultWSReadWait * 9) / 10
|
2016-01-12 16:50:06 -05:00
|
|
|
)
|
|
|
|
|
2018-04-05 16:07:29 -07:00
|
|
|
// A single websocket connection contains listener id, underlying ws
|
2017-11-02 14:00:18 -05:00
|
|
|
// connection, and the event switch for subscribing to events.
|
|
|
|
//
|
|
|
|
// In case of an error, the connection is stopped.
|
2016-01-12 16:50:06 -05:00
|
|
|
type wsConnection struct {
|
2017-03-07 18:34:54 +04:00
|
|
|
cmn.BaseService
|
2016-01-12 16:50:06 -05:00
|
|
|
|
2017-08-10 17:39:38 -04:00
|
|
|
remoteAddr string
|
|
|
|
baseConn *websocket.Conn
|
|
|
|
writeChan chan types.RPCResponse
|
2016-01-12 16:50:06 -05:00
|
|
|
|
|
|
|
funcMap map[string]*RPCFunc
|
2018-04-05 15:45:11 -07:00
|
|
|
cdc *amino.Codec
|
new pubsub package
comment out failing consensus tests for now
rewrite rpc httpclient to use new pubsub package
import pubsub as tmpubsub, query as tmquery
make event IDs constants
EventKey -> EventTypeKey
rename EventsPubsub to PubSub
mempool does not use pubsub
rename eventsSub to pubsub
new subscribe API
fix channel size issues and consensus tests bugs
refactor rpc client
add missing discardFromChan method
add mutex
rename pubsub to eventBus
remove IsRunning from WSRPCConnection interface (not needed)
add a comment in broadcastNewRoundStepsAndVotes
rename registerEventCallbacks to broadcastNewRoundStepsAndVotes
See https://dave.cheney.net/2014/03/19/channel-axioms
stop eventBuses after reactor tests
remove unnecessary Unsubscribe
return subscribe helper function
move discardFromChan to where it is used
subscribe now returns an err
this gives us ability to refuse to subscribe if pubsub is at its max
capacity.
use context for control overflow
cache queries
handle err when subscribing in replay_test
rename testClientID to testSubscriber
extract var
set channel buffer capacity to 1 in replay_file
fix byzantine_test
unsubscribe from single event, not all events
refactor httpclient to return events to appropriate channels
return failing testReplayCrashBeforeWriteVote test
fix TestValidatorSetChanges
refactor code a bit
fix testReplayCrashBeforeWriteVote
add comment
fix TestValidatorSetChanges
fixes from Bucky's review
update comment [ci skip]
test TxEventBuffer
update changelog
fix TestValidatorSetChanges (2nd attempt)
only do wg.Done when no errors
benchmark event bus
create pubsub server inside NewEventBus
only expose config params (later if needed)
set buffer capacity to 0 so we are not testing cache
new tx event format: key = "Tx" plus a tag {"tx.hash": XYZ}
This should allow to subscribe to all transactions! or a specific one
using a query: "tm.events.type = Tx and tx.hash = '013ABF99434...'"
use TimeoutCommit instead of afterPublishEventNewBlockTimeout
TimeoutCommit is the time a node waits after committing a block, before
it goes into the next height. So it will finish everything from the last
block, but then wait a bit. The idea is this gives it time to hear more
votes from other validators, to strengthen the commit it includes in the
next block. But it also gives it time to hear about new transactions.
waitForBlockWithUpdatedVals
rewrite WAL crash tests
Task:
test that we can recover from any WAL crash.
Solution:
the old tests were relying on event hub being run in the same thread (we
were injecting the private validator's last signature).
when considering a rewrite, we considered two possible solutions: write
a "fuzzy" testing system where WAL is crashing upon receiving a new
message, or inject failures and trigger them in tests using something
like https://github.com/coreos/gofail.
remove sleep
no cs.Lock around wal.Save
test different cases (empty block, non-empty block, ...)
comments
add comments
test 4 cases: empty block, non-empty block, non-empty block with smaller part size, many blocks
fixes as per Bucky's last review
reset subscriptions on UnsubscribeAll
use a simple counter to track message for which we panicked
also, set a smaller part size for all test cases
2017-06-26 19:00:30 +04:00
|
|
|
|
2017-08-10 17:39:38 -04:00
|
|
|
// write channel capacity
|
|
|
|
writeChanCapacity int
|
|
|
|
|
|
|
|
// each write times out after this.
|
|
|
|
writeWait time.Duration
|
|
|
|
|
2017-08-07 18:29:55 -04:00
|
|
|
// Connection times out if we haven't received *anything* in this long, not even pings.
|
2017-08-10 17:39:38 -04:00
|
|
|
readWait time.Duration
|
2017-08-07 18:29:55 -04:00
|
|
|
|
2017-08-10 17:39:38 -04:00
|
|
|
// Send pings to server with this period. Must be less than readWait, but greater than zero.
|
2017-08-07 18:29:55 -04:00
|
|
|
pingPeriod time.Duration
|
2017-11-02 14:00:18 -05:00
|
|
|
|
2017-12-09 23:40:51 -06:00
|
|
|
// object that is used to subscribe / unsubscribe from events
|
|
|
|
eventSub types.EventSubscriber
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
2017-11-02 14:00:18 -05:00
|
|
|
// NewWSConnection wraps websocket.Conn.
|
|
|
|
//
|
|
|
|
// See the commentary on the func(*wsConnection) functions for a detailed
|
|
|
|
// description of how to configure ping period and pong wait time. NOTE: if the
|
|
|
|
// write buffer is full, pongs may be dropped, which may cause clients to
|
|
|
|
// disconnect. see https://github.com/gorilla/websocket/issues/97
|
2018-04-05 15:45:11 -07:00
|
|
|
func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, cdc *amino.Codec, options ...func(*wsConnection)) *wsConnection {
|
2016-01-12 16:50:06 -05:00
|
|
|
wsc := &wsConnection{
|
2017-08-10 17:39:38 -04:00
|
|
|
remoteAddr: baseConn.RemoteAddr().String(),
|
|
|
|
baseConn: baseConn,
|
|
|
|
funcMap: funcMap,
|
2018-04-05 15:45:11 -07:00
|
|
|
cdc: cdc,
|
2017-08-10 17:39:38 -04:00
|
|
|
writeWait: defaultWSWriteWait,
|
|
|
|
writeChanCapacity: defaultWSWriteChanCapacity,
|
|
|
|
readWait: defaultWSReadWait,
|
|
|
|
pingPeriod: defaultWSPingPeriod,
|
2017-08-07 18:29:55 -04:00
|
|
|
}
|
|
|
|
for _, option := range options {
|
|
|
|
option(wsc)
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
2017-05-02 11:53:32 +04:00
|
|
|
wsc.BaseService = *cmn.NewBaseService(nil, "wsConnection", wsc)
|
2016-01-12 16:50:06 -05:00
|
|
|
return wsc
|
|
|
|
}
|
|
|
|
|
2017-12-09 23:40:51 -06:00
|
|
|
// EventSubscriber sets object that is used to subscribe / unsubscribe from
|
|
|
|
// events - not Goroutine-safe. If none given, default node's eventBus will be
|
|
|
|
// used.
|
|
|
|
func EventSubscriber(eventSub types.EventSubscriber) func(*wsConnection) {
|
|
|
|
return func(wsc *wsConnection) {
|
|
|
|
wsc.eventSub = eventSub
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// WriteWait sets the amount of time to wait before a websocket write times out.
|
|
|
|
// It should only be used in the constructor - not Goroutine-safe.
|
2017-08-10 17:39:38 -04:00
|
|
|
func WriteWait(writeWait time.Duration) func(*wsConnection) {
|
|
|
|
return func(wsc *wsConnection) {
|
|
|
|
wsc.writeWait = writeWait
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// WriteChanCapacity sets the capacity of the websocket write channel.
|
|
|
|
// It should only be used in the constructor - not Goroutine-safe.
|
2017-08-10 17:39:38 -04:00
|
|
|
func WriteChanCapacity(cap int) func(*wsConnection) {
|
|
|
|
return func(wsc *wsConnection) {
|
|
|
|
wsc.writeChanCapacity = cap
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// ReadWait sets the amount of time to wait before a websocket read times out.
|
|
|
|
// It should only be used in the constructor - not Goroutine-safe.
|
2017-08-10 17:39:38 -04:00
|
|
|
func ReadWait(readWait time.Duration) func(*wsConnection) {
|
|
|
|
return func(wsc *wsConnection) {
|
|
|
|
wsc.readWait = readWait
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// PingPeriod sets the duration for sending websocket pings.
|
|
|
|
// It should only be used in the constructor - not Goroutine-safe.
|
2017-08-10 17:39:38 -04:00
|
|
|
func PingPeriod(pingPeriod time.Duration) func(*wsConnection) {
|
2017-08-07 18:29:55 -04:00
|
|
|
return func(wsc *wsConnection) {
|
|
|
|
wsc.pingPeriod = pingPeriod
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-02 14:00:18 -05:00
|
|
|
// OnStart implements cmn.Service by starting the read and write routines. It
|
|
|
|
// blocks until the connection closes.
|
2016-01-12 16:50:06 -05:00
|
|
|
func (wsc *wsConnection) OnStart() error {
|
2017-08-10 17:39:38 -04:00
|
|
|
wsc.writeChan = make(chan types.RPCResponse, wsc.writeChanCapacity)
|
2017-04-11 13:29:49 +02:00
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
// Read subscriptions/unsubscriptions to events
|
|
|
|
go wsc.readRoutine()
|
|
|
|
// Write responses, BLOCKING.
|
|
|
|
wsc.writeRoutine()
|
2017-08-10 17:39:38 -04:00
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-12-09 23:40:51 -06:00
|
|
|
// OnStop implements cmn.Service by unsubscribing remoteAddr from all subscriptions.
|
2016-01-12 16:50:06 -05:00
|
|
|
func (wsc *wsConnection) OnStop() {
|
2017-08-24 16:25:56 -04:00
|
|
|
// Both read and write loops close the websocket connection when they exit their loops.
|
|
|
|
// The writeChan is never closed, to allow WriteRPCResponse() to fail.
|
2017-12-09 23:40:51 -06:00
|
|
|
if wsc.eventSub != nil {
|
|
|
|
wsc.eventSub.UnsubscribeAll(context.TODO(), wsc.remoteAddr)
|
2017-11-07 19:16:05 -05:00
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// GetRemoteAddr returns the remote address of the underlying connection.
|
|
|
|
// It implements WSRPCConnection
|
2016-01-12 16:50:06 -05:00
|
|
|
func (wsc *wsConnection) GetRemoteAddr() string {
|
|
|
|
return wsc.remoteAddr
|
|
|
|
}
|
|
|
|
|
2017-12-09 23:40:51 -06:00
|
|
|
// GetEventSubscriber implements WSRPCConnection by returning event subscriber.
|
|
|
|
func (wsc *wsConnection) GetEventSubscriber() types.EventSubscriber {
|
|
|
|
return wsc.eventSub
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// WriteRPCResponse pushes a response to the writeChan, and blocks until it is accepted.
|
|
|
|
// It implements WSRPCConnection. It is Goroutine-safe.
|
2017-03-07 18:34:54 +04:00
|
|
|
func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) {
|
2016-01-12 16:50:06 -05:00
|
|
|
select {
|
2018-02-12 14:31:52 +04:00
|
|
|
case <-wsc.Quit():
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
case wsc.writeChan <- resp:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// TryWriteRPCResponse attempts to push a response to the writeChan, but does not block.
|
|
|
|
// It implements WSRPCConnection. It is Goroutine-safe
|
2017-03-07 18:34:54 +04:00
|
|
|
func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool {
|
2016-01-12 16:50:06 -05:00
|
|
|
select {
|
2018-02-12 14:31:52 +04:00
|
|
|
case <-wsc.Quit():
|
2016-01-12 16:50:06 -05:00
|
|
|
return false
|
|
|
|
case wsc.writeChan <- resp:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-05 16:07:29 -07:00
|
|
|
// Codec returns an amino codec used to decode parameters and encode results.
|
|
|
|
// It implements WSRPCConnection.
|
|
|
|
func (wsc *wsConnection) Codec() *amino.Codec {
|
|
|
|
return wsc.cdc
|
|
|
|
}
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
// Read from the socket and subscribe to or unsubscribe from events
|
|
|
|
func (wsc *wsConnection) readRoutine() {
|
2017-08-10 17:39:38 -04:00
|
|
|
defer func() {
|
2017-10-10 13:48:56 +04:00
|
|
|
if r := recover(); r != nil {
|
|
|
|
err, ok := r.(error)
|
|
|
|
if !ok {
|
|
|
|
err = fmt.Errorf("WSJSONRPC: %v", r)
|
|
|
|
}
|
|
|
|
wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack()))
|
|
|
|
wsc.WriteRPCResponse(types.RPCInternalError("unknown", err))
|
|
|
|
go wsc.readRoutine()
|
|
|
|
} else {
|
2017-10-14 14:38:47 -04:00
|
|
|
wsc.baseConn.Close() // nolint: errcheck
|
2017-10-10 13:48:56 +04:00
|
|
|
}
|
2017-08-10 17:39:38 -04:00
|
|
|
}()
|
|
|
|
|
2017-09-29 11:32:30 +02:00
|
|
|
wsc.baseConn.SetPongHandler(func(m string) error {
|
|
|
|
return wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait))
|
|
|
|
})
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
for {
|
|
|
|
select {
|
2018-02-12 14:31:52 +04:00
|
|
|
case <-wsc.Quit():
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
default:
|
2017-08-10 17:39:38 -04:00
|
|
|
// reset deadline for every type of message (control or data)
|
2017-09-06 11:50:43 -04:00
|
|
|
if err := wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)); err != nil {
|
2017-09-21 09:55:06 -04:00
|
|
|
wsc.Logger.Error("failed to set read deadline", "err", err)
|
2017-09-06 11:50:43 -04:00
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
var in []byte
|
|
|
|
_, in, err := wsc.baseConn.ReadMessage()
|
|
|
|
if err != nil {
|
2017-08-08 16:03:04 -04:00
|
|
|
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
|
2017-08-10 17:39:38 -04:00
|
|
|
wsc.Logger.Info("Client closed the connection")
|
2017-08-08 16:03:04 -04:00
|
|
|
} else {
|
2017-08-10 17:39:38 -04:00
|
|
|
wsc.Logger.Error("Failed to read request", "err", err)
|
2017-08-08 16:03:04 -04:00
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
wsc.Stop()
|
|
|
|
return
|
|
|
|
}
|
2017-08-08 16:03:04 -04:00
|
|
|
|
2017-03-07 18:34:54 +04:00
|
|
|
var request types.RPCRequest
|
2016-01-12 16:50:06 -05:00
|
|
|
err = json.Unmarshal(in, &request)
|
|
|
|
if err != nil {
|
2017-05-26 17:45:09 +02:00
|
|
|
wsc.WriteRPCResponse(types.RPCParseError("", errors.Wrap(err, "Error unmarshaling request")))
|
2016-01-12 16:50:06 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-09-18 12:02:15 -07:00
|
|
|
// 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 == "" {
|
2017-10-10 13:01:25 +04:00
|
|
|
wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
|
2017-09-18 12:02:15 -07:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
// Now, fetch the RPCFunc and execute it.
|
|
|
|
|
|
|
|
rpcFunc := wsc.funcMap[request.Method]
|
|
|
|
if rpcFunc == nil {
|
2017-05-26 14:46:33 +02:00
|
|
|
wsc.WriteRPCResponse(types.RPCMethodNotFoundError(request.ID))
|
2016-01-12 16:50:06 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
var args []reflect.Value
|
|
|
|
if rpcFunc.ws {
|
2017-03-07 18:34:54 +04:00
|
|
|
wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc}
|
2017-10-10 13:50:06 +04:00
|
|
|
if len(request.Params) > 0 {
|
2018-04-05 15:45:11 -07:00
|
|
|
args, err = jsonParamsToArgsWS(rpcFunc, wsc.cdc, request.Params, wsCtx)
|
2017-10-09 13:30:52 +04:00
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
} else {
|
2017-10-10 13:50:06 +04:00
|
|
|
if len(request.Params) > 0 {
|
2018-04-05 15:45:11 -07:00
|
|
|
args, err = jsonParamsToArgsRPC(rpcFunc, wsc.cdc, request.Params)
|
2017-10-09 13:30:52 +04:00
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
if err != nil {
|
2017-05-26 17:45:09 +02:00
|
|
|
wsc.WriteRPCResponse(types.RPCInternalError(request.ID, errors.Wrap(err, "Error converting json params to arguments")))
|
2016-01-12 16:50:06 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
returns := rpcFunc.f.Call(args)
|
2017-06-26 17:12:52 -04:00
|
|
|
|
|
|
|
// TODO: Need to encode args/returns to string if we want to log them
|
|
|
|
wsc.Logger.Info("WSJSONRPC", "method", request.Method)
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
result, err := unreflectResult(returns)
|
|
|
|
if err != nil {
|
2017-05-26 17:45:09 +02:00
|
|
|
wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err))
|
2016-01-12 16:50:06 -05:00
|
|
|
continue
|
|
|
|
} else {
|
2018-04-05 15:45:11 -07:00
|
|
|
wsc.WriteRPCResponse(types.NewRPCSuccessResponse(wsc.cdc, request.ID, result))
|
2016-01-12 16:50:06 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// receives on a write channel and writes out on the socket
|
|
|
|
func (wsc *wsConnection) writeRoutine() {
|
2017-08-10 17:39:38 -04:00
|
|
|
pingTicker := time.NewTicker(wsc.pingPeriod)
|
|
|
|
defer func() {
|
|
|
|
pingTicker.Stop()
|
2017-09-21 11:42:44 -04:00
|
|
|
if err := wsc.baseConn.Close(); err != nil {
|
|
|
|
wsc.Logger.Error("Error closing connection", "err", err)
|
|
|
|
}
|
2017-08-10 17:39:38 -04:00
|
|
|
}()
|
|
|
|
|
|
|
|
// https://github.com/gorilla/websocket/issues/97
|
|
|
|
pongs := make(chan string, 1)
|
|
|
|
wsc.baseConn.SetPingHandler(func(m string) error {
|
|
|
|
select {
|
|
|
|
case pongs <- m:
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
for {
|
|
|
|
select {
|
2017-08-10 17:39:38 -04:00
|
|
|
case m := <-pongs:
|
|
|
|
err := wsc.writeMessageWithDeadline(websocket.PongMessage, []byte(m))
|
|
|
|
if err != nil {
|
|
|
|
wsc.Logger.Info("Failed to write pong (client may disconnect)", "err", err)
|
|
|
|
}
|
|
|
|
case <-pingTicker.C:
|
2017-06-28 11:12:45 -04:00
|
|
|
err := wsc.writeMessageWithDeadline(websocket.PingMessage, []byte{})
|
2016-01-12 16:50:06 -05:00
|
|
|
if err != nil {
|
2017-08-10 17:39:38 -04:00
|
|
|
wsc.Logger.Error("Failed to write ping", "err", err)
|
2016-01-12 16:50:06 -05:00
|
|
|
wsc.Stop()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case msg := <-wsc.writeChan:
|
2017-05-13 11:33:35 +02:00
|
|
|
jsonBytes, err := json.MarshalIndent(msg, "", " ")
|
2016-01-12 16:50:06 -05:00
|
|
|
if err != nil {
|
2017-06-14 12:50:49 +04:00
|
|
|
wsc.Logger.Error("Failed to marshal RPCResponse to JSON", "err", err)
|
2016-01-12 16:50:06 -05:00
|
|
|
} else {
|
2017-06-28 11:12:45 -04:00
|
|
|
if err = wsc.writeMessageWithDeadline(websocket.TextMessage, jsonBytes); err != nil {
|
2017-08-10 17:39:38 -04:00
|
|
|
wsc.Logger.Error("Failed to write response", "err", err)
|
2016-01-12 16:50:06 -05:00
|
|
|
wsc.Stop()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2018-02-12 14:31:52 +04:00
|
|
|
case <-wsc.Quit():
|
2017-08-10 17:39:38 -04:00
|
|
|
return
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-28 11:12:45 -04:00
|
|
|
// All writes to the websocket must (re)set the write deadline.
|
|
|
|
// If some writes don't set it while others do, they may timeout incorrectly (https://github.com/tendermint/tendermint/issues/553)
|
|
|
|
func (wsc *wsConnection) writeMessageWithDeadline(msgType int, msg []byte) error {
|
2017-09-21 11:42:44 -04:00
|
|
|
if err := wsc.baseConn.SetWriteDeadline(time.Now().Add(wsc.writeWait)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-28 11:12:45 -04:00
|
|
|
return wsc.baseConn.WriteMessage(msgType, msg)
|
|
|
|
}
|
|
|
|
|
2016-01-12 16:50:06 -05:00
|
|
|
//----------------------------------------
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// WebsocketManager is the main manager for all websocket connections.
|
|
|
|
// It holds the event switch and a map of functions for routing.
|
2016-01-12 16:50:06 -05:00
|
|
|
// NOTE: The websocket path is defined externally, e.g. in node/node.go
|
|
|
|
type WebsocketManager struct {
|
|
|
|
websocket.Upgrader
|
2017-08-07 18:29:55 -04:00
|
|
|
funcMap map[string]*RPCFunc
|
2018-04-05 15:45:11 -07:00
|
|
|
cdc *amino.Codec
|
2017-08-07 18:29:55 -04:00
|
|
|
logger log.Logger
|
|
|
|
wsConnOptions []func(*wsConnection)
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
new pubsub package
comment out failing consensus tests for now
rewrite rpc httpclient to use new pubsub package
import pubsub as tmpubsub, query as tmquery
make event IDs constants
EventKey -> EventTypeKey
rename EventsPubsub to PubSub
mempool does not use pubsub
rename eventsSub to pubsub
new subscribe API
fix channel size issues and consensus tests bugs
refactor rpc client
add missing discardFromChan method
add mutex
rename pubsub to eventBus
remove IsRunning from WSRPCConnection interface (not needed)
add a comment in broadcastNewRoundStepsAndVotes
rename registerEventCallbacks to broadcastNewRoundStepsAndVotes
See https://dave.cheney.net/2014/03/19/channel-axioms
stop eventBuses after reactor tests
remove unnecessary Unsubscribe
return subscribe helper function
move discardFromChan to where it is used
subscribe now returns an err
this gives us ability to refuse to subscribe if pubsub is at its max
capacity.
use context for control overflow
cache queries
handle err when subscribing in replay_test
rename testClientID to testSubscriber
extract var
set channel buffer capacity to 1 in replay_file
fix byzantine_test
unsubscribe from single event, not all events
refactor httpclient to return events to appropriate channels
return failing testReplayCrashBeforeWriteVote test
fix TestValidatorSetChanges
refactor code a bit
fix testReplayCrashBeforeWriteVote
add comment
fix TestValidatorSetChanges
fixes from Bucky's review
update comment [ci skip]
test TxEventBuffer
update changelog
fix TestValidatorSetChanges (2nd attempt)
only do wg.Done when no errors
benchmark event bus
create pubsub server inside NewEventBus
only expose config params (later if needed)
set buffer capacity to 0 so we are not testing cache
new tx event format: key = "Tx" plus a tag {"tx.hash": XYZ}
This should allow to subscribe to all transactions! or a specific one
using a query: "tm.events.type = Tx and tx.hash = '013ABF99434...'"
use TimeoutCommit instead of afterPublishEventNewBlockTimeout
TimeoutCommit is the time a node waits after committing a block, before
it goes into the next height. So it will finish everything from the last
block, but then wait a bit. The idea is this gives it time to hear more
votes from other validators, to strengthen the commit it includes in the
next block. But it also gives it time to hear about new transactions.
waitForBlockWithUpdatedVals
rewrite WAL crash tests
Task:
test that we can recover from any WAL crash.
Solution:
the old tests were relying on event hub being run in the same thread (we
were injecting the private validator's last signature).
when considering a rewrite, we considered two possible solutions: write
a "fuzzy" testing system where WAL is crashing upon receiving a new
message, or inject failures and trigger them in tests using something
like https://github.com/coreos/gofail.
remove sleep
no cs.Lock around wal.Save
test different cases (empty block, non-empty block, ...)
comments
add comments
test 4 cases: empty block, non-empty block, non-empty block with smaller part size, many blocks
fixes as per Bucky's last review
reset subscriptions on UnsubscribeAll
use a simple counter to track message for which we panicked
also, set a smaller part size for all test cases
2017-06-26 19:00:30 +04:00
|
|
|
// NewWebsocketManager returns a new WebsocketManager that routes according to
|
|
|
|
// the given funcMap and connects to the server with the given connection
|
|
|
|
// options.
|
2018-04-05 15:45:11 -07:00
|
|
|
func NewWebsocketManager(funcMap map[string]*RPCFunc, cdc *amino.Codec, wsConnOptions ...func(*wsConnection)) *WebsocketManager {
|
2016-01-12 16:50:06 -05:00
|
|
|
return &WebsocketManager{
|
|
|
|
funcMap: funcMap,
|
2018-04-05 15:45:11 -07:00
|
|
|
cdc: cdc,
|
2016-01-12 16:50:06 -05:00
|
|
|
Upgrader: websocket.Upgrader{
|
|
|
|
CheckOrigin: func(r *http.Request) bool {
|
2017-08-10 17:39:38 -04:00
|
|
|
// TODO ???
|
2016-01-12 16:50:06 -05:00
|
|
|
return true
|
|
|
|
},
|
|
|
|
},
|
2017-08-07 18:29:55 -04:00
|
|
|
logger: log.NewNopLogger(),
|
|
|
|
wsConnOptions: wsConnOptions,
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// SetLogger sets the logger.
|
2017-05-02 11:53:32 +04:00
|
|
|
func (wm *WebsocketManager) SetLogger(l log.Logger) {
|
|
|
|
wm.logger = l
|
|
|
|
}
|
|
|
|
|
2017-08-24 16:25:56 -04:00
|
|
|
// WebsocketHandler upgrades the request/response (via http.Hijack) and starts the wsConnection.
|
2016-01-12 16:50:06 -05:00
|
|
|
func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
wsConn, err := wm.Upgrade(w, r, nil)
|
|
|
|
if err != nil {
|
|
|
|
// TODO - return http error
|
2017-06-14 12:50:49 +04:00
|
|
|
wm.logger.Error("Failed to upgrade to websocket connection", "err", err)
|
2016-01-12 16:50:06 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// register connection
|
2018-04-05 15:45:11 -07:00
|
|
|
con := NewWSConnection(wsConn, wm.funcMap, wm.cdc, wm.wsConnOptions...)
|
2017-08-10 17:39:38 -04:00
|
|
|
con.SetLogger(wm.logger.With("remote", wsConn.RemoteAddr()))
|
2017-05-02 11:53:32 +04:00
|
|
|
wm.logger.Info("New websocket connection", "remote", con.remoteAddr)
|
2017-11-06 13:20:39 -05:00
|
|
|
err = con.Start() // Blocking
|
2017-09-06 11:50:43 -04:00
|
|
|
if err != nil {
|
2017-09-21 10:56:42 -04:00
|
|
|
wm.logger.Error("Error starting connection", "err", err)
|
2017-09-06 11:50:43 -04:00
|
|
|
}
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// rpc.websocket
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
2016-01-13 18:37:35 -05:00
|
|
|
// NOTE: assume returns is result struct and error. If error is not nil, return it
|
2016-01-12 16:50:06 -05:00
|
|
|
func unreflectResult(returns []reflect.Value) (interface{}, error) {
|
|
|
|
errV := returns[1]
|
|
|
|
if errV.Interface() != nil {
|
2017-03-09 19:00:05 +04:00
|
|
|
return nil, errors.Errorf("%v", errV.Interface())
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
2016-01-13 18:37:35 -05:00
|
|
|
rv := returns[0]
|
|
|
|
// the result is a registered interface,
|
|
|
|
// we need a pointer to it so we can marshal with type byte
|
|
|
|
rvp := reflect.New(rv.Type())
|
|
|
|
rvp.Elem().Set(rv)
|
|
|
|
return rvp.Interface(), nil
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// writes a list of available rpc endpoints as an html page
|
|
|
|
func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) {
|
|
|
|
noArgNames := []string{}
|
|
|
|
argNames := []string{}
|
|
|
|
for name, funcData := range funcMap {
|
|
|
|
if len(funcData.args) == 0 {
|
|
|
|
noArgNames = append(noArgNames, name)
|
|
|
|
} else {
|
|
|
|
argNames = append(argNames, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Strings(noArgNames)
|
|
|
|
sort.Strings(argNames)
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
buf.WriteString("<html><body>")
|
|
|
|
buf.WriteString("<br>Available endpoints:<br>")
|
|
|
|
|
|
|
|
for _, name := range noArgNames {
|
2018-03-05 16:59:04 +01:00
|
|
|
link := fmt.Sprintf("//%s/%s", r.Host, name)
|
2016-01-12 16:50:06 -05:00
|
|
|
buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link))
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.WriteString("<br>Endpoints that require arguments:<br>")
|
|
|
|
for _, name := range argNames {
|
2018-03-05 16:59:04 +01:00
|
|
|
link := fmt.Sprintf("//%s/%s?", r.Host, name)
|
2016-01-12 16:50:06 -05:00
|
|
|
funcData := funcMap[name]
|
|
|
|
for i, argName := range funcData.argNames {
|
|
|
|
link += argName + "=_"
|
|
|
|
if i < len(funcData.argNames)-1 {
|
|
|
|
link += "&"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link))
|
|
|
|
}
|
|
|
|
buf.WriteString("</body></html>")
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
w.WriteHeader(200)
|
2017-10-03 20:20:15 -04:00
|
|
|
w.Write(buf.Bytes()) // nolint: errcheck
|
2016-01-12 16:50:06 -05:00
|
|
|
}
|