forked from cerc-io/plugeth
rpc: remove 'exported or builtin' restriction for parameters (#20332)
* rpc: remove 'exported or builtin' restriction for parameters There is no technial reason for this restriction because package reflect can create values of any type. Requiring parameters and return values to be exported causes a lot of noise in package exports. * rpc: fix staticcheck warnings
This commit is contained in:
parent
9c6cf960b4
commit
8008c5b1fa
@ -40,11 +40,11 @@ func TestClientRequest(t *testing.T) {
|
|||||||
client := DialInProc(server)
|
client := DialInProc(server)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
var resp Result
|
var resp echoResult
|
||||||
if err := client.Call(&resp, "test_echo", "hello", 10, &Args{"world"}); err != nil {
|
if err := client.Call(&resp, "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) {
|
if !reflect.DeepEqual(resp, echoResult{"hello", 10, &echoArgs{"world"}}) {
|
||||||
t.Errorf("incorrect result %#v", resp)
|
t.Errorf("incorrect result %#v", resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,13 +58,13 @@ func TestClientBatchRequest(t *testing.T) {
|
|||||||
batch := []BatchElem{
|
batch := []BatchElem{
|
||||||
{
|
{
|
||||||
Method: "test_echo",
|
Method: "test_echo",
|
||||||
Args: []interface{}{"hello", 10, &Args{"world"}},
|
Args: []interface{}{"hello", 10, &echoArgs{"world"}},
|
||||||
Result: new(Result),
|
Result: new(echoResult),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Method: "test_echo",
|
Method: "test_echo",
|
||||||
Args: []interface{}{"hello2", 11, &Args{"world"}},
|
Args: []interface{}{"hello2", 11, &echoArgs{"world"}},
|
||||||
Result: new(Result),
|
Result: new(echoResult),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Method: "no_such_method",
|
Method: "no_such_method",
|
||||||
@ -78,13 +78,13 @@ func TestClientBatchRequest(t *testing.T) {
|
|||||||
wantResult := []BatchElem{
|
wantResult := []BatchElem{
|
||||||
{
|
{
|
||||||
Method: "test_echo",
|
Method: "test_echo",
|
||||||
Args: []interface{}{"hello", 10, &Args{"world"}},
|
Args: []interface{}{"hello", 10, &echoArgs{"world"}},
|
||||||
Result: &Result{"hello", 10, &Args{"world"}},
|
Result: &echoResult{"hello", 10, &echoArgs{"world"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Method: "test_echo",
|
Method: "test_echo",
|
||||||
Args: []interface{}{"hello2", 11, &Args{"world"}},
|
Args: []interface{}{"hello2", 11, &echoArgs{"world"}},
|
||||||
Result: &Result{"hello2", 11, &Args{"world"}},
|
Result: &echoResult{"hello2", 11, &echoArgs{"world"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Method: "no_such_method",
|
Method: "no_such_method",
|
||||||
@ -104,7 +104,7 @@ func TestClientNotify(t *testing.T) {
|
|||||||
client := DialInProc(server)
|
client := DialInProc(server)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
if err := client.Notify(context.Background(), "test_echo", "hello", 10, &Args{"world"}); err != nil {
|
if err := client.Notify(context.Background(), "test_echo", "hello", 10, &echoArgs{"world"}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -393,9 +393,9 @@ func TestClientHTTP(t *testing.T) {
|
|||||||
|
|
||||||
// Launch concurrent requests.
|
// Launch concurrent requests.
|
||||||
var (
|
var (
|
||||||
results = make([]Result, 100)
|
results = make([]echoResult, 100)
|
||||||
errc = make(chan error)
|
errc = make(chan error)
|
||||||
wantResult = Result{"a", 1, new(Args)}
|
wantResult = echoResult{"a", 1, new(echoArgs)}
|
||||||
)
|
)
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
for i := range results {
|
for i := range results {
|
||||||
@ -450,7 +450,7 @@ func TestClientReconnect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Perform a call. This should work because the server is up.
|
// Perform a call. This should work because the server is up.
|
||||||
var resp Result
|
var resp echoResult
|
||||||
if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil {
|
if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -478,7 +478,7 @@ func TestClientReconnect(t *testing.T) {
|
|||||||
for i := 0; i < cap(errors); i++ {
|
for i := 0; i < cap(errors); i++ {
|
||||||
go func() {
|
go func() {
|
||||||
<-start
|
<-start
|
||||||
var resp Result
|
var resp echoResult
|
||||||
errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil)
|
errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
10
rpc/doc.go
10
rpc/doc.go
@ -29,8 +29,6 @@ Methods that satisfy the following criteria are made available for remote access
|
|||||||
|
|
||||||
- method must be exported
|
- method must be exported
|
||||||
- method returns 0, 1 (response or error) or 2 (response and error) values
|
- method returns 0, 1 (response or error) or 2 (response and error) values
|
||||||
- method argument(s) must be exported or builtin types
|
|
||||||
- method returned value(s) must be exported or builtin types
|
|
||||||
|
|
||||||
An example method:
|
An example method:
|
||||||
|
|
||||||
@ -74,13 +72,8 @@ An example server which uses the JSON codec:
|
|||||||
calculator := new(CalculatorService)
|
calculator := new(CalculatorService)
|
||||||
server := NewServer()
|
server := NewServer()
|
||||||
server.RegisterName("calculator", calculator)
|
server.RegisterName("calculator", calculator)
|
||||||
|
|
||||||
l, _ := net.ListenUnix("unix", &net.UnixAddr{Net: "unix", Name: "/tmp/calculator.sock"})
|
l, _ := net.ListenUnix("unix", &net.UnixAddr{Net: "unix", Name: "/tmp/calculator.sock"})
|
||||||
for {
|
server.ServeListener(l)
|
||||||
c, _ := l.AcceptUnix()
|
|
||||||
codec := v2.NewJSONCodec(c)
|
|
||||||
go server.ServeCodec(codec, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Subscriptions
|
Subscriptions
|
||||||
|
|
||||||
@ -90,7 +83,6 @@ criteria:
|
|||||||
|
|
||||||
- method must be exported
|
- method must be exported
|
||||||
- first method argument type must be context.Context
|
- first method argument type must be context.Context
|
||||||
- method argument(s) must be exported or builtin types
|
|
||||||
- method must have return types (rpc.Subscription, error)
|
- method must have return types (rpc.Subscription, error)
|
||||||
|
|
||||||
An example method:
|
An example method:
|
||||||
|
@ -189,7 +189,7 @@ func (h *handler) cancelAllRequests(err error, inflightReq *requestOp) {
|
|||||||
}
|
}
|
||||||
for id, sub := range h.clientSubs {
|
for id, sub := range h.clientSubs {
|
||||||
delete(h.clientSubs, id)
|
delete(h.clientSubs, id)
|
||||||
sub.quitWithError(err, false)
|
sub.quitWithError(false, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,14 +153,6 @@ type ConnRemoteAddr interface {
|
|||||||
RemoteAddr() string
|
RemoteAddr() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// connWithRemoteAddr overrides the remote address of a connection.
|
|
||||||
type connWithRemoteAddr struct {
|
|
||||||
Conn
|
|
||||||
addr string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c connWithRemoteAddr) RemoteAddr() string { return c.addr }
|
|
||||||
|
|
||||||
// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has
|
// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has
|
||||||
// support for parsing arguments and serializing (result) objects.
|
// support for parsing arguments and serializing (result) objects.
|
||||||
type jsonCodec struct {
|
type jsonCodec struct {
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
@ -139,16 +138,14 @@ func newCallback(receiver, fn reflect.Value) *callback {
|
|||||||
c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)}
|
c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)}
|
||||||
// Determine parameter types. They must all be exported or builtin types.
|
// Determine parameter types. They must all be exported or builtin types.
|
||||||
c.makeArgTypes()
|
c.makeArgTypes()
|
||||||
if !allExportedOrBuiltin(c.argTypes) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Verify return types. The function must return at most one error
|
// Verify return types. The function must return at most one error
|
||||||
// and/or one other non-error value.
|
// and/or one other non-error value.
|
||||||
outs := make([]reflect.Type, fntype.NumOut())
|
outs := make([]reflect.Type, fntype.NumOut())
|
||||||
for i := 0; i < fntype.NumOut(); i++ {
|
for i := 0; i < fntype.NumOut(); i++ {
|
||||||
outs[i] = fntype.Out(i)
|
outs[i] = fntype.Out(i)
|
||||||
}
|
}
|
||||||
if len(outs) > 2 || !allExportedOrBuiltin(outs) {
|
if len(outs) > 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// If an error is returned, it must be the last returned value.
|
// If an error is returned, it must be the last returned value.
|
||||||
@ -218,27 +215,6 @@ func (c *callback) call(ctx context.Context, method string, args []reflect.Value
|
|||||||
return results[0].Interface(), nil
|
return results[0].Interface(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this an exported - upper case - name?
|
|
||||||
func isExported(name string) bool {
|
|
||||||
rune, _ := utf8.DecodeRuneInString(name)
|
|
||||||
return unicode.IsUpper(rune)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Are all those types exported or built-in?
|
|
||||||
func allExportedOrBuiltin(types []reflect.Type) bool {
|
|
||||||
for _, typ := range types {
|
|
||||||
for typ.Kind() == reflect.Ptr {
|
|
||||||
typ = typ.Elem()
|
|
||||||
}
|
|
||||||
// PkgPath will be non-empty even for an exported type,
|
|
||||||
// so we need to check the type name as well.
|
|
||||||
if !isExported(typ.Name()) && typ.PkgPath() != "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is t context.Context or *context.Context?
|
// Is t context.Context or *context.Context?
|
||||||
func isContextType(t reflect.Type) bool {
|
func isContextType(t reflect.Type) bool {
|
||||||
for t.Kind() == reflect.Ptr {
|
for t.Kind() == reflect.Ptr {
|
||||||
|
@ -241,11 +241,11 @@ func (sub *ClientSubscription) Err() <-chan error {
|
|||||||
// Unsubscribe unsubscribes the notification and closes the error channel.
|
// Unsubscribe unsubscribes the notification and closes the error channel.
|
||||||
// It can safely be called more than once.
|
// It can safely be called more than once.
|
||||||
func (sub *ClientSubscription) Unsubscribe() {
|
func (sub *ClientSubscription) Unsubscribe() {
|
||||||
sub.quitWithError(nil, true)
|
sub.quitWithError(true, nil)
|
||||||
sub.errOnce.Do(func() { close(sub.err) })
|
sub.errOnce.Do(func() { close(sub.err) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sub *ClientSubscription) quitWithError(err error, unsubscribeServer bool) {
|
func (sub *ClientSubscription) quitWithError(unsubscribeServer bool, err error) {
|
||||||
sub.quitOnce.Do(func() {
|
sub.quitOnce.Do(func() {
|
||||||
// The dispatch loop won't be able to execute the unsubscribe call
|
// The dispatch loop won't be able to execute the unsubscribe call
|
||||||
// if it is blocked on deliver. Close sub.quit first because it
|
// if it is blocked on deliver. Close sub.quit first because it
|
||||||
@ -276,7 +276,7 @@ func (sub *ClientSubscription) start() {
|
|||||||
sub.quitWithError(sub.forward())
|
sub.quitWithError(sub.forward())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sub *ClientSubscription) forward() (err error, unsubscribeServer bool) {
|
func (sub *ClientSubscription) forward() (unsubscribeServer bool, err error) {
|
||||||
cases := []reflect.SelectCase{
|
cases := []reflect.SelectCase{
|
||||||
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)},
|
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)},
|
||||||
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)},
|
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)},
|
||||||
@ -298,14 +298,14 @@ func (sub *ClientSubscription) forward() (err error, unsubscribeServer bool) {
|
|||||||
|
|
||||||
switch chosen {
|
switch chosen {
|
||||||
case 0: // <-sub.quit
|
case 0: // <-sub.quit
|
||||||
return nil, false
|
return false, nil
|
||||||
case 1: // <-sub.in
|
case 1: // <-sub.in
|
||||||
val, err := sub.unmarshal(recv.Interface().(json.RawMessage))
|
val, err := sub.unmarshal(recv.Interface().(json.RawMessage))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, true
|
return true, err
|
||||||
}
|
}
|
||||||
if buffer.Len() == maxClientSubscriptionBuffer {
|
if buffer.Len() == maxClientSubscriptionBuffer {
|
||||||
return ErrSubscriptionQueueOverflow, true
|
return true, ErrSubscriptionQueueOverflow
|
||||||
}
|
}
|
||||||
buffer.PushBack(val)
|
buffer.PushBack(val)
|
||||||
case 2: // sub.channel<-
|
case 2: // sub.channel<-
|
||||||
|
@ -53,24 +53,24 @@ func sequentialIDGenerator() func() ID {
|
|||||||
|
|
||||||
type testService struct{}
|
type testService struct{}
|
||||||
|
|
||||||
type Args struct {
|
type echoArgs struct {
|
||||||
S string
|
S string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type echoResult struct {
|
||||||
String string
|
String string
|
||||||
Int int
|
Int int
|
||||||
Args *Args
|
Args *echoArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testService) NoArgsRets() {}
|
func (s *testService) NoArgsRets() {}
|
||||||
|
|
||||||
func (s *testService) Echo(str string, i int, args *Args) Result {
|
func (s *testService) Echo(str string, i int, args *echoArgs) echoResult {
|
||||||
return Result{str, i, args}
|
return echoResult{str, i, args}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testService) EchoWithCtx(ctx context.Context, str string, i int, args *Args) Result {
|
func (s *testService) EchoWithCtx(ctx context.Context, str string, i int, args *echoArgs) echoResult {
|
||||||
return Result{str, i, args}
|
return echoResult{str, i, args}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *testService) Sleep(ctx context.Context, duration time.Duration) {
|
func (s *testService) Sleep(ctx context.Context, duration time.Duration) {
|
||||||
@ -81,6 +81,7 @@ func (s *testService) Rets() (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//lint:ignore ST1008 returns error first on purpose.
|
||||||
func (s *testService) InvalidRets1() (error, string) {
|
func (s *testService) InvalidRets1() (error, string) {
|
||||||
return nil, ""
|
return nil, ""
|
||||||
}
|
}
|
||||||
|
@ -97,9 +97,8 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if blckNum > math.MaxInt64 {
|
if blckNum > math.MaxInt64 {
|
||||||
return fmt.Errorf("Blocknumber too high")
|
return fmt.Errorf("block number larger than int64")
|
||||||
}
|
}
|
||||||
|
|
||||||
*bn = BlockNumber(blckNum)
|
*bn = BlockNumber(blckNum)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ func TestWebsocketLargeCall(t *testing.T) {
|
|||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
// This call sends slightly less than the limit and should work.
|
// This call sends slightly less than the limit and should work.
|
||||||
var result Result
|
var result echoResult
|
||||||
arg := strings.Repeat("x", maxRequestContentLength-200)
|
arg := strings.Repeat("x", maxRequestContentLength-200)
|
||||||
if err := client.Call(&result, "test_echo", arg, 1); err != nil {
|
if err := client.Call(&result, "test_echo", arg, 1); err != nil {
|
||||||
t.Fatalf("valid call didn't work: %v", err)
|
t.Fatalf("valid call didn't work: %v", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user