Files
tendermint/rpc/jsonrpc/client/decode.go
M. J. Fromberger 6291d22f46 rpc: simplify the JSON-RPC client Caller interface (#7549)
* Update Caller interface and its documentation.
* Rework MapToRequest as ParamsToRequest.

The old interface returned the result as well as populating it.  Nothing was
using this, so drop the duplicated value from the return signature. Clarify the
documentation on the Caller type.

Rework the MapToRequest helper to take an arbitrary value instead of only a
map. This is groundwork for getting rid of the custom marshaling code. For now,
however, the implementation preserves the existing behaviour for the map, until
we can replace those.
2022-01-10 13:56:43 -08:00

121 lines
3.1 KiB
Go

package client
import (
"encoding/json"
"errors"
"fmt"
tmjson "github.com/tendermint/tendermint/libs/json"
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
)
func unmarshalResponseBytes(responseBytes []byte, expectedID rpctypes.JSONRPCIntID, result interface{}) error {
// Read response. If rpc/core/types is imported, the result will unmarshal
// into the correct type.
response := &rpctypes.RPCResponse{}
if err := json.Unmarshal(responseBytes, response); err != nil {
return fmt.Errorf("error unmarshaling: %w", err)
}
if response.Error != nil {
return response.Error
}
if err := validateAndVerifyID(response, expectedID); err != nil {
return fmt.Errorf("wrong ID: %w", err)
}
// Unmarshal the RawMessage into the result.
if err := tmjson.Unmarshal(response.Result, result); err != nil {
return fmt.Errorf("error unmarshaling result: %w", err)
}
return nil
}
func unmarshalResponseBytesArray(
responseBytes []byte,
expectedIDs []rpctypes.JSONRPCIntID,
results []interface{},
) ([]interface{}, error) {
var (
responses []rpctypes.RPCResponse
)
if err := json.Unmarshal(responseBytes, &responses); err != nil {
return nil, fmt.Errorf("error unmarshaling: %w", err)
}
// No response error checking here as there may be a mixture of successful
// and unsuccessful responses.
if len(results) != len(responses) {
return nil, fmt.Errorf(
"expected %d result objects into which to inject responses, but got %d",
len(responses),
len(results),
)
}
// Intersect IDs from responses with expectedIDs.
ids := make([]rpctypes.JSONRPCIntID, len(responses))
var ok bool
for i, resp := range responses {
ids[i], ok = resp.ID.(rpctypes.JSONRPCIntID)
if !ok {
return nil, fmt.Errorf("expected JSONRPCIntID, got %T", resp.ID)
}
}
if err := validateResponseIDs(ids, expectedIDs); err != nil {
return nil, fmt.Errorf("wrong IDs: %w", err)
}
for i := 0; i < len(responses); i++ {
if err := tmjson.Unmarshal(responses[i].Result, results[i]); err != nil {
return nil, fmt.Errorf("error unmarshaling #%d result: %w", i, err)
}
}
return results, nil
}
func validateResponseIDs(ids, expectedIDs []rpctypes.JSONRPCIntID) error {
m := make(map[rpctypes.JSONRPCIntID]bool, len(expectedIDs))
for _, expectedID := range expectedIDs {
m[expectedID] = true
}
for i, id := range ids {
if m[id] {
delete(m, id)
} else {
return fmt.Errorf("unsolicited ID #%d: %v", i, id)
}
}
return nil
}
// From the JSON-RPC 2.0 spec:
// id: It MUST be the same as the value of the id member in the Request Object.
func validateAndVerifyID(res *rpctypes.RPCResponse, expectedID rpctypes.JSONRPCIntID) error {
if err := validateResponseID(res.ID); err != nil {
return err
}
if expectedID != res.ID.(rpctypes.JSONRPCIntID) { // validateResponseID ensured res.ID has the right type
return fmt.Errorf("response ID (%d) does not match request ID (%d)", res.ID, expectedID)
}
return nil
}
func validateResponseID(id interface{}) error {
if id == nil {
return errors.New("no ID")
}
_, ok := id.(rpctypes.JSONRPCIntID)
if !ok {
return fmt.Errorf("expected JSONRPCIntID, but got: %T", id)
}
return nil
}