github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/lib/server/handlers.go (about)

     1  package rpcserver
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"log/slog"
    11  	"net/http"
    12  	"reflect"
    13  	"regexp"
    14  	"runtime/debug"
    15  	"sort"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/gorilla/websocket"
    20  
    21  	"github.com/gnolang/gno/tm2/pkg/amino"
    22  	types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types"
    23  	"github.com/gnolang/gno/tm2/pkg/errors"
    24  	"github.com/gnolang/gno/tm2/pkg/log"
    25  	"github.com/gnolang/gno/tm2/pkg/service"
    26  )
    27  
    28  // RegisterRPCFuncs adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions.
    29  // "result" is the interface on which the result objects are registered, and is populated with every RPCResponse
    30  func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger *slog.Logger) {
    31  	// HTTP endpoints
    32  	for funcName, rpcFunc := range funcMap {
    33  		mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc, logger))
    34  	}
    35  
    36  	// JSONRPC endpoints
    37  	mux.HandleFunc("/", handleInvalidJSONRPCPaths(makeJSONRPCHandler(funcMap, logger)))
    38  }
    39  
    40  // -------------------------------------
    41  // function introspection
    42  
    43  // RPCFunc contains the introspected type information for a function
    44  type RPCFunc struct {
    45  	f        reflect.Value  // underlying rpc function
    46  	args     []reflect.Type // type of each function arg
    47  	returns  []reflect.Type // type of each return arg
    48  	argNames []string       // name of each argument
    49  	ws       bool           // websocket only
    50  }
    51  
    52  // NewRPCFunc wraps a function for introspection.
    53  // f is the function, args are comma separated argument names
    54  func NewRPCFunc(f interface{}, args string) *RPCFunc {
    55  	return newRPCFunc(f, args, false)
    56  }
    57  
    58  // NewWSRPCFunc wraps a function for introspection and use in the websockets.
    59  func NewWSRPCFunc(f interface{}, args string) *RPCFunc {
    60  	return newRPCFunc(f, args, true)
    61  }
    62  
    63  func newRPCFunc(f interface{}, args string, ws bool) *RPCFunc {
    64  	var argNames []string
    65  	if args != "" {
    66  		argNames = strings.Split(args, ",")
    67  	}
    68  	return &RPCFunc{
    69  		f:        reflect.ValueOf(f),
    70  		args:     funcArgTypes(f),
    71  		returns:  funcReturnTypes(f),
    72  		argNames: argNames,
    73  		ws:       ws,
    74  	}
    75  }
    76  
    77  // return a function's argument types
    78  func funcArgTypes(f interface{}) []reflect.Type {
    79  	t := reflect.TypeOf(f)
    80  	n := t.NumIn()
    81  	typez := make([]reflect.Type, n)
    82  	for i := 0; i < n; i++ {
    83  		typez[i] = t.In(i)
    84  	}
    85  	return typez
    86  }
    87  
    88  // return a function's return types
    89  func funcReturnTypes(f interface{}) []reflect.Type {
    90  	t := reflect.TypeOf(f)
    91  	n := t.NumOut()
    92  	typez := make([]reflect.Type, n)
    93  	for i := 0; i < n; i++ {
    94  		typez[i] = t.Out(i)
    95  	}
    96  	return typez
    97  }
    98  
    99  // function introspection
   100  // -----------------------------------------------------------------------------
   101  // rpc.json
   102  
   103  // jsonrpc calls grab the given method's function info and runs reflect.Call
   104  func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger *slog.Logger) http.HandlerFunc {
   105  	return func(w http.ResponseWriter, r *http.Request) {
   106  		b, err := io.ReadAll(r.Body)
   107  		if err != nil {
   108  			WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(types.JSONRPCStringID(""), errors.Wrap(err, "error reading request body")))
   109  			return
   110  		}
   111  		// if its an empty request (like from a browser),
   112  		// just display a list of functions
   113  		if len(b) == 0 {
   114  			writeListOfEndpoints(w, r, funcMap)
   115  			return
   116  		}
   117  
   118  		// first try to unmarshal the incoming request as an array of RPC requests
   119  		var (
   120  			requests  types.RPCRequests
   121  			responses types.RPCResponses
   122  		)
   123  		if err := json.Unmarshal(b, &requests); err != nil {
   124  			// next, try to unmarshal as a single request
   125  			var request types.RPCRequest
   126  			if err := json.Unmarshal(b, &request); err != nil {
   127  				WriteRPCResponseHTTP(w, types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "error unmarshalling request")))
   128  				return
   129  			}
   130  			requests = []types.RPCRequest{request}
   131  		}
   132  
   133  		for _, request := range requests {
   134  			request := request
   135  			// A Notification is a Request object without an "id" member.
   136  			// The Server MUST NOT reply to a Notification, including those that are within a batch request.
   137  			if request.ID == types.JSONRPCStringID("") {
   138  				logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)")
   139  				continue
   140  			}
   141  			if len(r.URL.Path) > 1 {
   142  				responses = append(responses, types.RPCInvalidRequestError(request.ID, errors.New("path %s is invalid", r.URL.Path)))
   143  				continue
   144  			}
   145  			rpcFunc, ok := funcMap[request.Method]
   146  			if !ok || rpcFunc.ws {
   147  				responses = append(responses, types.RPCMethodNotFoundError(request.ID))
   148  				continue
   149  			}
   150  			ctx := &types.Context{JSONReq: &request, HTTPReq: r}
   151  			args := []reflect.Value{reflect.ValueOf(ctx)}
   152  			if len(request.Params) > 0 {
   153  				fnArgs, err := jsonParamsToArgs(rpcFunc, request.Params)
   154  				if err != nil {
   155  					responses = append(responses, types.RPCInvalidParamsError(request.ID, errors.Wrap(err, "error converting json params to arguments")))
   156  					continue
   157  				}
   158  				args = append(args, fnArgs...)
   159  			}
   160  			returns := rpcFunc.f.Call(args)
   161  			logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
   162  			result, err := unreflectResult(returns)
   163  			if err != nil {
   164  				responses = append(responses, types.RPCInternalError(request.ID, err))
   165  				continue
   166  			}
   167  			responses = append(responses, types.NewRPCSuccessResponse(request.ID, result))
   168  		}
   169  		if len(responses) > 0 {
   170  			WriteRPCResponseArrayHTTP(w, responses)
   171  		}
   172  	}
   173  }
   174  
   175  func handleInvalidJSONRPCPaths(next http.HandlerFunc) http.HandlerFunc {
   176  	return func(w http.ResponseWriter, r *http.Request) {
   177  		// Since the pattern "/" matches all paths not matched by other registered patterns we check whether the path is indeed
   178  		// "/", otherwise return a 404 error
   179  		if r.URL.Path != "/" {
   180  			http.NotFound(w, r)
   181  			return
   182  		}
   183  
   184  		next(w, r)
   185  	}
   186  }
   187  
   188  func mapParamsToArgs(rpcFunc *RPCFunc, params map[string]json.RawMessage, argsOffset int) ([]reflect.Value, error) {
   189  	values := make([]reflect.Value, len(rpcFunc.argNames))
   190  	for i, argName := range rpcFunc.argNames {
   191  		argType := rpcFunc.args[i+argsOffset]
   192  
   193  		if p, ok := params[argName]; ok && p != nil && len(p) > 0 {
   194  			val := reflect.New(argType)
   195  			err := amino.UnmarshalJSON(p, val.Interface())
   196  			if err != nil {
   197  				return nil, err
   198  			}
   199  			values[i] = val.Elem()
   200  		} else { // use default for that type
   201  			values[i] = reflect.Zero(argType)
   202  		}
   203  	}
   204  
   205  	return values, nil
   206  }
   207  
   208  func arrayParamsToArgs(rpcFunc *RPCFunc, params []json.RawMessage, argsOffset int) ([]reflect.Value, error) {
   209  	if len(rpcFunc.argNames) != len(params) {
   210  		return nil, errors.New("expected %v parameters (%v), got %v (%v)",
   211  			len(rpcFunc.argNames), rpcFunc.argNames, len(params), params)
   212  	}
   213  
   214  	values := make([]reflect.Value, len(params))
   215  	for i, p := range params {
   216  		argType := rpcFunc.args[i+argsOffset]
   217  		val := reflect.New(argType)
   218  		err := amino.UnmarshalJSON(p, val.Interface())
   219  		if err != nil {
   220  			return nil, err
   221  		}
   222  		values[i] = val.Elem()
   223  	}
   224  	return values, nil
   225  }
   226  
   227  // raw is unparsed json (from json.RawMessage) encoding either a map or an
   228  // array.
   229  //
   230  // Example:
   231  //
   232  //	rpcFunc.args = [rpctypes.Context string]
   233  //	rpcFunc.argNames = ["arg"]
   234  func jsonParamsToArgs(rpcFunc *RPCFunc, raw []byte) ([]reflect.Value, error) {
   235  	const argsOffset = 1
   236  
   237  	// TODO: Make more efficient, perhaps by checking the first character for '{' or '['?
   238  	// First, try to get the map.
   239  	var m map[string]json.RawMessage
   240  	err := json.Unmarshal(raw, &m)
   241  	if err == nil {
   242  		return mapParamsToArgs(rpcFunc, m, argsOffset)
   243  	}
   244  
   245  	// Otherwise, try an array.
   246  	var a []json.RawMessage
   247  	err = json.Unmarshal(raw, &a)
   248  	if err == nil {
   249  		return arrayParamsToArgs(rpcFunc, a, argsOffset)
   250  	}
   251  
   252  	// Otherwise, bad format, we cannot parse
   253  	return nil, errors.New("unknown type for JSON params: %v. Expected map or array", err)
   254  }
   255  
   256  // rpc.json
   257  // -----------------------------------------------------------------------------
   258  // rpc.http
   259  
   260  // convert from a function name to the http handler
   261  func makeHTTPHandler(rpcFunc *RPCFunc, logger *slog.Logger) func(http.ResponseWriter, *http.Request) {
   262  	// Exception for websocket endpoints
   263  	if rpcFunc.ws {
   264  		return func(w http.ResponseWriter, r *http.Request) {
   265  			WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(types.JSONRPCStringID("")))
   266  		}
   267  	}
   268  
   269  	// All other endpoints
   270  	return func(w http.ResponseWriter, r *http.Request) {
   271  		logger.Debug("HTTP HANDLER", "req", r)
   272  
   273  		ctx := &types.Context{HTTPReq: r}
   274  		args := []reflect.Value{reflect.ValueOf(ctx)}
   275  
   276  		fnArgs, err := httpParamsToArgs(rpcFunc, r)
   277  		if err != nil {
   278  			WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(types.JSONRPCStringID(""), errors.Wrap(err, "error converting http params to arguments")))
   279  			return
   280  		}
   281  		args = append(args, fnArgs...)
   282  
   283  		returns := rpcFunc.f.Call(args)
   284  
   285  		logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
   286  		result, err := unreflectResult(returns)
   287  		if err != nil {
   288  			WriteRPCResponseHTTP(w, types.RPCInternalError(types.JSONRPCStringID(""), err))
   289  			return
   290  		}
   291  		WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(types.JSONRPCStringID(""), result))
   292  	}
   293  }
   294  
   295  // Convert an http query to a list of properly typed values.
   296  // To be properly decoded the arg must be a concrete type from tendermint (if its an interface).
   297  func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
   298  	// skip types.Context
   299  	const argsOffset = 1
   300  
   301  	values := make([]reflect.Value, len(rpcFunc.argNames))
   302  
   303  	for i, name := range rpcFunc.argNames {
   304  		argType := rpcFunc.args[i+argsOffset]
   305  
   306  		values[i] = reflect.Zero(argType) // set default for that type
   307  
   308  		arg := GetParam(r, name)
   309  		// log.Notice("param to arg", "argType", argType, "name", name, "arg", arg)
   310  
   311  		if arg == "" {
   312  			continue
   313  		}
   314  
   315  		v, err, ok := nonJSONStringToArg(argType, arg)
   316  		if err != nil {
   317  			return nil, err
   318  		}
   319  		if ok {
   320  			values[i] = v
   321  			continue
   322  		}
   323  
   324  		values[i], err = jsonStringToArg(argType, arg)
   325  		if err != nil {
   326  			return nil, err
   327  		}
   328  	}
   329  
   330  	return values, nil
   331  }
   332  
   333  func jsonStringToArg(rt reflect.Type, arg string) (reflect.Value, error) {
   334  	rv := reflect.New(rt)
   335  	err := amino.UnmarshalJSON([]byte(arg), rv.Interface())
   336  	if err != nil {
   337  		return rv, err
   338  	}
   339  	rv = rv.Elem()
   340  	return rv, nil
   341  }
   342  
   343  func nonJSONStringToArg(rt reflect.Type, arg string) (reflect.Value, error, bool) {
   344  	if rt.Kind() == reflect.Ptr {
   345  		rv_, err, ok := nonJSONStringToArg(rt.Elem(), arg)
   346  		switch {
   347  		case err != nil:
   348  			return reflect.Value{}, err, false
   349  		case ok:
   350  			rv := reflect.New(rt.Elem())
   351  			rv.Elem().Set(rv_)
   352  			return rv, nil, true
   353  		default:
   354  			return reflect.Value{}, nil, false
   355  		}
   356  	} else {
   357  		return _nonJSONStringToArg(rt, arg)
   358  	}
   359  }
   360  
   361  var reInt = regexp.MustCompile(`^-?[0-9]+$`)
   362  
   363  // NOTE: rt.Kind() isn't a pointer.
   364  func _nonJSONStringToArg(rt reflect.Type, arg string) (reflect.Value, error, bool) {
   365  	isIntString := reInt.Match([]byte(arg))
   366  	isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`)
   367  	isHexString := strings.HasPrefix(strings.ToLower(arg), "0x")
   368  
   369  	var expectingString, expectingByteSlice, expectingInt bool
   370  	switch rt.Kind() {
   371  	case reflect.Int, reflect.Uint, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
   372  		expectingInt = true
   373  	case reflect.String:
   374  		expectingString = true
   375  	case reflect.Slice:
   376  		expectingByteSlice = rt.Elem().Kind() == reflect.Uint8
   377  	}
   378  
   379  	if isIntString && expectingInt {
   380  		qarg := `"` + arg + `"`
   381  		// jsonStringToArg
   382  		rv, err := jsonStringToArg(rt, qarg)
   383  		if err != nil {
   384  			return rv, err, false
   385  		}
   386  
   387  		return rv, nil, true
   388  	}
   389  
   390  	if isHexString {
   391  		if !expectingString && !expectingByteSlice {
   392  			err := errors.New("got a hex string arg, but expected '%s'",
   393  				rt.Kind().String())
   394  			return reflect.ValueOf(nil), err, false
   395  		}
   396  
   397  		var value []byte
   398  		value, err := hex.DecodeString(arg[2:])
   399  		if err != nil {
   400  			return reflect.ValueOf(nil), err, false
   401  		}
   402  		if rt.Kind() == reflect.String {
   403  			return reflect.ValueOf(string(value)), nil, true
   404  		}
   405  		return reflect.ValueOf(value), nil, true
   406  	}
   407  
   408  	if isQuotedString && expectingByteSlice {
   409  		v := reflect.New(reflect.TypeOf(""))
   410  		err := amino.UnmarshalJSON([]byte(arg), v.Interface())
   411  		if err != nil {
   412  			return reflect.ValueOf(nil), err, false
   413  		}
   414  		v = v.Elem()
   415  		return reflect.ValueOf([]byte(v.String())), nil, true
   416  	}
   417  
   418  	return reflect.ValueOf(nil), nil, false
   419  }
   420  
   421  // rpc.http
   422  // -----------------------------------------------------------------------------
   423  // rpc.websocket
   424  
   425  const (
   426  	defaultWSWriteChanCapacity = 1000
   427  	defaultWSWriteWait         = 10 * time.Second
   428  	defaultWSReadWait          = 30 * time.Second
   429  	defaultWSPingPeriod        = (defaultWSReadWait * 9) / 10
   430  )
   431  
   432  // A single websocket connection contains listener id, underlying ws
   433  // connection.
   434  //
   435  // In case of an error, the connection is stopped.
   436  type wsConnection struct {
   437  	service.BaseService
   438  
   439  	remoteAddr string
   440  	baseConn   *websocket.Conn
   441  	writeChan  chan types.RPCResponses
   442  
   443  	funcMap map[string]*RPCFunc
   444  
   445  	// write channel capacity
   446  	writeChanCapacity int
   447  
   448  	// each write times out after this.
   449  	writeWait time.Duration
   450  
   451  	// Connection times out if we haven't received *anything* in this long, not even pings.
   452  	readWait time.Duration
   453  
   454  	// Send pings to server with this period. Must be less than readWait, but greater than zero.
   455  	pingPeriod time.Duration
   456  
   457  	// Maximum message size.
   458  	readLimit int64
   459  
   460  	// callback which is called upon disconnect
   461  	onDisconnect func(remoteAddr string)
   462  
   463  	ctx    context.Context
   464  	cancel context.CancelFunc
   465  }
   466  
   467  // NewWSConnection wraps websocket.Conn.
   468  //
   469  // See the commentary on the func(*wsConnection) functions for a detailed
   470  // description of how to configure ping period and pong wait time. NOTE: if the
   471  // write buffer is full, pongs may be dropped, which may cause clients to
   472  // disconnect. see https://github.com/gorilla/websocket/issues/97
   473  func NewWSConnection(
   474  	baseConn *websocket.Conn,
   475  	funcMap map[string]*RPCFunc,
   476  	options ...func(*wsConnection),
   477  ) *wsConnection {
   478  	wsc := &wsConnection{
   479  		remoteAddr:        baseConn.RemoteAddr().String(),
   480  		baseConn:          baseConn,
   481  		funcMap:           funcMap,
   482  		writeWait:         defaultWSWriteWait,
   483  		writeChanCapacity: defaultWSWriteChanCapacity,
   484  		readWait:          defaultWSReadWait,
   485  		pingPeriod:        defaultWSPingPeriod,
   486  	}
   487  	for _, option := range options {
   488  		option(wsc)
   489  	}
   490  	wsc.baseConn.SetReadLimit(wsc.readLimit)
   491  	wsc.BaseService = *service.NewBaseService(nil, "wsConnection", wsc)
   492  	return wsc
   493  }
   494  
   495  // OnDisconnect sets a callback which is used upon disconnect - not
   496  // Goroutine-safe. Nop by default.
   497  func OnDisconnect(onDisconnect func(remoteAddr string)) func(*wsConnection) {
   498  	return func(wsc *wsConnection) {
   499  		wsc.onDisconnect = onDisconnect
   500  	}
   501  }
   502  
   503  // WriteWait sets the amount of time to wait before a websocket write times out.
   504  // It should only be used in the constructor - not Goroutine-safe.
   505  func WriteWait(writeWait time.Duration) func(*wsConnection) {
   506  	return func(wsc *wsConnection) {
   507  		wsc.writeWait = writeWait
   508  	}
   509  }
   510  
   511  // WriteChanCapacity sets the capacity of the websocket write channel.
   512  // It should only be used in the constructor - not Goroutine-safe.
   513  func WriteChanCapacity(capacity int) func(*wsConnection) {
   514  	return func(wsc *wsConnection) {
   515  		wsc.writeChanCapacity = capacity
   516  	}
   517  }
   518  
   519  // ReadWait sets the amount of time to wait before a websocket read times out.
   520  // It should only be used in the constructor - not Goroutine-safe.
   521  func ReadWait(readWait time.Duration) func(*wsConnection) {
   522  	return func(wsc *wsConnection) {
   523  		wsc.readWait = readWait
   524  	}
   525  }
   526  
   527  // PingPeriod sets the duration for sending websocket pings.
   528  // It should only be used in the constructor - not Goroutine-safe.
   529  func PingPeriod(pingPeriod time.Duration) func(*wsConnection) {
   530  	return func(wsc *wsConnection) {
   531  		wsc.pingPeriod = pingPeriod
   532  	}
   533  }
   534  
   535  // ReadLimit sets the maximum size for reading message.
   536  // It should only be used in the constructor - not Goroutine-safe.
   537  func ReadLimit(readLimit int64) func(*wsConnection) {
   538  	return func(wsc *wsConnection) {
   539  		wsc.readLimit = readLimit
   540  	}
   541  }
   542  
   543  // OnStart implements service.Service by starting the read and write routines. It
   544  // blocks until the connection closes.
   545  func (wsc *wsConnection) OnStart() error {
   546  	wsc.writeChan = make(chan types.RPCResponses, wsc.writeChanCapacity)
   547  
   548  	// Read subscriptions/unsubscriptions to events
   549  	go wsc.readRoutine()
   550  	// Write responses, BLOCKING.
   551  	wsc.writeRoutine()
   552  
   553  	return nil
   554  }
   555  
   556  // OnStop implements service.Service by unsubscribing remoteAddr from all subscriptions.
   557  func (wsc *wsConnection) OnStop() {
   558  	// Both read and write loops close the websocket connection when they exit their loops.
   559  	// The writeChan is never closed, to allow WriteRPCResponses() to fail.
   560  
   561  	if wsc.onDisconnect != nil {
   562  		wsc.onDisconnect(wsc.remoteAddr)
   563  	}
   564  
   565  	if wsc.ctx != nil {
   566  		wsc.cancel()
   567  	}
   568  }
   569  
   570  // GetRemoteAddr returns the remote address of the underlying connection.
   571  // It implements WSRPCConnection
   572  func (wsc *wsConnection) GetRemoteAddr() string {
   573  	return wsc.remoteAddr
   574  }
   575  
   576  // WriteRPCResponse pushes a response to the writeChan, and blocks until it is accepted.
   577  // It implements WSRPCConnection. It is Goroutine-safe.
   578  func (wsc *wsConnection) WriteRPCResponses(resp types.RPCResponses) {
   579  	select {
   580  	case <-wsc.Quit():
   581  		return
   582  	case wsc.writeChan <- resp:
   583  	}
   584  }
   585  
   586  // TryWriteRPCResponse attempts to push a response to the writeChan, but does not block.
   587  // It implements WSRPCConnection. It is Goroutine-safe
   588  func (wsc *wsConnection) TryWriteRPCResponses(resp types.RPCResponses) bool {
   589  	select {
   590  	case <-wsc.Quit():
   591  		return false
   592  	case wsc.writeChan <- resp:
   593  		return true
   594  	default:
   595  		return false
   596  	}
   597  }
   598  
   599  // Context returns the connection's context.
   600  // The context is canceled when the client's connection closes.
   601  func (wsc *wsConnection) Context() context.Context {
   602  	if wsc.ctx != nil {
   603  		return wsc.ctx
   604  	}
   605  	wsc.ctx, wsc.cancel = context.WithCancel(context.Background())
   606  	return wsc.ctx
   607  }
   608  
   609  // Read from the socket and subscribe to or unsubscribe from events
   610  func (wsc *wsConnection) readRoutine() {
   611  	defer func() {
   612  		if r := recover(); r != nil {
   613  			err, ok := r.(error)
   614  			if !ok {
   615  				err = fmt.Errorf("WSJSONRPC: %v", r)
   616  			}
   617  			wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack()))
   618  			wsc.WriteRPCResponses(types.RPCResponses{types.RPCInternalError(types.JSONRPCStringID("unknown"), err)})
   619  			go wsc.readRoutine()
   620  		} else {
   621  			wsc.baseConn.Close() //nolint: errcheck
   622  		}
   623  	}()
   624  
   625  	wsc.baseConn.SetPongHandler(func(m string) error {
   626  		return wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait))
   627  	})
   628  
   629  	for {
   630  		select {
   631  		case <-wsc.Quit():
   632  			return
   633  		default:
   634  			// reset deadline for every type of message (control or data)
   635  			if err := wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)); err != nil {
   636  				wsc.Logger.Error("failed to set read deadline", "err", err)
   637  			}
   638  			var in []byte
   639  			_, in, err := wsc.baseConn.ReadMessage()
   640  			if err != nil {
   641  				if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
   642  					wsc.Logger.Info("Client closed the connection")
   643  				} else {
   644  					wsc.Logger.Error("Failed to read request", "err", err)
   645  				}
   646  				wsc.Stop()
   647  				return
   648  			}
   649  
   650  			// first try to unmarshal the incoming request as an array of RPC requests
   651  			var (
   652  				requests  types.RPCRequests
   653  				responses types.RPCResponses
   654  			)
   655  
   656  			// Try to unmarshal the requests as a batch
   657  			if err := json.Unmarshal(in, &requests); err != nil {
   658  				// Next, try to unmarshal as a single request
   659  				var request types.RPCRequest
   660  				if err := json.Unmarshal(in, &request); err != nil {
   661  					wsc.WriteRPCResponses(
   662  						types.RPCResponses{
   663  							types.RPCParseError(
   664  								types.JSONRPCStringID(""),
   665  								errors.Wrap(err, "error unmarshalling request"),
   666  							),
   667  						},
   668  					)
   669  
   670  					return
   671  				}
   672  
   673  				requests = []types.RPCRequest{request}
   674  			}
   675  
   676  			for _, request := range requests {
   677  				request := request
   678  
   679  				// A Notification is a Request object without an "id" member.
   680  				// The Server MUST NOT reply to a Notification, including those that are within a batch request.
   681  				if request.ID == types.JSONRPCStringID("") {
   682  					wsc.Logger.Debug("Skipping notification JSON-RPC request")
   683  
   684  					continue
   685  				}
   686  
   687  				// Now, fetch the RPCFunc and execute it.
   688  				rpcFunc := wsc.funcMap[request.Method]
   689  				if rpcFunc == nil {
   690  					responses = append(responses, types.RPCMethodNotFoundError(request.ID))
   691  
   692  					continue
   693  				}
   694  
   695  				ctx := &types.Context{JSONReq: &request, WSConn: wsc}
   696  				args := []reflect.Value{reflect.ValueOf(ctx)}
   697  				if len(request.Params) > 0 {
   698  					fnArgs, err := jsonParamsToArgs(rpcFunc, request.Params)
   699  					if err != nil {
   700  						responses = append(responses, types.RPCInternalError(request.ID, errors.Wrap(err, "error converting json params to arguments")))
   701  
   702  						continue
   703  					}
   704  					args = append(args, fnArgs...)
   705  				}
   706  
   707  				returns := rpcFunc.f.Call(args)
   708  
   709  				// TODO: Need to encode args/returns to string if we want to log them
   710  				wsc.Logger.Info("WSJSONRPC", "method", request.Method)
   711  
   712  				result, err := unreflectResult(returns)
   713  				if err != nil {
   714  					responses = append(responses, types.RPCInternalError(request.ID, err))
   715  
   716  					continue
   717  				}
   718  
   719  				responses = append(responses, types.NewRPCSuccessResponse(request.ID, result))
   720  
   721  				if len(responses) > 0 {
   722  					wsc.WriteRPCResponses(responses)
   723  				}
   724  			}
   725  		}
   726  	}
   727  }
   728  
   729  // receives on a write channel and writes out on the socket
   730  func (wsc *wsConnection) writeRoutine() {
   731  	pingTicker := time.NewTicker(wsc.pingPeriod)
   732  	defer func() {
   733  		pingTicker.Stop()
   734  		if err := wsc.baseConn.Close(); err != nil {
   735  			wsc.Logger.Error("Error closing connection", "err", err)
   736  		}
   737  	}()
   738  
   739  	// https://github.com/gorilla/websocket/issues/97
   740  	pongs := make(chan string, 1)
   741  	wsc.baseConn.SetPingHandler(func(m string) error {
   742  		select {
   743  		case pongs <- m:
   744  		default:
   745  		}
   746  		return nil
   747  	})
   748  
   749  	for {
   750  		select {
   751  		case m := <-pongs:
   752  			err := wsc.writeMessageWithDeadline(websocket.PongMessage, []byte(m))
   753  			if err != nil {
   754  				wsc.Logger.Info("Failed to write pong (client may disconnect)", "err", err)
   755  			}
   756  		case <-pingTicker.C:
   757  			err := wsc.writeMessageWithDeadline(websocket.PingMessage, []byte{})
   758  			if err != nil {
   759  				wsc.Logger.Error("Failed to write ping", "err", err)
   760  				wsc.Stop()
   761  				return
   762  			}
   763  		case msgs := <-wsc.writeChan:
   764  			var writeData any
   765  
   766  			if len(msgs) == 1 {
   767  				writeData = msgs[0]
   768  			} else {
   769  				writeData = msgs
   770  			}
   771  
   772  			jsonBytes, err := json.MarshalIndent(writeData, "", "  ")
   773  			if err != nil {
   774  				wsc.Logger.Error("Failed to marshal RPCResponse to JSON", "err", err)
   775  			} else if err = wsc.writeMessageWithDeadline(websocket.TextMessage, jsonBytes); err != nil {
   776  				wsc.Logger.Error("Failed to write response", "err", err)
   777  				wsc.Stop()
   778  				return
   779  			}
   780  		case <-wsc.Quit():
   781  			return
   782  		}
   783  	}
   784  }
   785  
   786  // All writes to the websocket must (re)set the write deadline.
   787  // If some writes don't set it while others do, they may timeout incorrectly (https://github.com/gnolang/gno/tm2/pkg/bft/issues/553)
   788  func (wsc *wsConnection) writeMessageWithDeadline(msgType int, msg []byte) error {
   789  	if err := wsc.baseConn.SetWriteDeadline(time.Now().Add(wsc.writeWait)); err != nil {
   790  		return err
   791  	}
   792  	return wsc.baseConn.WriteMessage(msgType, msg)
   793  }
   794  
   795  // ----------------------------------------
   796  
   797  // WebsocketManager provides a WS handler for incoming connections and passes a
   798  // map of functions along with any additional params to new connections.
   799  // NOTE: The websocket path is defined externally, e.g. in node/node.go
   800  type WebsocketManager struct {
   801  	websocket.Upgrader
   802  
   803  	funcMap       map[string]*RPCFunc
   804  	logger        *slog.Logger
   805  	wsConnOptions []func(*wsConnection)
   806  }
   807  
   808  // NewWebsocketManager returns a new WebsocketManager that passes a map of
   809  // functions, connection options and logger to new WS connections.
   810  func NewWebsocketManager(funcMap map[string]*RPCFunc, wsConnOptions ...func(*wsConnection)) *WebsocketManager {
   811  	return &WebsocketManager{
   812  		funcMap: funcMap,
   813  		Upgrader: websocket.Upgrader{
   814  			CheckOrigin: func(r *http.Request) bool {
   815  				// TODO ???
   816  				return true
   817  			},
   818  		},
   819  		logger:        log.NewNoopLogger(),
   820  		wsConnOptions: wsConnOptions,
   821  	}
   822  }
   823  
   824  // SetLogger sets the logger.
   825  func (wm *WebsocketManager) SetLogger(l *slog.Logger) {
   826  	wm.logger = l
   827  }
   828  
   829  // WebsocketHandler upgrades the request/response (via http.Hijack) and starts
   830  // the wsConnection.
   831  func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) {
   832  	wsConn, err := wm.Upgrade(w, r, nil)
   833  	if err != nil {
   834  		// TODO - return http error
   835  		wm.logger.Error("Failed to upgrade to websocket connection", "err", err)
   836  		return
   837  	}
   838  
   839  	// register connection
   840  	con := NewWSConnection(wsConn, wm.funcMap, wm.wsConnOptions...)
   841  	con.SetLogger(wm.logger.With("remote", wsConn.RemoteAddr()))
   842  	wm.logger.Info("New websocket connection", "remote", con.remoteAddr)
   843  	err = con.Start() // Blocking
   844  	if err != nil {
   845  		wm.logger.Error("Error starting connection", "err", err)
   846  	}
   847  }
   848  
   849  // rpc.websocket
   850  // -----------------------------------------------------------------------------
   851  
   852  // NOTE: assume returns is result struct and error. If error is not nil, return it
   853  func unreflectResult(returns []reflect.Value) (interface{}, error) {
   854  	errV := returns[1]
   855  	if errV.Interface() != nil {
   856  		return nil, errors.New("%v", errV.Interface())
   857  	}
   858  	rv := returns[0]
   859  	// If the result is a registered interface, we need a pointer to it so
   860  	// we can marshal with type info.
   861  	if rv.Kind() == reflect.Interface {
   862  		rvp := reflect.New(rv.Type())
   863  		rvp.Elem().Set(rv)
   864  		return rvp.Interface(), nil
   865  	} else {
   866  		return rv.Interface(), nil
   867  	}
   868  }
   869  
   870  // writes a list of available rpc endpoints as an html page
   871  func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) {
   872  	noArgNames := []string{}
   873  	argNames := []string{}
   874  	for name, funcData := range funcMap {
   875  		if len(funcData.args) == 0 {
   876  			noArgNames = append(noArgNames, name)
   877  		} else {
   878  			argNames = append(argNames, name)
   879  		}
   880  	}
   881  	sort.Strings(noArgNames)
   882  	sort.Strings(argNames)
   883  	buf := new(bytes.Buffer)
   884  	buf.WriteString("<html><body>")
   885  	buf.WriteString("<br>Available endpoints:<br>")
   886  
   887  	for _, name := range noArgNames {
   888  		link := fmt.Sprintf("//%s/%s", r.Host, name)
   889  		buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link))
   890  	}
   891  
   892  	buf.WriteString("<br>Endpoints that require arguments:<br>")
   893  	for _, name := range argNames {
   894  		link := fmt.Sprintf("//%s/%s?", r.Host, name)
   895  		funcData := funcMap[name]
   896  		for i, argName := range funcData.argNames {
   897  			link += argName + "=_"
   898  			if i < len(funcData.argNames)-1 {
   899  				link += "&"
   900  			}
   901  		}
   902  		buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link))
   903  	}
   904  	buf.WriteString("</body></html>")
   905  	w.Header().Set("Content-Type", "text/html")
   906  	w.WriteHeader(200)
   907  	w.Write(buf.Bytes()) //nolint: errcheck
   908  }