Skip to content

Commit 70f44a9

Browse files
authored
Merge pull request #83 from filecoin-project/raulk/fix-id
make request and response ID handling spec-compliant.
2 parents 236bc02 + 666479d commit 70f44a9

File tree

5 files changed

+105
-42
lines changed

5 files changed

+105
-42
lines changed

client.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (e *ErrClient) Unwrap() error {
6767
type clientResponse struct {
6868
Jsonrpc string `json:"jsonrpc"`
6969
Result json.RawMessage `json:"result"`
70-
ID int64 `json:"id"`
70+
ID interface{} `json:"id"`
7171
Error *respError `json:"error,omitempty"`
7272
}
7373

@@ -167,12 +167,15 @@ func httpClient(ctx context.Context, addr string, namespace string, outs []inter
167167
defer httpResp.Body.Close()
168168

169169
var resp clientResponse
170-
171170
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
172171
return clientResponse{}, xerrors.Errorf("http status %s unmarshaling response: %w", httpResp.Status, err)
173172
}
174173

175-
if resp.ID != *cr.req.ID {
174+
if resp.ID, err = normalizeID(resp.ID); err != nil {
175+
return clientResponse{}, xerrors.Errorf("failed to response ID: %w", err)
176+
}
177+
178+
if resp.ID != cr.req.ID {
176179
return clientResponse{}, xerrors.New("request and response id didn't match")
177180
}
178181

@@ -246,7 +249,7 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs []
246249
req: request{
247250
Jsonrpc: "2.0",
248251
Method: wsCancel,
249-
Params: []param{{v: reflect.ValueOf(*cr.req.ID)}},
252+
Params: []param{{v: reflect.ValueOf(cr.req.ID)}},
250253
},
251254
}
252255
select {
@@ -468,7 +471,7 @@ func (fn *rpcFunc) processError(err error) []reflect.Value {
468471
}
469472

470473
func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value) {
471-
id := atomic.AddInt64(&fn.client.idCtr, 1)
474+
var id interface{} = atomic.AddInt64(&fn.client.idCtr, 1)
472475
params := make([]param, len(args)-fn.hasCtx)
473476
for i, arg := range args[fn.hasCtx:] {
474477
enc, found := fn.client.paramEncoders[arg.Type()]
@@ -503,9 +506,19 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
503506
retVal, chCtor = fn.client.makeOutChan(ctx, fn.ftyp, fn.valOut)
504507
}
505508

509+
// Prepare the ID to send on the wire.
510+
// We track int64 ids as float64 in the inflight map (because that's what
511+
// they'll be decoded to). encoding/json outputs numbers with their minimal
512+
// encoding, avoding the decimal point when possible, i.e. 3 will never get
513+
// converted to 3.0.
514+
id, err := normalizeID(id)
515+
if err != nil {
516+
return fn.processError(fmt.Errorf("failed to normalize id")) // should probably panic
517+
}
518+
506519
req := request{
507520
Jsonrpc: "2.0",
508-
ID: &id,
521+
ID: id,
509522
Method: fn.client.namespace + "." + fn.name,
510523
Params: params,
511524
}
@@ -526,7 +539,6 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
526539
}
527540

528541
var resp clientResponse
529-
var err error
530542
// keep retrying if got a forced closed websocket conn and calling method
531543
// has retry annotation
532544
for attempt := 0; true; attempt++ {
@@ -535,7 +547,7 @@ func (fn *rpcFunc) handleRpcCall(args []reflect.Value) (results []reflect.Value)
535547
return fn.processError(fmt.Errorf("sendRequest failed: %w", err))
536548
}
537549

538-
if resp.ID != *req.ID {
550+
if resp.ID != req.ID {
539551
return fn.processError(xerrors.New("request and response id didn't match"))
540552
}
541553

handler.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type rpcHandler struct {
3737

3838
type request struct {
3939
Jsonrpc string `json:"jsonrpc"`
40-
ID *int64 `json:"id,omitempty"`
40+
ID interface{} `json:"id,omitempty"`
4141
Method string `json:"method"`
4242
Params []param `json:"params"`
4343
Meta map[string]string `json:"meta,omitempty"`
@@ -90,15 +90,15 @@ func (e *respError) val(errors *Errors) reflect.Value {
9090
type response struct {
9191
Jsonrpc string `json:"jsonrpc"`
9292
Result interface{} `json:"result,omitempty"`
93-
ID int64 `json:"id"`
93+
ID interface{} `json:"id"`
9494
Error *respError `json:"error,omitempty"`
9595
}
9696

9797
// Register
9898

9999
func (s *RPCServer) register(namespace string, r interface{}) {
100100
val := reflect.ValueOf(r)
101-
//TODO: expect ptr
101+
// TODO: expect ptr
102102

103103
for i := 0; i < val.NumMethod(); i++ {
104104
method := val.Type().Method(i)
@@ -135,7 +135,7 @@ func (s *RPCServer) register(namespace string, r interface{}) {
135135
// Handle
136136

137137
type rpcErrFunc func(w func(func(io.Writer)), req *request, code ErrorCode, err error)
138-
type chanOut func(reflect.Value, int64) error
138+
type chanOut func(reflect.Value, interface{}) error
139139

140140
func (s *RPCServer) handleReader(ctx context.Context, r io.Reader, w io.Writer, rpcError rpcErrFunc) {
141141
wf := func(cb func(io.Writer)) {
@@ -174,6 +174,11 @@ func (s *RPCServer) handleReader(ctx context.Context, r io.Reader, w io.Writer,
174174
return
175175
}
176176

177+
if req.ID, err = normalizeID(req.ID); err != nil {
178+
rpcError(wf, &req, rpcParseError, xerrors.Errorf("failed to parse ID: %w", err))
179+
return
180+
}
181+
177182
s.handle(ctx, req, wf, rpcError, func(bool) {}, nil)
178183
}
179184

@@ -304,7 +309,7 @@ func (s *RPCServer) handle(ctx context.Context, req request, w func(func(io.Writ
304309
callParams[i+1+handler.hasCtx] = reflect.ValueOf(rp.Interface())
305310
}
306311

307-
///////////////////
312+
// /////////////////
308313

309314
callResult, err := doCall(req.Method, handler.handlerFunc, callParams)
310315
if err != nil {
@@ -316,11 +321,11 @@ func (s *RPCServer) handle(ctx context.Context, req request, w func(func(io.Writ
316321
return // notification
317322
}
318323

319-
///////////////////
324+
// /////////////////
320325

321326
resp := response{
322327
Jsonrpc: "2.0",
323-
ID: *req.ID,
328+
ID: req.ID,
324329
}
325330

326331
if handler.errOut != -1 {
@@ -350,7 +355,7 @@ func (s *RPCServer) handle(ctx context.Context, req request, w func(func(io.Writ
350355
// sending channel messages before this rpc call returns
351356

352357
//noinspection GoNilness // already checked above
353-
err = chOut(callResult[handler.valOut], *req.ID)
358+
err = chOut(callResult[handler.valOut], req.ID)
354359
if err == nil {
355360
return // channel goroutine handles responding
356361
}

rpc_test.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ func testControlChanDeadlock(t *testing.T) {
944944
for i := 0; i < n; i++ {
945945
if <-sub != i+1 {
946946
panic("bad!")
947-
//require.Equal(t, i+1, <-sub)
947+
// require.Equal(t, i+1, <-sub)
948948
}
949949
}
950950
}()
@@ -1107,3 +1107,35 @@ func TestUserError(t *testing.T) {
11071107

11081108
closer()
11091109
}
1110+
1111+
// Unit test for request/response ID translation.
1112+
func TestIDHandling(t *testing.T) {
1113+
var decoded request
1114+
1115+
cases := []struct {
1116+
str string
1117+
expect interface{}
1118+
expectErr bool
1119+
}{
1120+
{`{"id":"8116d306-56cc-4637-9dd7-39ce1548a5a0","jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, "8116d306-56cc-4637-9dd7-39ce1548a5a0", false},
1121+
{`{"id":1234,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, float64(1234), false},
1122+
{`{"id":null,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, nil, false},
1123+
{`{"id":1234.0,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, 1234.0, false},
1124+
{`{"id":1.2,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, 1.2, false},
1125+
{`{"id":["1"],"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, nil, true},
1126+
{`{"id":{"a":"b"},"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}`, nil, true},
1127+
}
1128+
1129+
for _, tc := range cases {
1130+
t.Run(fmt.Sprintf("%v", tc.expect), func(t *testing.T) {
1131+
dec := json.NewDecoder(strings.NewReader(tc.str))
1132+
require.NoError(t, dec.Decode(&decoded))
1133+
if id, err := normalizeID(decoded.ID); !tc.expectErr {
1134+
require.NoError(t, err)
1135+
require.Equal(t, tc.expect, id)
1136+
} else {
1137+
require.Error(t, err)
1138+
}
1139+
})
1140+
}
1141+
}

server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func rpcError(wf func(func(io.Writer)), req *request, code ErrorCode, err error)
108108

109109
resp := response{
110110
Jsonrpc: "2.0",
111-
ID: *req.ID,
111+
ID: req.ID,
112112
Error: &respError{
113113
Code: code,
114114
Message: err.Error(),

0 commit comments

Comments
 (0)