From eeaabed4bfa2da0e130291322037a41e332a9d79 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Sat, 20 Sep 2025 08:42:34 +0100 Subject: [PATCH 1/9] chore: add gorilla/rpc --- go.mod | 1 + go.sum | 2 + node/get_status_node.go | 2 + vendor/github.com/gorilla/rpc/.editorconfig | 20 ++ vendor/github.com/gorilla/rpc/.gitignore | 1 + vendor/github.com/gorilla/rpc/LICENSE | 27 ++ vendor/github.com/gorilla/rpc/Makefile | 34 +++ vendor/github.com/gorilla/rpc/README.md | 13 + vendor/github.com/gorilla/rpc/doc.go | 81 ++++++ vendor/github.com/gorilla/rpc/map.go | 180 +++++++++++++ vendor/github.com/gorilla/rpc/server.go | 269 ++++++++++++++++++++ vendor/modules.txt | 3 + 12 files changed, 633 insertions(+) create mode 100644 vendor/github.com/gorilla/rpc/.editorconfig create mode 100644 vendor/github.com/gorilla/rpc/.gitignore create mode 100644 vendor/github.com/gorilla/rpc/LICENSE create mode 100644 vendor/github.com/gorilla/rpc/Makefile create mode 100644 vendor/github.com/gorilla/rpc/README.md create mode 100644 vendor/github.com/gorilla/rpc/doc.go create mode 100644 vendor/github.com/gorilla/rpc/map.go create mode 100644 vendor/github.com/gorilla/rpc/server.go diff --git a/go.mod b/go.mod index 7b807d04f9d..f13a6648f43 100644 --- a/go.mod +++ b/go.mod @@ -85,6 +85,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 github.com/cenkalti/backoff/v4 v4.2.1 github.com/getsentry/sentry-go v0.29.1 + github.com/gorilla/rpc v1.2.1 github.com/gorilla/sessions v1.2.1 github.com/gorilla/websocket v1.5.3 github.com/ipfs/go-log/v2 v2.5.1 diff --git a/go.sum b/go.sum index fbd2a104809..03b160fd79c 100644 --- a/go.sum +++ b/go.sum @@ -1084,6 +1084,8 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k= +github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= diff --git a/node/get_status_node.go b/node/get_status_node.go index f0f69216b4e..05f20d35c49 100644 --- a/node/get_status_node.go +++ b/node/get_status_node.go @@ -19,6 +19,8 @@ import ( "github.com/ethereum/go-ethereum/event" gethrpc "github.com/ethereum/go-ethereum/rpc" + _ "github.com/gorilla/rpc" + accsmanagement "github.com/status-im/status-go/accounts-management" common2 "github.com/status-im/status-go/common" "github.com/status-im/status-go/connection" diff --git a/vendor/github.com/gorilla/rpc/.editorconfig b/vendor/github.com/gorilla/rpc/.editorconfig new file mode 100644 index 00000000000..2940ec92ac2 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/.editorconfig @@ -0,0 +1,20 @@ +; https://editorconfig.org/ + +root = true + +[*] +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[{Makefile,go.mod,go.sum,*.go,.gitmodules}] +indent_style = tab +indent_size = 4 + +[*.md] +indent_size = 4 +trim_trailing_whitespace = false + +eclint_indent_style = unset diff --git a/vendor/github.com/gorilla/rpc/.gitignore b/vendor/github.com/gorilla/rpc/.gitignore new file mode 100644 index 00000000000..84039fec687 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/.gitignore @@ -0,0 +1 @@ +coverage.coverprofile diff --git a/vendor/github.com/gorilla/rpc/LICENSE b/vendor/github.com/gorilla/rpc/LICENSE new file mode 100644 index 00000000000..bb9d80bc9b6 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2023 The Gorilla Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gorilla/rpc/Makefile b/vendor/github.com/gorilla/rpc/Makefile new file mode 100644 index 00000000000..ac37ffd3297 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/Makefile @@ -0,0 +1,34 @@ +GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '') +GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +GO_SEC=$(shell which gosec 2> /dev/null || echo '') +GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest + +GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '') +GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest + +.PHONY: golangci-lint +golangci-lint: + $(if $(GO_LINT), ,go install $(GO_LINT_URI)) + @echo "##### Running golangci-lint" + golangci-lint run -v + +.PHONY: gosec +gosec: + $(if $(GO_SEC), ,go install $(GO_SEC_URI)) + @echo "##### Running gosec" + gosec ./... + +.PHONY: govulncheck +govulncheck: + $(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI)) + @echo "##### Running govulncheck" + govulncheck ./... + +.PHONY: verify +verify: golangci-lint gosec govulncheck + +.PHONY: test +test: + @echo "##### Running tests" + go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./... diff --git a/vendor/github.com/gorilla/rpc/README.md b/vendor/github.com/gorilla/rpc/README.md new file mode 100644 index 00000000000..063674a57cd --- /dev/null +++ b/vendor/github.com/gorilla/rpc/README.md @@ -0,0 +1,13 @@ +# gorilla/rpc + +![testing](https://github.com/gorilla/rpc/actions/workflows/test.yml/badge.svg) +[![codecov](https://codecov.io/github/gorilla/rpc/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/rpc) +[![godoc](https://godoc.org/github.com/gorilla/rpc?status.svg)](https://godoc.org/github.com/gorilla/rpc) +[![sourcegraph](https://sourcegraph.com/github.com/gorilla/rpc/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/rpc?badge) + + +![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5) + +gorilla/rpc is a foundation for RPC over HTTP services, providing access to the exported methods of an object through HTTP requests. + +Read the full documentation here: https://www.gorillatoolkit.org/pkg/rpc diff --git a/vendor/github.com/gorilla/rpc/doc.go b/vendor/github.com/gorilla/rpc/doc.go new file mode 100644 index 00000000000..8ad8475bc42 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/doc.go @@ -0,0 +1,81 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package gorilla/rpc is a foundation for RPC over HTTP services, providing +access to the exported methods of an object through HTTP requests. + +This package derives from the standard net/rpc package but uses a single HTTP +request per call instead of persistent connections. Other differences +compared to net/rpc: + + - Multiple codecs can be registered in the same server. + - A codec is chosen based on the "Content-Type" header from the request. + - Service methods also receive http.Request as parameter. + - This package can be used on Google App Engine. + +Let's setup a server and register a codec and service: + + import ( + "net/http" + "github.com/gorilla/rpc" + "github.com/gorilla/rpc/json" + ) + + func init() { + s := rpc.NewServer() + s.RegisterCodec(json.NewCodec(), "application/json") + s.RegisterService(new(HelloService), "") + http.Handle("/rpc", s) + } + +This server handles requests to the "/rpc" path using a JSON codec. +A codec is tied to a content type. In the example above, the JSON codec is +registered to serve requests with "application/json" as the value for the +"Content-Type" header. If the header includes a charset definition, it is +ignored; only the media-type part is taken into account. + +A service can be registered using a name. If the name is empty, like in the +example above, it will be inferred from the service type. + +That's all about the server setup. Now let's define a simple service: + + type HelloArgs struct { + Who string + } + + type HelloReply struct { + Message string + } + + type HelloService struct {} + + func (h *HelloService) Say(r *http.Request, args *HelloArgs, reply *HelloReply) error { + reply.Message = "Hello, " + args.Who + "!" + return nil + } + +The example above defines a service with a method "HelloService.Say" and +the arguments and reply related to that method. + +The service must be exported (begin with an upper case letter) or local +(defined in the package registering the service). + +When a service is registered, the server inspects the service methods +and make available the ones that follow these rules: + + - The method name is exported. + - The method has three arguments: *http.Request, *args, *reply. + - All three arguments are pointers. + - The second and third arguments are exported or local. + - The method has return type error. + +All other methods are ignored. + +Gorilla has packages with common RPC codecs. Check out their documentation: + + JSON: http://gorilla-web.appspot.com/pkg/rpc/json +*/ +package rpc diff --git a/vendor/github.com/gorilla/rpc/map.go b/vendor/github.com/gorilla/rpc/map.go new file mode 100644 index 00000000000..433f275b8d4 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/map.go @@ -0,0 +1,180 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpc + +import ( + "fmt" + "net/http" + "reflect" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +var ( + // Precompute the reflect.Type of error and http.Request + typeOfError = reflect.TypeOf((*error)(nil)).Elem() + typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem() +) + +// ---------------------------------------------------------------------------- +// service +// ---------------------------------------------------------------------------- + +type service struct { + name string // name of service + rcvr reflect.Value // receiver of methods for the service + rcvrType reflect.Type // type of the receiver + methods map[string]*serviceMethod // registered methods + passReq bool +} + +type serviceMethod struct { + method reflect.Method // receiver method + argsType reflect.Type // type of the request argument + replyType reflect.Type // type of the response argument +} + +// ---------------------------------------------------------------------------- +// serviceMap +// ---------------------------------------------------------------------------- + +// serviceMap is a registry for services. +type serviceMap struct { + mutex sync.Mutex + services map[string]*service +} + +// register adds a new service using reflection to extract its methods. +func (m *serviceMap) register(rcvr interface{}, name string, passReq bool) error { + // Setup service. + s := &service{ + name: name, + rcvr: reflect.ValueOf(rcvr), + rcvrType: reflect.TypeOf(rcvr), + methods: make(map[string]*serviceMethod), + passReq: passReq, + } + if name == "" { + s.name = reflect.Indirect(s.rcvr).Type().Name() + if !isExported(s.name) { + return fmt.Errorf("rpc: type %q is not exported", s.name) + } + } + if s.name == "" { + return fmt.Errorf("rpc: no service name for type %q", + s.rcvrType.String()) + } + // Setup methods. + for i := 0; i < s.rcvrType.NumMethod(); i++ { + method := s.rcvrType.Method(i) + mtype := method.Type + + // offset the parameter indexes by one if the + // service methods accept an HTTP request pointer + var paramOffset int + if passReq { + paramOffset = 1 + } else { + paramOffset = 0 + } + + // Method must be exported. + if method.PkgPath != "" { + continue + } + // Method needs four ins: receiver, *http.Request, *args, *reply. + if mtype.NumIn() != 3+paramOffset { + continue + } + + // If the service methods accept an HTTP request pointer + if passReq { + // First argument must be a pointer and must be http.Request. + reqType := mtype.In(1) + if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest { + continue + } + } + // Next argument must be a pointer and must be exported. + args := mtype.In(1 + paramOffset) + if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) { + continue + } + // Next argument must be a pointer and must be exported. + reply := mtype.In(2 + paramOffset) + if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) { + continue + } + // Method needs one out: error. + if mtype.NumOut() != 1 { + continue + } + if returnType := mtype.Out(0); returnType != typeOfError { + continue + } + s.methods[method.Name] = &serviceMethod{ + method: method, + argsType: args.Elem(), + replyType: reply.Elem(), + } + } + if len(s.methods) == 0 { + return fmt.Errorf("rpc: %q has no exported methods of suitable type", + s.name) + } + // Add to the map. + m.mutex.Lock() + defer m.mutex.Unlock() + if m.services == nil { + m.services = make(map[string]*service) + } else if _, ok := m.services[s.name]; ok { + return fmt.Errorf("rpc: service already defined: %q", s.name) + } + m.services[s.name] = s + return nil +} + +// get returns a registered service given a method name. +// +// The method name uses a dotted notation as in "Service.Method". +func (m *serviceMap) get(method string) (*service, *serviceMethod, error) { + parts := strings.Split(method, ".") + if len(parts) != 2 { + err := fmt.Errorf("rpc: service/method request ill-formed: %q", method) + return nil, nil, err + } + m.mutex.Lock() + service := m.services[parts[0]] + m.mutex.Unlock() + if service == nil { + err := fmt.Errorf("rpc: can't find service %q", method) + return nil, nil, err + } + serviceMethod := service.methods[parts[1]] + if serviceMethod == nil { + err := fmt.Errorf("rpc: can't find method %q", method) + return nil, nil, err + } + return service, serviceMethod, nil +} + +// isExported returns true of a string is an exported (upper case) name. +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +// isExportedOrBuiltin returns true if a type is exported or a builtin. +func isExportedOrBuiltin(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + // PkgPath will be non-empty even for an exported type, + // so we need to check the type name as well. + return isExported(t.Name()) || t.PkgPath() == "" +} diff --git a/vendor/github.com/gorilla/rpc/server.go b/vendor/github.com/gorilla/rpc/server.go new file mode 100644 index 00000000000..76a32601498 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/server.go @@ -0,0 +1,269 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpc + +import ( + "fmt" + "net/http" + "reflect" + "strings" +) + +// ---------------------------------------------------------------------------- +// Codec +// ---------------------------------------------------------------------------- + +// Codec creates a CodecRequest to process each request. +type Codec interface { + NewRequest(*http.Request) CodecRequest +} + +// CodecRequest decodes a request and encodes a response using a specific +// serialization scheme. +type CodecRequest interface { + // Reads request and returns the RPC method name. + Method() (string, error) + // Reads request filling the RPC method args. + ReadRequest(interface{}) error + // Writes response using the RPC method reply. The error parameter is + // the error returned by the method call, if any. + WriteResponse(http.ResponseWriter, interface{}, error) error +} + +// ---------------------------------------------------------------------------- +// Server +// ---------------------------------------------------------------------------- + +// NewServer returns a new RPC server. +func NewServer() *Server { + return &Server{ + codecs: make(map[string]Codec), + services: new(serviceMap), + } +} + +// RequestInfo contains all the information we pass to before/after functions +type RequestInfo struct { + Method string + Error error + Request *http.Request + StatusCode int +} + +// Server serves registered RPC services using registered codecs. +type Server struct { + codecs map[string]Codec + services *serviceMap + interceptFunc func(i *RequestInfo) *http.Request + beforeFunc func(i *RequestInfo) + afterFunc func(i *RequestInfo) +} + +// RegisterCodec adds a new codec to the server. +// +// Codecs are defined to process a given serialization scheme, e.g., JSON or +// XML. A codec is chosen based on the "Content-Type" header from the request, +// excluding the charset definition. +func (s *Server) RegisterCodec(codec Codec, contentType string) { + s.codecs[strings.ToLower(contentType)] = codec +} + +// RegisterService adds a new service to the server. +// +// The name parameter is optional: if empty it will be inferred from +// the receiver type name. +// +// Methods from the receiver will be extracted if these rules are satisfied: +// +// - The receiver is exported (begins with an upper case letter) or local +// (defined in the package registering the service). +// - The method name is exported. +// - The method has three arguments: *http.Request, *args, *reply. +// - All three arguments are pointers. +// - The second and third arguments are exported or local. +// - The method has return type error. +// +// All other methods are ignored. +func (s *Server) RegisterService(receiver interface{}, name string) error { + return s.services.register(receiver, name, true) +} + +// RegisterTCPService adds a new TCP service to the server. +// No HTTP request struct will be passed to the service methods. +// +// The name parameter is optional: if empty it will be inferred from +// the receiver type name. +// +// Methods from the receiver will be extracted if these rules are satisfied: +// +// - The receiver is exported (begins with an upper case letter) or local +// (defined in the package registering the service). +// - The method name is exported. +// - The method has two arguments: *args, *reply. +// - Both arguments are pointers. +// - Both arguments are exported or local. +// - The method has return type error. +// +// All other methods are ignored. +func (s *Server) RegisterTCPService(receiver interface{}, name string) error { + return s.services.register(receiver, name, false) +} + +// HasMethod returns true if the given method is registered. +// +// The method uses a dotted notation as in "Service.Method". +func (s *Server) HasMethod(method string) bool { + if _, _, err := s.services.get(method); err == nil { + return true + } + return false +} + +// RegisterInterceptFunc registers the specified function as the function +// that will be called before every request. The function is allowed to intercept +// the request e.g. add values to the context. +// +// Note: Only one function can be registered, subsequent calls to this +// method will overwrite all the previous functions. +func (s *Server) RegisterInterceptFunc(f func(i *RequestInfo) *http.Request) { + s.interceptFunc = f +} + +// RegisterBeforeFunc registers the specified function as the function +// that will be called before every request. +// +// Note: Only one function can be registered, subsequent calls to this +// method will overwrite all the previous functions. +func (s *Server) RegisterBeforeFunc(f func(i *RequestInfo)) { + s.beforeFunc = f +} + +// RegisterAfterFunc registers the specified function as the function +// that will be called after every request +// +// Note: Only one function can be registered, subsequent calls to this +// method will overwrite all the previous functions. +func (s *Server) RegisterAfterFunc(f func(i *RequestInfo)) { + s.afterFunc = f +} + +// ServeHTTP +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + s.writeError(w, 405, "rpc: POST method required, received "+r.Method) + return + } + contentType := r.Header.Get("Content-Type") + idx := strings.Index(contentType, ";") + if idx != -1 { + contentType = contentType[:idx] + } + var codec Codec + if contentType == "" && len(s.codecs) == 1 { + // If Content-Type is not set and only one codec has been registered, + // then default to that codec. + for _, c := range s.codecs { + codec = c + } + } else if codec = s.codecs[strings.ToLower(contentType)]; codec == nil { + s.writeError(w, 415, "rpc: unrecognized Content-Type: "+contentType) + return + } + // Create a new codec request. + codecReq := codec.NewRequest(r) + // Get service method to be called. + method, errMethod := codecReq.Method() + if errMethod != nil { + s.writeError(w, 400, errMethod.Error()) + return + } + serviceSpec, methodSpec, errGet := s.services.get(method) + if errGet != nil { + s.writeError(w, 400, errGet.Error()) + return + } + // Decode the args. + args := reflect.New(methodSpec.argsType) + if errRead := codecReq.ReadRequest(args.Interface()); errRead != nil { + s.writeError(w, 400, errRead.Error()) + return + } + + // Call the registered Intercept Function + if s.interceptFunc != nil { + req := s.interceptFunc(&RequestInfo{ + Request: r, + Method: method, + }) + if req != nil { + r = req + } + } + // Call the registered Before Function + if s.beforeFunc != nil { + s.beforeFunc(&RequestInfo{ + Request: r, + Method: method, + }) + } + + // Call the service method. + reply := reflect.New(methodSpec.replyType) + + // omit the HTTP request if the service method doesn't accept it + var errValue []reflect.Value + if serviceSpec.passReq { + errValue = methodSpec.method.Func.Call([]reflect.Value{ + serviceSpec.rcvr, + reflect.ValueOf(r), + args, + reply, + }) + } else { + errValue = methodSpec.method.Func.Call([]reflect.Value{ + serviceSpec.rcvr, + args, + reply, + }) + } + + // Cast the result to error if needed. + var errResult error + errInter := errValue[0].Interface() + if errInter != nil { + errResult = errInter.(error) + } + + // Prevents Internet Explorer from MIME-sniffing a response away + // from the declared content-type + w.Header().Set("x-content-type-options", "nosniff") + // Encode the response. + if errWrite := codecReq.WriteResponse(w, reply.Interface(), errResult); errWrite != nil { + s.writeError(w, 400, errWrite.Error()) + } else { + // Call the registered After Function + if s.afterFunc != nil { + s.afterFunc(&RequestInfo{ + Request: r, + Method: method, + Error: errResult, + StatusCode: 200, + }) + } + } +} + +func (s *Server) writeError(w http.ResponseWriter, status int, msg string) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(status) + fmt.Fprint(w, msg) + if s.afterFunc != nil { + s.afterFunc(&RequestInfo{ + Error: fmt.Errorf(msg), + StatusCode: status, + }) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4df096fc8a2..8973a602098 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -367,6 +367,9 @@ github.com/google/pprof/profile # github.com/google/uuid v1.6.0 ## explicit github.com/google/uuid +# github.com/gorilla/rpc v1.2.1 +## explicit; go 1.20 +github.com/gorilla/rpc # github.com/gorilla/securecookie v1.1.1 ## explicit github.com/gorilla/securecookie From 36e1e6d14b51f1283ef011a2e0325c9716fd4de5 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Mon, 22 Sep 2025 12:49:15 +0100 Subject: [PATCH 2/9] wip --- mobile/status.go | 9 ++++ node/get_status_node.go | 51 ++++++++++++++++++++-- services/eth/api.go | 44 +++++++++++++++++++ services/eth/service.go | 5 +++ tests-functional/clients/status_backend.py | 2 +- tests-functional/tests/test_gorilla_rpc.py | 31 +++++++++++++ 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 tests-functional/tests/test_gorilla_rpc.py diff --git a/mobile/status.go b/mobile/status.go index 991ce6627d2..6926620fcea 100644 --- a/mobile/status.go +++ b/mobile/status.go @@ -320,6 +320,15 @@ func callRPC(inputJSON string) string { return callWithResponse(statusBackend.StatusNode().CallInProcessRPC, inputJSON) } +func CallGorillaRPC(inputJSON string) string { + return callGorillaRPC(inputJSON) +} + +// +func callGorillaRPC(inputJSON string) string { + return callWithResponse(statusBackend.StatusNode().CallInProcessGorillaRPC, inputJSON) +} + // Deprecated: Use CallRPC instead, the behaviour is the same. func CallPrivateRPC(inputJSON string) string { return callRPC(inputJSON) diff --git a/node/get_status_node.go b/node/get_status_node.go index 05f20d35c49..4825ba1d36a 100644 --- a/node/get_status_node.go +++ b/node/get_status_node.go @@ -1,10 +1,14 @@ package node +import "C" import ( + "bytes" "context" "database/sql" "errors" "fmt" + "io" + "net/http/httptest" "os" "path/filepath" "reflect" @@ -18,6 +22,8 @@ import ( "github.com/ethereum/go-ethereum/event" gethrpc "github.com/ethereum/go-ethereum/rpc" + gorillarpc "github.com/gorilla/rpc" + gorillajson "github.com/gorilla/rpc/json" _ "github.com/gorilla/rpc" @@ -79,8 +85,9 @@ type StatusNode struct { config *params.NodeConfig // Status node configuration rpcClient *rpc.Client // reference to an RPC client - services []common2.StatusService - rpcServer *gethrpc.Server + services []common2.StatusService + rpcServer *gethrpc.Server + gorillaRPCServer *gorillarpc.Server downloader *ipfs.Downloader @@ -130,6 +137,10 @@ type StatusNode struct { // New makes new instance of StatusNode. func New(transactor *transactions.Transactor, gethAccountsManager *accsmanagement.AccountsManager, logger *zap.Logger) *StatusNode { logger = logger.Named("StatusNode") + + gorillaRPCServer := gorillarpc.NewServer() + gorillaRPCServer.RegisterCodec(gorillajson.NewCodec(), "application/json") + return &StatusNode{ transactor: transactor, gethAccountsManager: gethAccountsManager, @@ -137,7 +148,9 @@ func New(transactor *transactions.Transactor, gethAccountsManager *accsmanagemen publicMethods: make(map[string]bool), accountsPublisher: pubsub.NewPublisher(), rpcServer: gethrpc.NewServer(), + gorillaRPCServer: gorillaRPCServer, } + } // Config exposes reference to running node's configuration @@ -341,9 +354,14 @@ func (n *StatusNode) startWithDB(config *params.NodeConfig) error { } } + err := n.gorillaRPCServer.RegisterTCPService(n.ethSrvc.GorillaAPI(), "eth") + if err != nil { + return errorspkg.Wrap(err, "failed to register eth service") + } + // Start services - err := n.timeSourceSrvc.Start(context.Background()) + err = n.timeSourceSrvc.Start(context.Background()) if err != nil { return errorspkg.Wrap(err, "failed to start time source") } @@ -476,6 +494,33 @@ func (n *StatusNode) CallInProcessRPC(inputJSON string) string { return codec.Output() } +func (n *StatusNode) CallInProcessGorillaRPC(inputJSON string) string { + //n.gorillaRPCServer.ServeHTTP() + + payloadBytes := []byte(inputJSON) + + // Create a fake HTTP request + req := httptest.NewRequest("POST", "/CallInProcessGorillaRPC", bytes.NewBuffer(payloadBytes)) + req.Header.Set("Content-Type", "application/json") + + // Create a fake HTTP response writer + rr := httptest.NewRecorder() + + // Call the server's ServeHTTP method + n.gorillaRPCServer.ServeHTTP(rr, req) + + // Read and return the response body + resp := rr.Result() + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + + return string(body) +} + // RPCClient exposes reference to RPC client connected to the running node. func (n *StatusNode) RPCClient() *rpc.Client { n.mu.RLock() diff --git a/services/eth/api.go b/services/eth/api.go index 388940ddda1..e88cf132828 100644 --- a/services/eth/api.go +++ b/services/eth/api.go @@ -2,12 +2,23 @@ package eth import ( "context" + "fmt" accounts "github.com/status-im/status-go/accounts-management" "github.com/status-im/status-go/rpc" ) +type ( + // EstimateGasArgs are the Gorilla RPC request args for eth_estimateGas. + EstimateGasArgs struct { + ChainID uint64 `json:"chainID"` + Payload interface{} `json:"payload"` + } + // EstimateGasReply is the Gorilla RPC reply type for eth_estimateGas. + EstimateGasReply string +) + type API struct { client *rpc.Client accountsManager *accounts.AccountsManager @@ -29,3 +40,36 @@ func (a *API) EstimateGas(ctx context.Context, chainID uint64, payload any) (res err = client.CallContext(ctx, &result, "eth_estimateGas", payload) return } + +// EstimateGasGorilla is a gorilla/rpc-compatible wrapper around EstimateGas. +// It matches RegisterTCPService expected signature: (args *Args, reply *Reply) error +// JSON-RPC method name: "eth_estimateGas" +func (a *API) EstimateGasGorilla(args *EstimateGasArgs, reply *EstimateGasReply) error { + if args == nil || reply == nil { + return nil + } + res, err := a.EstimateGas(context.Background(), args.ChainID, args.Payload) + if err != nil { + return err + } + *reply = EstimateGasReply(res) + return nil +} + +type GorillaArgs struct { + A string `json:"a"` +} + +type GorillaReply struct { + B string `json:"b"` +} + +func (a *API) TestGorilla(args *GorillaArgs, reply *GorillaReply) error { + reply.B = fmt.Sprintf("a: %s", args.A) + return nil +} + +func (a *API) TestGorilla2(args *GorillaArgs, reply *GorillaReply) error { + reply.B = fmt.Sprintf("a: %s", args.A) + return fmt.Errorf("test error: %s", args.A) +} diff --git a/services/eth/service.go b/services/eth/service.go index a63275a6643..3a1abfdd38a 100644 --- a/services/eth/service.go +++ b/services/eth/service.go @@ -21,6 +21,7 @@ func NewService(client *rpc.Client, accountsManager *accounts.AccountsManager) * } } + func (s *Service) Start() error { return nil } @@ -38,3 +39,7 @@ func (s *Service) APIs() []gethrpc.API { }, } } + +func (s *Service) GorillaAPI() *API { + return NewAPI(s.client, s.accountsManager) +} \ No newline at end of file diff --git a/tests-functional/clients/status_backend.py b/tests-functional/clients/status_backend.py index 261769994a8..a8917048875 100644 --- a/tests-functional/clients/status_backend.py +++ b/tests-functional/clients/status_backend.py @@ -136,7 +136,7 @@ def init_status_backend(self): "apiLoggingEnabled": True, "wakuFleetsConfigFilePath": Config.waku_fleets_config, "pushFleetsConfigFilePath": Config.push_fleets_config, - "mediaServerAddress": f"""{"0.0.0.0" if self.container else "localhost"}:{constants.STATUS_MEDIA_SERVER_PORT}""", + "mediaServerAddress": f"""{"0.0.0.0" if self.container else "127.0.0.1"}:{constants.STATUS_MEDIA_SERVER_PORT}""", "mediaServerAdvertizeHost": "localhost" if self.container else "", "mediaServerAdvertizePort": self.container.media_server_port if self.container else 0, } diff --git a/tests-functional/tests/test_gorilla_rpc.py b/tests-functional/tests/test_gorilla_rpc.py new file mode 100644 index 00000000000..3e0b917e94e --- /dev/null +++ b/tests-functional/tests/test_gorilla_rpc.py @@ -0,0 +1,31 @@ +import pytest + +import utils.fake as fake +from clients.signals import SignalType + + +@pytest.mark.rpc +@pytest.mark.ethclient +@pytest.mark.xdist_group(name="Eth") +class TestEth: + await_signals = [ + SignalType.NODE_LOGIN.value, + SignalType.WALLET.value, + SignalType.WALLET_SUGGESTED_ROUTES.value, + SignalType.WALLET_ROUTER_SIGN_TRANSACTIONS.value, + SignalType.WALLET_ROUTER_SENDING_TRANSACTIONS_STARTED.value, + SignalType.WALLET_ROUTER_TRANSACTIONS_SENT.value, + ] + + def test_gorilla_rpc(self, backend_new_profile): + backend = backend_new_profile("sender") + + testvalue = fake.profile_name() + args = {"a": testvalue} + + request = {"jsonrpc": "2.0", "method": "eth.TestGorilla", "id": 1, "params": [args]} + reply = backend.api_request_json("CallGorillaRPC", request) + result = reply.get("result") + + assert result is not None + assert result.get("b") == ("a: " + testvalue) From d15db5c62023db71a14af37569ef0aa4e0695356 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Tue, 23 Sep 2025 11:12:09 +0100 Subject: [PATCH 3/9] chore: make vendor --- vendor/github.com/gorilla/rpc/json/client.go | 70 ++++++++++ vendor/github.com/gorilla/rpc/json/doc.go | 58 ++++++++ vendor/github.com/gorilla/rpc/json/server.go | 135 +++++++++++++++++++ vendor/modules.txt | 1 + 4 files changed, 264 insertions(+) create mode 100644 vendor/github.com/gorilla/rpc/json/client.go create mode 100644 vendor/github.com/gorilla/rpc/json/doc.go create mode 100644 vendor/github.com/gorilla/rpc/json/server.go diff --git a/vendor/github.com/gorilla/rpc/json/client.go b/vendor/github.com/gorilla/rpc/json/client.go new file mode 100644 index 00000000000..e0aae3bddc4 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/json/client.go @@ -0,0 +1,70 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package json + +import ( + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "math" + "math/big" +) + +// ---------------------------------------------------------------------------- +// Request and Response +// ---------------------------------------------------------------------------- + +// clientRequest represents a JSON-RPC request sent by a client. +type clientRequest struct { + // A String containing the name of the method to be invoked. + Method string `json:"method"` + // Object to pass as request parameter to the method. + Params [1]interface{} `json:"params"` + // The request id. This can be of any type. It is used to match the + // response with the request that it is replying to. + Id uint64 `json:"id"` +} + +// clientResponse represents a JSON-RPC response returned to a client. +type clientResponse struct { + Result *json.RawMessage `json:"result"` + Error interface{} `json:"error"` + Id uint64 `json:"id"` +} + +// EncodeClientRequest encodes parameters for a JSON-RPC client request. +func EncodeClientRequest(method string, args interface{}) ([]byte, error) { + val, err := rand.Int(rand.Reader, big.NewInt(int64(math.MaxInt64))) + if err != nil { + log.Fatal(err) + } + + c := &clientRequest{ + Method: method, + Params: [1]interface{}{args}, + Id: val.Uint64(), + } + return json.Marshal(c) +} + +// DecodeClientResponse decodes the response body of a client request into +// the interface reply. +func DecodeClientResponse(r io.Reader, reply interface{}) error { + var c clientResponse + if err := json.NewDecoder(r).Decode(&c); err != nil { + return err + } + if c.Error != nil { + return fmt.Errorf("%v", c.Error) + } + if c.Result == nil { + return errors.New("result is null") + } + return json.Unmarshal(*c.Result, reply) +} diff --git a/vendor/github.com/gorilla/rpc/json/doc.go b/vendor/github.com/gorilla/rpc/json/doc.go new file mode 100644 index 00000000000..bfae0af1e66 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/json/doc.go @@ -0,0 +1,58 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package gorilla/rpc/json provides a codec for JSON-RPC over HTTP services. + +To register the codec in a RPC server: + + import ( + "http" + "github.com/gorilla/rpc" + "github.com/gorilla/rpc/json" + ) + + func init() { + s := rpc.NewServer() + s.RegisterCodec(json.NewCodec(), "application/json") + // [...] + http.Handle("/rpc", s) + } + +A codec is tied to a content type. In the example above, the server will use +the JSON codec for requests with "application/json" as the value for the +"Content-Type" header. + +This package follows the JSON-RPC 1.0 specification: + + http://json-rpc.org/wiki/specification + +Request format is: + + method: + The name of the method to be invoked, as a string in dotted notation + as in "Service.Method". + params: + An array with a single object to pass as argument to the method. + id: + The request id, a uint. It is used to match the response with the + request that it is replying to. + +Response format is: + + result: + The Object that was returned by the invoked method, + or null in case there was an error invoking the method. + error: + An Error object if there was an error invoking the method, + or null if there was no error. + id: + The same id as the request it is responding to. + +Check the gorilla/rpc documentation for more details: + + http://gorilla-web.appspot.com/pkg/rpc +*/ +package json diff --git a/vendor/github.com/gorilla/rpc/json/server.go b/vendor/github.com/gorilla/rpc/json/server.go new file mode 100644 index 00000000000..e44913ef7bc --- /dev/null +++ b/vendor/github.com/gorilla/rpc/json/server.go @@ -0,0 +1,135 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package json + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/gorilla/rpc" +) + +var null = json.RawMessage([]byte("null")) + +// ---------------------------------------------------------------------------- +// Request and Response +// ---------------------------------------------------------------------------- + +// serverRequest represents a JSON-RPC request received by the server. +type serverRequest struct { + // A String containing the name of the method to be invoked. + Method string `json:"method"` + // An Array of objects to pass as arguments to the method. + Params *json.RawMessage `json:"params"` + // The request id. This can be of any type. It is used to match the + // response with the request that it is replying to. + Id *json.RawMessage `json:"id"` +} + +// serverResponse represents a JSON-RPC response returned by the server. +type serverResponse struct { + // The Object that was returned by the invoked method. This must be null + // in case there was an error invoking the method. + Result interface{} `json:"result"` + // An Error object if there was an error invoking the method. It must be + // null if there was no error. + Error interface{} `json:"error"` + // This must be the same id as the request it is responding to. + Id *json.RawMessage `json:"id"` +} + +// ---------------------------------------------------------------------------- +// Codec +// ---------------------------------------------------------------------------- + +// NewCodec returns a new JSON Codec. +func NewCodec() *Codec { + return &Codec{} +} + +// Codec creates a CodecRequest to process each request. +type Codec struct { +} + +// NewRequest returns a CodecRequest. +func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest { + return newCodecRequest(r) +} + +// ---------------------------------------------------------------------------- +// CodecRequest +// ---------------------------------------------------------------------------- + +// newCodecRequest returns a new CodecRequest. +func newCodecRequest(r *http.Request) rpc.CodecRequest { + // Decode the request body and check if RPC method is valid. + req := new(serverRequest) + err := json.NewDecoder(r.Body).Decode(req) + return &CodecRequest{request: req, err: err} +} + +// CodecRequest decodes and encodes a single request. +type CodecRequest struct { + request *serverRequest + err error +} + +// Method returns the RPC method for the current request. +// +// The method uses a dotted notation as in "Service.Method". +func (c *CodecRequest) Method() (string, error) { + if c.err == nil { + return c.request.Method, nil + } + return "", c.err +} + +// ReadRequest fills the request object for the RPC method. +func (c *CodecRequest) ReadRequest(args interface{}) error { + if c.err == nil { + if c.request.Params != nil { + // JSON params is array value. RPC params is struct. + // Unmarshal into array containing the request struct. + params := [1]interface{}{args} + c.err = json.Unmarshal(*c.request.Params, ¶ms) + } else { + c.err = errors.New("rpc: method request ill-formed: missing params field") + } + } + return c.err +} + +// WriteResponse encodes the response and writes it to the ResponseWriter. +// +// The err parameter is the error resulted from calling the RPC method, +// or nil if there was no error. +func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}, methodErr error) error { + if c.err != nil { + return c.err + } + res := &serverResponse{ + Result: reply, + Error: &null, + Id: c.request.Id, + } + if methodErr != nil { + // Propagate error message as string. + res.Error = methodErr.Error() + // Result must be null if there was an error invoking the method. + // http://json-rpc.org/wiki/specification#a1.2Response + res.Result = &null + } + if c.request.Id == nil { + // Id is null for notifications and they don't have a response. + res.Id = &null + } else { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + encoder := json.NewEncoder(w) + c.err = encoder.Encode(res) + } + return c.err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8973a602098..32dd7578651 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -370,6 +370,7 @@ github.com/google/uuid # github.com/gorilla/rpc v1.2.1 ## explicit; go 1.20 github.com/gorilla/rpc +github.com/gorilla/rpc/json # github.com/gorilla/securecookie v1.1.1 ## explicit github.com/gorilla/securecookie From 3f813400d67365e014167e3798419f5773ca6d37 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Tue, 23 Sep 2025 11:18:51 +0100 Subject: [PATCH 4/9] chore: cleanu --- services/eth/api.go | 25 ------------------------- services/eth/service.go | 3 +-- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/services/eth/api.go b/services/eth/api.go index e88cf132828..1751e48234d 100644 --- a/services/eth/api.go +++ b/services/eth/api.go @@ -9,16 +9,6 @@ import ( "github.com/status-im/status-go/rpc" ) -type ( - // EstimateGasArgs are the Gorilla RPC request args for eth_estimateGas. - EstimateGasArgs struct { - ChainID uint64 `json:"chainID"` - Payload interface{} `json:"payload"` - } - // EstimateGasReply is the Gorilla RPC reply type for eth_estimateGas. - EstimateGasReply string -) - type API struct { client *rpc.Client accountsManager *accounts.AccountsManager @@ -41,21 +31,6 @@ func (a *API) EstimateGas(ctx context.Context, chainID uint64, payload any) (res return } -// EstimateGasGorilla is a gorilla/rpc-compatible wrapper around EstimateGas. -// It matches RegisterTCPService expected signature: (args *Args, reply *Reply) error -// JSON-RPC method name: "eth_estimateGas" -func (a *API) EstimateGasGorilla(args *EstimateGasArgs, reply *EstimateGasReply) error { - if args == nil || reply == nil { - return nil - } - res, err := a.EstimateGas(context.Background(), args.ChainID, args.Payload) - if err != nil { - return err - } - *reply = EstimateGasReply(res) - return nil -} - type GorillaArgs struct { A string `json:"a"` } diff --git a/services/eth/service.go b/services/eth/service.go index 3a1abfdd38a..1ef33dec686 100644 --- a/services/eth/service.go +++ b/services/eth/service.go @@ -21,7 +21,6 @@ func NewService(client *rpc.Client, accountsManager *accounts.AccountsManager) * } } - func (s *Service) Start() error { return nil } @@ -42,4 +41,4 @@ func (s *Service) APIs() []gethrpc.API { func (s *Service) GorillaAPI() *API { return NewAPI(s.client, s.accountsManager) -} \ No newline at end of file +} From 36a5b638e6f74040137fba2fab0f3038ec2e7ab5 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Tue, 23 Sep 2025 11:27:26 +0100 Subject: [PATCH 5/9] test: panic --- services/eth/api.go | 4 ++++ tests-functional/tests/test_gorilla_rpc.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/services/eth/api.go b/services/eth/api.go index 1751e48234d..4df8328fae8 100644 --- a/services/eth/api.go +++ b/services/eth/api.go @@ -48,3 +48,7 @@ func (a *API) TestGorilla2(args *GorillaArgs, reply *GorillaReply) error { reply.B = fmt.Sprintf("a: %s", args.A) return fmt.Errorf("test error: %s", args.A) } + +func (a *API) TestPanic(args *GorillaArgs, reply *GorillaReply) error { + panic("test panic") +} \ No newline at end of file diff --git a/tests-functional/tests/test_gorilla_rpc.py b/tests-functional/tests/test_gorilla_rpc.py index 3e0b917e94e..2ee06037ccf 100644 --- a/tests-functional/tests/test_gorilla_rpc.py +++ b/tests-functional/tests/test_gorilla_rpc.py @@ -1,3 +1,5 @@ +import logging + import pytest import utils.fake as fake @@ -29,3 +31,7 @@ def test_gorilla_rpc(self, backend_new_profile): assert result is not None assert result.get("b") == ("a: " + testvalue) + + request = {"jsonrpc": "2.0", "method": "eth.TestPanic", "id": 1, "params": [args]} + reply = backend.api_request_json("CallGorillaRPC", request) + logging.debug(f"<<< reply {reply}") From fa4599ad4d00b628a759ac4b7bc9b9f80f1a3a7f Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Tue, 23 Sep 2025 12:11:23 +0100 Subject: [PATCH 6/9] more test --- services/eth/api.go | 8 +++++--- tests-functional/tests/test_gorilla_rpc.py | 24 ++++++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/services/eth/api.go b/services/eth/api.go index 4df8328fae8..94700520e74 100644 --- a/services/eth/api.go +++ b/services/eth/api.go @@ -44,9 +44,11 @@ func (a *API) TestGorilla(args *GorillaArgs, reply *GorillaReply) error { return nil } -func (a *API) TestGorilla2(args *GorillaArgs, reply *GorillaReply) error { - reply.B = fmt.Sprintf("a: %s", args.A) - return fmt.Errorf("test error: %s", args.A) +func (a *API) TestGorilla2(args *GorillaArgs) (*GorillaReply, error) { + reply := &GorillaReply{ + B: fmt.Sprintf("a: %s", args.A), + } + return reply, nil } func (a *API) TestPanic(args *GorillaArgs, reply *GorillaReply) error { diff --git a/tests-functional/tests/test_gorilla_rpc.py b/tests-functional/tests/test_gorilla_rpc.py index 2ee06037ccf..5ca4f448162 100644 --- a/tests-functional/tests/test_gorilla_rpc.py +++ b/tests-functional/tests/test_gorilla_rpc.py @@ -1,5 +1,3 @@ -import logging - import pytest import utils.fake as fake @@ -25,13 +23,27 @@ def test_gorilla_rpc(self, backend_new_profile): testvalue = fake.profile_name() args = {"a": testvalue} - request = {"jsonrpc": "2.0", "method": "eth.TestGorilla", "id": 1, "params": [args]} + request = {"jsonrpc": "2.0", "method": "eth.TestGorilla", "id": 1, "params": args} reply = backend.api_request_json("CallGorillaRPC", request) result = reply.get("result") assert result is not None assert result.get("b") == ("a: " + testvalue) - request = {"jsonrpc": "2.0", "method": "eth.TestPanic", "id": 1, "params": [args]} - reply = backend.api_request_json("CallGorillaRPC", request) - logging.debug(f"<<< reply {reply}") + request = {"jsonrpc": "2.0", "method": "eth_testGorilla2", "id": 2, "params": [args]} + reply = backend.api_request_json("CallRPC", request) + result = reply.get("result") + + assert result is not None + assert result.get("b") == ("a: " + testvalue) + + request = {"jsonrpc": "2.0", "method": "eth_testGorilla2", "id": 3, "params": args} + reply = backend.api_request_json("CallRPC", request) + result = reply.get("result") + + assert result is not None + assert result.get("b") == ("a: " + testvalue) + + # request = {"jsonrpc": "2.0", "method": "eth.TestPanic", "id": 4, "params": [args]} + # reply = backend.api_request_json("CallGorillaRPC", request) + # logging.debug(f"<<< reply {reply}") From 00b24bdc95026a1534fe34477a444577758ba67e Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Wed, 24 Sep 2025 12:10:35 +0100 Subject: [PATCH 7/9] gorilla rpc v2 --- node/get_status_node.go | 6 +- services/eth/api.go | 11 +- vendor/github.com/gorilla/rpc/v2/LICENSE | 27 ++ vendor/github.com/gorilla/rpc/v2/README.md | 9 + .../gorilla/rpc/v2/compression_selector.go | 80 ++++++ vendor/github.com/gorilla/rpc/v2/doc.go | 81 ++++++ .../gorilla/rpc/v2/encoder_selector.go | 43 +++ .../gorilla/rpc/{ => v2}/json/client.go | 7 +- .../gorilla/rpc/{ => v2}/json/doc.go | 4 +- .../gorilla/rpc/{ => v2}/json/server.go | 66 +++-- vendor/github.com/gorilla/rpc/v2/map.go | 164 +++++++++++ vendor/github.com/gorilla/rpc/v2/server.go | 263 ++++++++++++++++++ 12 files changed, 727 insertions(+), 34 deletions(-) create mode 100644 vendor/github.com/gorilla/rpc/v2/LICENSE create mode 100644 vendor/github.com/gorilla/rpc/v2/README.md create mode 100644 vendor/github.com/gorilla/rpc/v2/compression_selector.go create mode 100644 vendor/github.com/gorilla/rpc/v2/doc.go create mode 100644 vendor/github.com/gorilla/rpc/v2/encoder_selector.go rename vendor/github.com/gorilla/rpc/{ => v2}/json/client.go (92%) rename vendor/github.com/gorilla/rpc/{ => v2}/json/doc.go (95%) rename vendor/github.com/gorilla/rpc/{ => v2}/json/server.go (76%) create mode 100644 vendor/github.com/gorilla/rpc/v2/map.go create mode 100644 vendor/github.com/gorilla/rpc/v2/server.go diff --git a/node/get_status_node.go b/node/get_status_node.go index 4825ba1d36a..05b9a6bbc21 100644 --- a/node/get_status_node.go +++ b/node/get_status_node.go @@ -22,8 +22,8 @@ import ( "github.com/ethereum/go-ethereum/event" gethrpc "github.com/ethereum/go-ethereum/rpc" - gorillarpc "github.com/gorilla/rpc" - gorillajson "github.com/gorilla/rpc/json" + gorillarpc "github.com/gorilla/rpc/v2" + gorillajson "github.com/gorilla/rpc/v2/json" _ "github.com/gorilla/rpc" @@ -354,7 +354,7 @@ func (n *StatusNode) startWithDB(config *params.NodeConfig) error { } } - err := n.gorillaRPCServer.RegisterTCPService(n.ethSrvc.GorillaAPI(), "eth") + err := n.gorillaRPCServer.RegisterService(n.ethSrvc.GorillaAPI(), "eth") if err != nil { return errorspkg.Wrap(err, "failed to register eth service") } diff --git a/services/eth/api.go b/services/eth/api.go index 94700520e74..c5e81d826d5 100644 --- a/services/eth/api.go +++ b/services/eth/api.go @@ -3,6 +3,7 @@ package eth import ( "context" "fmt" + "net/http" accounts "github.com/status-im/status-go/accounts-management" @@ -39,18 +40,22 @@ type GorillaReply struct { B string `json:"b"` } -func (a *API) TestGorilla(args *GorillaArgs, reply *GorillaReply) error { +func (a *API) TestGorilla(request *http.Request, args *GorillaArgs, reply *GorillaReply) error { reply.B = fmt.Sprintf("a: %s", args.A) return nil } -func (a *API) TestGorilla2(args *GorillaArgs) (*GorillaReply, error) { +func (a *API) TestGorilla2(request *http.Request, args *GorillaArgs) (*GorillaReply, error) { reply := &GorillaReply{ B: fmt.Sprintf("a: %s", args.A), } return reply, nil } -func (a *API) TestPanic(args *GorillaArgs, reply *GorillaReply) error { +func (a *API) TestPanic(request *http.Request, args *GorillaArgs, reply *GorillaReply) error { panic("test panic") +} + +func (a *API) IntendedPanic() error { + panic("intended panic") } \ No newline at end of file diff --git a/vendor/github.com/gorilla/rpc/v2/LICENSE b/vendor/github.com/gorilla/rpc/v2/LICENSE new file mode 100644 index 00000000000..0e5fb872800 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 Rodrigo Moraes. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gorilla/rpc/v2/README.md b/vendor/github.com/gorilla/rpc/v2/README.md new file mode 100644 index 00000000000..a0077ea8547 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/README.md @@ -0,0 +1,9 @@ +rpc +=== + + +rpc/v2 support for JSON-RPC 2.0 Specification. + +gorilla/rpc is a foundation for RPC over HTTP services, providing access to the exported methods of an object through HTTP requests. + +Read the full documentation here: http://www.gorillatoolkit.org/pkg/rpc diff --git a/vendor/github.com/gorilla/rpc/v2/compression_selector.go b/vendor/github.com/gorilla/rpc/v2/compression_selector.go new file mode 100644 index 00000000000..4f6cd919c07 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/compression_selector.go @@ -0,0 +1,80 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpc + +import ( + "compress/flate" + "compress/gzip" + "io" + "net/http" + "strings" + "unicode" +) + +// gzipWriter writes and closes the gzip writer. +type gzipWriter struct { + w *gzip.Writer +} + +func (gw *gzipWriter) Write(p []byte) (n int, err error) { + defer gw.w.Close() + return gw.w.Write(p) +} + +// gzipEncoder implements the gzip compressed http encoder. +type gzipEncoder struct { +} + +func (enc *gzipEncoder) Encode(w http.ResponseWriter) io.Writer { + w.Header().Set("Content-Encoding", "gzip") + return &gzipWriter{gzip.NewWriter(w)} +} + +// flateWriter writes and closes the flate writer. +type flateWriter struct { + w *flate.Writer +} + +func (fw *flateWriter) Write(p []byte) (n int, err error) { + defer fw.w.Close() + return fw.w.Write(p) +} + +// flateEncoder implements the flate compressed http encoder. +type flateEncoder struct { +} + +func (enc *flateEncoder) Encode(w http.ResponseWriter) io.Writer { + fw, err := flate.NewWriter(w, flate.DefaultCompression) + if err != nil { + return w + } + w.Header().Set("Content-Encoding", "deflate") + return &flateWriter{fw} +} + +// CompressionSelector generates the compressed http encoder. +type CompressionSelector struct { +} + +// Select method selects the correct compression encoder based on http HEADER. +func (*CompressionSelector) Select(r *http.Request) Encoder { + encHeader := r.Header.Get("Accept-Encoding") + encTypes := strings.FieldsFunc(encHeader, func(r rune) bool { + return unicode.IsSpace(r) || r == ',' + }) + + for _, enc := range encTypes { + switch enc { + case "gzip": + return &gzipEncoder{} + case "deflate": + return &flateEncoder{} + } + } + + return DefaultEncoder +} diff --git a/vendor/github.com/gorilla/rpc/v2/doc.go b/vendor/github.com/gorilla/rpc/v2/doc.go new file mode 100644 index 00000000000..301d5dc061e --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/doc.go @@ -0,0 +1,81 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package gorilla/rpc is a foundation for RPC over HTTP services, providing +access to the exported methods of an object through HTTP requests. + +This package derives from the standard net/rpc package but uses a single HTTP +request per call instead of persistent connections. Other differences +compared to net/rpc: + + - Multiple codecs can be registered in the same server. + - A codec is chosen based on the "Content-Type" header from the request. + - Service methods also receive http.Request as parameter. + - This package can be used on Google App Engine. + +Let's setup a server and register a codec and service: + + import ( + "http" + "github.com/gorilla/rpc/v2" + "github.com/gorilla/rpc/v2/json" + ) + + func init() { + s := rpc.NewServer() + s.RegisterCodec(json.NewCodec(), "application/json") + s.RegisterService(new(HelloService), "") + http.Handle("/rpc", s) + } + +This server handles requests to the "/rpc" path using a JSON codec. +A codec is tied to a content type. In the example above, the JSON codec is +registered to serve requests with "application/json" as the value for the +"Content-Type" header. If the header includes a charset definition, it is +ignored; only the media-type part is taken into account. + +A service can be registered using a name. If the name is empty, like in the +example above, it will be inferred from the service type. + +That's all about the server setup. Now let's define a simple service: + + type HelloArgs struct { + Who string + } + + type HelloReply struct { + Message string + } + + type HelloService struct {} + + func (h *HelloService) Say(r *http.Request, args *HelloArgs, reply *HelloReply) error { + reply.Message = "Hello, " + args.Who + "!" + return nil + } + +The example above defines a service with a method "HelloService.Say" and +the arguments and reply related to that method. + +The service must be exported (begin with an upper case letter) or local +(defined in the package registering the service). + +When a service is registered, the server inspects the service methods +and make available the ones that follow these rules: + + - The method name is exported. + - The method has three arguments: *http.Request, *args, *reply. + - All three arguments are pointers. + - The second and third arguments are exported or local. + - The method has return type error. + +All other methods are ignored. + +Gorilla has packages with common RPC codecs. Check out their documentation: + + JSON: http://gorilla-web.appspot.com/pkg/rpc/json +*/ +package rpc diff --git a/vendor/github.com/gorilla/rpc/v2/encoder_selector.go b/vendor/github.com/gorilla/rpc/v2/encoder_selector.go new file mode 100644 index 00000000000..333361f3a97 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/encoder_selector.go @@ -0,0 +1,43 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpc + +import ( + "io" + "net/http" +) + +// Encoder interface contains the encoder for http response. +// Eg. gzip, flate compressions. +type Encoder interface { + Encode(w http.ResponseWriter) io.Writer +} + +type encoder struct { +} + +func (_ *encoder) Encode(w http.ResponseWriter) io.Writer { + return w +} + +var DefaultEncoder = &encoder{} + +// EncoderSelector interface provides a way to select encoder using the http +// request. Typically people can use this to check HEADER of the request and +// figure out client capabilities. +// Eg. "Accept-Encoding" tells about supported compressions. +type EncoderSelector interface { + Select(r *http.Request) Encoder +} + +type encoderSelector struct { +} + +func (_ *encoderSelector) Select(_ *http.Request) Encoder { + return DefaultEncoder +} + +var DefaultEncoderSelector = &encoderSelector{} diff --git a/vendor/github.com/gorilla/rpc/json/client.go b/vendor/github.com/gorilla/rpc/v2/json/client.go similarity index 92% rename from vendor/github.com/gorilla/rpc/json/client.go rename to vendor/github.com/gorilla/rpc/v2/json/client.go index e0aae3bddc4..870d3a155d2 100644 --- a/vendor/github.com/gorilla/rpc/json/client.go +++ b/vendor/github.com/gorilla/rpc/v2/json/client.go @@ -1,5 +1,5 @@ // Copyright 2009 The Go Authors. All rights reserved. -// Copyright 2012 The Gorilla Authors. All rights reserved. +// Copyright 2012-2013 The Gorilla Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -8,7 +8,6 @@ package json import ( "crypto/rand" "encoding/json" - "errors" "fmt" "io" "log" @@ -61,10 +60,10 @@ func DecodeClientResponse(r io.Reader, reply interface{}) error { return err } if c.Error != nil { - return fmt.Errorf("%v", c.Error) + return &Error{Data: c.Error} } if c.Result == nil { - return errors.New("result is null") + return fmt.Errorf("unexpected null result") } return json.Unmarshal(*c.Result, reply) } diff --git a/vendor/github.com/gorilla/rpc/json/doc.go b/vendor/github.com/gorilla/rpc/v2/json/doc.go similarity index 95% rename from vendor/github.com/gorilla/rpc/json/doc.go rename to vendor/github.com/gorilla/rpc/v2/json/doc.go index bfae0af1e66..3f92b9cb3ba 100644 --- a/vendor/github.com/gorilla/rpc/json/doc.go +++ b/vendor/github.com/gorilla/rpc/v2/json/doc.go @@ -10,8 +10,8 @@ To register the codec in a RPC server: import ( "http" - "github.com/gorilla/rpc" - "github.com/gorilla/rpc/json" + "github.com/gorilla/rpc/v2" + "github.com/gorilla/rpc/v2/json" ) func init() { diff --git a/vendor/github.com/gorilla/rpc/json/server.go b/vendor/github.com/gorilla/rpc/v2/json/server.go similarity index 76% rename from vendor/github.com/gorilla/rpc/json/server.go rename to vendor/github.com/gorilla/rpc/v2/json/server.go index e44913ef7bc..d70a01d8f14 100644 --- a/vendor/github.com/gorilla/rpc/json/server.go +++ b/vendor/github.com/gorilla/rpc/v2/json/server.go @@ -8,13 +8,26 @@ package json import ( "encoding/json" "errors" + "fmt" + "log" "net/http" - "github.com/gorilla/rpc" + "github.com/gorilla/rpc/v2" ) var null = json.RawMessage([]byte("null")) +// An Error is a wrapper for a JSON interface value. It can be used by either +// a service's handler func to write more complex JSON data to an error field +// of a server's response, or by a client to read it. +type Error struct { + Data interface{} +} + +func (e *Error) Error() string { + return fmt.Sprintf("%v", e.Data) +} + // ---------------------------------------------------------------------------- // Request and Response // ---------------------------------------------------------------------------- @@ -104,32 +117,41 @@ func (c *CodecRequest) ReadRequest(args interface{}) error { } // WriteResponse encodes the response and writes it to the ResponseWriter. -// -// The err parameter is the error resulted from calling the RPC method, -// or nil if there was no error. -func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}, methodErr error) error { - if c.err != nil { - return c.err +func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) { + if c.request.Id != nil { + // Id is null for notifications and they don't have a response. + res := &serverResponse{ + Result: reply, + Error: &null, + Id: c.request.Id, + } + c.writeServerResponse(w, 200, res) } +} + +func (c *CodecRequest) WriteError(w http.ResponseWriter, _ int, err error) { res := &serverResponse{ - Result: reply, - Error: &null, + Result: &null, Id: c.request.Id, } - if methodErr != nil { - // Propagate error message as string. - res.Error = methodErr.Error() - // Result must be null if there was an error invoking the method. - // http://json-rpc.org/wiki/specification#a1.2Response - res.Result = &null - } - if c.request.Id == nil { - // Id is null for notifications and they don't have a response. - res.Id = &null + if jsonErr, ok := err.(*Error); ok { + res.Error = jsonErr.Data } else { + res.Error = err.Error() + } + c.writeServerResponse(w, 400, res) +} + +func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, status int, res *serverResponse) { + b, err := json.Marshal(res) + if err == nil { w.Header().Set("Content-Type", "application/json; charset=utf-8") - encoder := json.NewEncoder(w) - c.err = encoder.Encode(res) + w.WriteHeader(status) + if _, err := w.Write(b); err != nil { + log.Fatal(err) + } + } else { + // Not sure in which case will this happen. But seems harmless. + rpc.WriteError(w, 400, err.Error()) } - return c.err } diff --git a/vendor/github.com/gorilla/rpc/v2/map.go b/vendor/github.com/gorilla/rpc/v2/map.go new file mode 100644 index 00000000000..dda42161c6b --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/map.go @@ -0,0 +1,164 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpc + +import ( + "fmt" + "net/http" + "reflect" + "strings" + "sync" + "unicode" + "unicode/utf8" +) + +var ( + // Precompute the reflect.Type of error and http.Request + typeOfError = reflect.TypeOf((*error)(nil)).Elem() + typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem() +) + +// ---------------------------------------------------------------------------- +// service +// ---------------------------------------------------------------------------- + +type service struct { + name string // name of service + rcvr reflect.Value // receiver of methods for the service + rcvrType reflect.Type // type of the receiver + methods map[string]*serviceMethod // registered methods +} + +type serviceMethod struct { + method reflect.Method // receiver method + argsType reflect.Type // type of the request argument + replyType reflect.Type // type of the response argument +} + +// ---------------------------------------------------------------------------- +// serviceMap +// ---------------------------------------------------------------------------- + +// serviceMap is a registry for services. +type serviceMap struct { + mutex sync.Mutex + services map[string]*service +} + +// register adds a new service using reflection to extract its methods. +func (m *serviceMap) register(rcvr interface{}, name string) error { + // Setup service. + s := &service{ + name: name, + rcvr: reflect.ValueOf(rcvr), + rcvrType: reflect.TypeOf(rcvr), + methods: make(map[string]*serviceMethod), + } + if name == "" { + s.name = reflect.Indirect(s.rcvr).Type().Name() + if !isExported(s.name) { + return fmt.Errorf("rpc: type %q is not exported", s.name) + } + } + if s.name == "" { + return fmt.Errorf("rpc: no service name for type %q", + s.rcvrType.String()) + } + // Setup methods. + for i := 0; i < s.rcvrType.NumMethod(); i++ { + method := s.rcvrType.Method(i) + mtype := method.Type + // Method must be exported. + if method.PkgPath != "" { + continue + } + // Method needs four ins: receiver, *http.Request, *args, *reply. + if mtype.NumIn() != 4 { + continue + } + // First argument must be a pointer and must be http.Request. + reqType := mtype.In(1) + if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest { + continue + } + // Second argument must be a pointer and must be exported. + args := mtype.In(2) + if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) { + continue + } + // Third argument must be a pointer and must be exported. + reply := mtype.In(3) + if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) { + continue + } + // Method needs one out: error. + if mtype.NumOut() != 1 { + continue + } + if returnType := mtype.Out(0); returnType != typeOfError { + continue + } + s.methods[method.Name] = &serviceMethod{ + method: method, + argsType: args.Elem(), + replyType: reply.Elem(), + } + } + if len(s.methods) == 0 { + return fmt.Errorf("rpc: %q has no exported methods of suitable type", + s.name) + } + // Add to the map. + m.mutex.Lock() + defer m.mutex.Unlock() + if m.services == nil { + m.services = make(map[string]*service) + } else if _, ok := m.services[s.name]; ok { + return fmt.Errorf("rpc: service already defined: %q", s.name) + } + m.services[s.name] = s + return nil +} + +// get returns a registered service given a method name. +// +// The method name uses a dotted notation as in "Service.Method". +func (m *serviceMap) get(method string) (*service, *serviceMethod, error) { + parts := strings.Split(method, ".") + if len(parts) != 2 { + err := fmt.Errorf("rpc: service/method request ill-formed: %q", method) + return nil, nil, err + } + m.mutex.Lock() + service := m.services[parts[0]] + m.mutex.Unlock() + if service == nil { + err := fmt.Errorf("rpc: can't find service %q", method) + return nil, nil, err + } + serviceMethod := service.methods[parts[1]] + if serviceMethod == nil { + err := fmt.Errorf("rpc: can't find method %q", method) + return nil, nil, err + } + return service, serviceMethod, nil +} + +// isExported returns true of a string is an exported (upper case) name. +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +// isExportedOrBuiltin returns true if a type is exported or a builtin. +func isExportedOrBuiltin(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + // PkgPath will be non-empty even for an exported type, + // so we need to check the type name as well. + return isExported(t.Name()) || t.PkgPath() == "" +} diff --git a/vendor/github.com/gorilla/rpc/v2/server.go b/vendor/github.com/gorilla/rpc/v2/server.go new file mode 100644 index 00000000000..8d265391345 --- /dev/null +++ b/vendor/github.com/gorilla/rpc/v2/server.go @@ -0,0 +1,263 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpc + +import ( + "fmt" + "net/http" + "reflect" + "strings" +) + +var nilErrorValue = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()) + +// ---------------------------------------------------------------------------- +// Codec +// ---------------------------------------------------------------------------- + +// Codec creates a CodecRequest to process each request. +type Codec interface { + NewRequest(*http.Request) CodecRequest +} + +// CodecRequest decodes a request and encodes a response using a specific +// serialization scheme. +type CodecRequest interface { + // Reads the request and returns the RPC method name. + Method() (string, error) + // Reads the request filling the RPC method args. + ReadRequest(interface{}) error + // Writes the response using the RPC method reply. + WriteResponse(http.ResponseWriter, interface{}) + // Writes an error produced by the server. + WriteError(w http.ResponseWriter, status int, err error) +} + +// ---------------------------------------------------------------------------- +// Server +// ---------------------------------------------------------------------------- + +// NewServer returns a new RPC server. +func NewServer() *Server { + return &Server{ + codecs: make(map[string]Codec), + services: new(serviceMap), + } +} + +// RequestInfo contains all the information we pass to before/after functions +type RequestInfo struct { + Method string + Error error + Request *http.Request + StatusCode int +} + +// Server serves registered RPC services using registered codecs. +type Server struct { + codecs map[string]Codec + services *serviceMap + interceptFunc func(i *RequestInfo) *http.Request + beforeFunc func(i *RequestInfo) + afterFunc func(i *RequestInfo) + validateFunc reflect.Value +} + +// RegisterCodec adds a new codec to the server. +// +// Codecs are defined to process a given serialization scheme, e.g., JSON or +// XML. A codec is chosen based on the "Content-Type" header from the request, +// excluding the charset definition. +func (s *Server) RegisterCodec(codec Codec, contentType string) { + s.codecs[strings.ToLower(contentType)] = codec +} + +// RegisterInterceptFunc registers the specified function as the function +// that will be called before every request. The function is allowed to intercept +// the request e.g. add values to the context. +// +// Note: Only one function can be registered, subsequent calls to this +// method will overwrite all the previous functions. +func (s *Server) RegisterInterceptFunc(f func(i *RequestInfo) *http.Request) { + s.interceptFunc = f +} + +// RegisterBeforeFunc registers the specified function as the function +// that will be called before every request. +// +// Note: Only one function can be registered, subsequent calls to this +// method will overwrite all the previous functions. +func (s *Server) RegisterBeforeFunc(f func(i *RequestInfo)) { + s.beforeFunc = f +} + +// RegisterValidateRequestFunc registers the specified function as the function +// that will be called after the BeforeFunc (if registered) and before invoking +// the actual Service method. If this function returns a non-nil error, the method +// won't be invoked and this error will be considered as the method result. +// The first argument is information about the request, useful for accessing to http.Request.Context() +// The second argument of this function is the already-unmarshalled *args parameter of the method. +func (s *Server) RegisterValidateRequestFunc(f func(r *RequestInfo, i interface{}) error) { + s.validateFunc = reflect.ValueOf(f) +} + +// RegisterAfterFunc registers the specified function as the function +// that will be called after every request +// +// Note: Only one function can be registered, subsequent calls to this +// method will overwrite all the previous functions. +func (s *Server) RegisterAfterFunc(f func(i *RequestInfo)) { + s.afterFunc = f +} + +// RegisterService adds a new service to the server. +// +// The name parameter is optional: if empty it will be inferred from +// the receiver type name. +// +// Methods from the receiver will be extracted if these rules are satisfied: +// +// - The receiver is exported (begins with an upper case letter) or local +// (defined in the package registering the service). +// - The method name is exported. +// - The method has three arguments: *http.Request, *args, *reply. +// - All three arguments are pointers. +// - The second and third arguments are exported or local. +// - The method has return type error. +// +// All other methods are ignored. +func (s *Server) RegisterService(receiver interface{}, name string) error { + return s.services.register(receiver, name) +} + +// HasMethod returns true if the given method is registered. +// +// The method uses a dotted notation as in "Service.Method". +func (s *Server) HasMethod(method string) bool { + if _, _, err := s.services.get(method); err == nil { + return true + } + return false +} + +// ServeHTTP +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + WriteError(w, http.StatusMethodNotAllowed, "rpc: POST method required, received "+r.Method) + return + } + contentType := r.Header.Get("Content-Type") + idx := strings.Index(contentType, ";") + if idx != -1 { + contentType = contentType[:idx] + } + var codec Codec + if contentType == "" && len(s.codecs) == 1 { + // If Content-Type is not set and only one codec has been registered, + // then default to that codec. + for _, c := range s.codecs { + codec = c + } + } else if codec = s.codecs[strings.ToLower(contentType)]; codec == nil { + WriteError(w, http.StatusUnsupportedMediaType, "rpc: unrecognized Content-Type: "+contentType) + return + } + // Create a new codec request. + codecReq := codec.NewRequest(r) + // Get service method to be called. + method, errMethod := codecReq.Method() + if errMethod != nil { + codecReq.WriteError(w, http.StatusBadRequest, errMethod) + return + } + serviceSpec, methodSpec, errGet := s.services.get(method) + if errGet != nil { + codecReq.WriteError(w, http.StatusBadRequest, errGet) + return + } + // Decode the args. + args := reflect.New(methodSpec.argsType) + if errRead := codecReq.ReadRequest(args.Interface()); errRead != nil { + codecReq.WriteError(w, http.StatusBadRequest, errRead) + return + } + + // Call the registered Intercept Function + if s.interceptFunc != nil { + req := s.interceptFunc(&RequestInfo{ + Request: r, + Method: method, + }) + if req != nil { + r = req + } + } + + requestInfo := &RequestInfo{ + Request: r, + Method: method, + } + + // Call the registered Before Function + if s.beforeFunc != nil { + s.beforeFunc(requestInfo) + } + + // Prepare the reply, we need it even if validation fails + reply := reflect.New(methodSpec.replyType) + errValue := []reflect.Value{nilErrorValue} + + // Call the registered Validator Function + if s.validateFunc.IsValid() { + errValue = s.validateFunc.Call([]reflect.Value{reflect.ValueOf(requestInfo), args}) + } + + // If still no errors after validation, call the method + if errValue[0].IsNil() { + errValue = methodSpec.method.Func.Call([]reflect.Value{ + serviceSpec.rcvr, + reflect.ValueOf(r), + args, + reply, + }) + } + + // Extract the result to error if needed. + var errResult error + statusCode := http.StatusOK + errInter := errValue[0].Interface() + if errInter != nil { + statusCode = http.StatusBadRequest + errResult = errInter.(error) + } + + // Prevents Internet Explorer from MIME-sniffing a response away + // from the declared content-type + w.Header().Set("x-content-type-options", "nosniff") + + // Encode the response. + if errResult == nil { + codecReq.WriteResponse(w, reply.Interface()) + } else { + codecReq.WriteError(w, statusCode, errResult) + } + + // Call the registered After Function + if s.afterFunc != nil { + s.afterFunc(&RequestInfo{ + Request: r, + Method: method, + Error: errResult, + StatusCode: statusCode, + }) + } +} + +func WriteError(w http.ResponseWriter, status int, msg string) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(status) + fmt.Fprint(w, msg) +} From db30ef2bcfe9d4c6bd44ff723ec5116637ad1f06 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Wed, 24 Sep 2025 12:11:05 +0100 Subject: [PATCH 8/9] go-ethereum rpc panic --- Makefile | 3 +-- .../ethereum/go-ethereum/rpc/recover.go | 23 +++++++++++++++++++ .../ethereum/go-ethereum/rpc/recover_nop.go | 5 ++++ .../ethereum/go-ethereum/rpc/service.go | 14 ++--------- 4 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 vendor/github.com/ethereum/go-ethereum/rpc/recover.go create mode 100644 vendor/github.com/ethereum/go-ethereum/rpc/recover_nop.go diff --git a/Makefile b/Makefile index eea614b4adb..36b6e3b8687 100644 --- a/Makefile +++ b/Makefile @@ -60,13 +60,12 @@ GIT_ROOT ?= $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) GIT_COMMIT ?= $(shell git rev-parse --short HEAD) GIT_AUTHOR ?= $(shell git config user.email || echo $$USER) -BUILD_TAGS ?= gowaku_no_rln +BUILD_TAGS ?= gowaku_no_rln rpc_panic ifeq ($(USE_NWAKU), true) BUILD_TAGS += use_nwaku endif -BUILD_FLAGS ?= -ldflags="" BUILD_FLAGS_MOBILE ?= networkid ?= StatusChain diff --git a/vendor/github.com/ethereum/go-ethereum/rpc/recover.go b/vendor/github.com/ethereum/go-ethereum/rpc/recover.go new file mode 100644 index 00000000000..9afd7f6cbc3 --- /dev/null +++ b/vendor/github.com/ethereum/go-ethereum/rpc/recover.go @@ -0,0 +1,23 @@ +//go:build !rpc_panic + +package rpc + +import ( + "fmt" + "runtime" + + "github.com/ethereum/go-ethereum/log" +) + +func handlePanic(method string, errRes *error) { + err := recover() + if err == nil { + return + } + + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf)) + *errRes = &internalServerError{errcodePanic, "method handler crashed"} +} diff --git a/vendor/github.com/ethereum/go-ethereum/rpc/recover_nop.go b/vendor/github.com/ethereum/go-ethereum/rpc/recover_nop.go new file mode 100644 index 00000000000..603027cecbf --- /dev/null +++ b/vendor/github.com/ethereum/go-ethereum/rpc/recover_nop.go @@ -0,0 +1,5 @@ +//go:build rpc_panic + +package rpc + +func handlePanic(string, *error) {} diff --git a/vendor/github.com/ethereum/go-ethereum/rpc/service.go b/vendor/github.com/ethereum/go-ethereum/rpc/service.go index cfdfba023a0..7d869adf39d 100644 --- a/vendor/github.com/ethereum/go-ethereum/rpc/service.go +++ b/vendor/github.com/ethereum/go-ethereum/rpc/service.go @@ -20,12 +20,9 @@ import ( "context" "fmt" "reflect" - "runtime" "strings" "sync" "unicode" - - "github.com/ethereum/go-ethereum/log" ) var ( @@ -192,15 +189,8 @@ func (c *callback) call(ctx context.Context, method string, args []reflect.Value fullargs = append(fullargs, args...) // Catch panic while running the callback. - defer func() { - if err := recover(); err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf)) - errRes = &internalServerError{errcodePanic, "method handler crashed"} - } - }() + defer handlePanic(method, &errRes) + // Run the callback. results := c.fn.Call(fullargs) if len(results) == 0 { From 6f366c673f6d6355c42212049176d1e46e8f3a70 Mon Sep 17 00:00:00 2001 From: Igor Sirotin Date: Wed, 24 Sep 2025 12:53:20 +0100 Subject: [PATCH 9/9] make vendor --- vendor/modules.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vendor/modules.txt b/vendor/modules.txt index 32dd7578651..9dc4b043529 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -370,7 +370,8 @@ github.com/google/uuid # github.com/gorilla/rpc v1.2.1 ## explicit; go 1.20 github.com/gorilla/rpc -github.com/gorilla/rpc/json +github.com/gorilla/rpc/v2 +github.com/gorilla/rpc/v2/json # github.com/gorilla/securecookie v1.1.1 ## explicit github.com/gorilla/securecookie