171 lines
3.4 KiB
Go
171 lines
3.4 KiB
Go
package proxyd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
type RPCReq struct {
|
|
JSONRPC string `json:"jsonrpc"`
|
|
Method string `json:"method"`
|
|
Params json.RawMessage `json:"params"`
|
|
ID json.RawMessage `json:"id"`
|
|
}
|
|
|
|
type RPCRes struct {
|
|
JSONRPC string
|
|
Result interface{}
|
|
Error *RPCErr
|
|
ID json.RawMessage
|
|
}
|
|
|
|
type rpcResJSON struct {
|
|
JSONRPC string `json:"jsonrpc"`
|
|
Result interface{} `json:"result,omitempty"`
|
|
Error *RPCErr `json:"error,omitempty"`
|
|
ID json.RawMessage `json:"id"`
|
|
}
|
|
|
|
type nullResultRPCRes struct {
|
|
JSONRPC string `json:"jsonrpc"`
|
|
Result interface{} `json:"result"`
|
|
ID json.RawMessage `json:"id"`
|
|
}
|
|
|
|
func (r *RPCRes) IsError() bool {
|
|
return r.Error != nil
|
|
}
|
|
|
|
func (r *RPCRes) MarshalJSON() ([]byte, error) {
|
|
if r.Result == nil && r.Error == nil {
|
|
return json.Marshal(&nullResultRPCRes{
|
|
JSONRPC: r.JSONRPC,
|
|
Result: nil,
|
|
ID: r.ID,
|
|
})
|
|
}
|
|
|
|
return json.Marshal(&rpcResJSON{
|
|
JSONRPC: r.JSONRPC,
|
|
Result: r.Result,
|
|
Error: r.Error,
|
|
ID: r.ID,
|
|
})
|
|
}
|
|
|
|
type RPCErr struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Data string `json:"data,omitempty"`
|
|
HTTPErrorCode int `json:"-"`
|
|
}
|
|
|
|
func (r *RPCErr) Error() string {
|
|
return r.Message
|
|
}
|
|
|
|
func (r *RPCErr) Clone() *RPCErr {
|
|
return &RPCErr{
|
|
Code: r.Code,
|
|
Message: r.Message,
|
|
HTTPErrorCode: r.HTTPErrorCode,
|
|
}
|
|
}
|
|
|
|
func IsValidID(id json.RawMessage) bool {
|
|
// handle the case where the ID is a string
|
|
if strings.HasPrefix(string(id), "\"") && strings.HasSuffix(string(id), "\"") {
|
|
return len(id) > 2
|
|
}
|
|
|
|
// technically allows a boolean/null ID, but so does Geth
|
|
// https://github.com/ethereum/go-ethereum/blob/master/rpc/json.go#L72
|
|
return len(id) > 0 && id[0] != '{' && id[0] != '['
|
|
}
|
|
|
|
func ParseRPCReq(body []byte) (*RPCReq, error) {
|
|
req := new(RPCReq)
|
|
if err := json.Unmarshal(body, req); err != nil {
|
|
return nil, ErrParseErr
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
func ParseBatchRPCReq(body []byte) ([]json.RawMessage, error) {
|
|
batch := make([]json.RawMessage, 0)
|
|
if err := json.Unmarshal(body, &batch); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return batch, nil
|
|
}
|
|
|
|
func ParseRPCRes(r io.Reader) (*RPCRes, error) {
|
|
body, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return nil, wrapErr(err, "error reading RPC response")
|
|
}
|
|
|
|
res := new(RPCRes)
|
|
if err := json.Unmarshal(body, res); err != nil {
|
|
return nil, wrapErr(err, "error unmarshalling RPC response")
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func ValidateRPCReq(req *RPCReq) error {
|
|
if req.JSONRPC != JSONRPCVersion {
|
|
return ErrInvalidRequest("invalid JSON-RPC version")
|
|
}
|
|
|
|
if req.Method == "" {
|
|
return ErrInvalidRequest("no method specified")
|
|
}
|
|
|
|
if !IsValidID(req.ID) {
|
|
return ErrInvalidRequest("invalid ID")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewRPCErrorRes(id json.RawMessage, err error) *RPCRes {
|
|
var rpcErr *RPCErr
|
|
if rr, ok := err.(*RPCErr); ok {
|
|
rpcErr = rr
|
|
} else {
|
|
rpcErr = &RPCErr{
|
|
Code: JSONRPCErrorInternal,
|
|
Message: err.Error(),
|
|
}
|
|
}
|
|
|
|
return &RPCRes{
|
|
JSONRPC: JSONRPCVersion,
|
|
Error: rpcErr,
|
|
ID: id,
|
|
}
|
|
}
|
|
|
|
func NewRPCRes(id json.RawMessage, result interface{}) *RPCRes {
|
|
return &RPCRes{
|
|
JSONRPC: JSONRPCVersion,
|
|
Result: result,
|
|
ID: id,
|
|
}
|
|
}
|
|
|
|
func IsBatch(raw []byte) bool {
|
|
for _, c := range raw {
|
|
// skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt)
|
|
if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d {
|
|
continue
|
|
}
|
|
return c == '['
|
|
}
|
|
return false
|
|
}
|