github.com/evdatsion/aphelion-dpos-bft@v0.32.1/rpc/lib/types/types.go (about) 1 package rpctypes 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "reflect" 9 "strings" 10 11 "github.com/pkg/errors" 12 13 amino "github.com/evdatsion/go-amino" 14 ) 15 16 // a wrapper to emulate a sum type: jsonrpcid = string | int 17 // TODO: refactor when Go 2.0 arrives https://github.com/golang/go/issues/19412 18 type jsonrpcid interface { 19 isJSONRPCID() 20 } 21 22 // JSONRPCStringID a wrapper for JSON-RPC string IDs 23 type JSONRPCStringID string 24 25 func (JSONRPCStringID) isJSONRPCID() {} 26 27 // JSONRPCIntID a wrapper for JSON-RPC integer IDs 28 type JSONRPCIntID int 29 30 func (JSONRPCIntID) isJSONRPCID() {} 31 32 func idFromInterface(idInterface interface{}) (jsonrpcid, error) { 33 switch id := idInterface.(type) { 34 case string: 35 return JSONRPCStringID(id), nil 36 case float64: 37 // json.Unmarshal uses float64 for all numbers 38 // (https://golang.org/pkg/encoding/json/#Unmarshal), 39 // but the JSONRPC2.0 spec says the id SHOULD NOT contain 40 // decimals - so we truncate the decimals here. 41 return JSONRPCIntID(int(id)), nil 42 default: 43 typ := reflect.TypeOf(id) 44 return nil, fmt.Errorf("JSON-RPC ID (%v) is of unknown type (%v)", id, typ) 45 } 46 } 47 48 //---------------------------------------- 49 // REQUEST 50 51 type RPCRequest struct { 52 JSONRPC string `json:"jsonrpc"` 53 ID jsonrpcid `json:"id"` 54 Method string `json:"method"` 55 Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} 56 } 57 58 // UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int 59 func (request *RPCRequest) UnmarshalJSON(data []byte) error { 60 unsafeReq := &struct { 61 JSONRPC string `json:"jsonrpc"` 62 ID interface{} `json:"id"` 63 Method string `json:"method"` 64 Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} 65 }{} 66 err := json.Unmarshal(data, &unsafeReq) 67 if err != nil { 68 return err 69 } 70 request.JSONRPC = unsafeReq.JSONRPC 71 request.Method = unsafeReq.Method 72 request.Params = unsafeReq.Params 73 if unsafeReq.ID == nil { 74 return nil 75 } 76 id, err := idFromInterface(unsafeReq.ID) 77 if err != nil { 78 return err 79 } 80 request.ID = id 81 return nil 82 } 83 84 func NewRPCRequest(id jsonrpcid, method string, params json.RawMessage) RPCRequest { 85 return RPCRequest{ 86 JSONRPC: "2.0", 87 ID: id, 88 Method: method, 89 Params: params, 90 } 91 } 92 93 func (req RPCRequest) String() string { 94 return fmt.Sprintf("[%s %s]", req.ID, req.Method) 95 } 96 97 func MapToRequest(cdc *amino.Codec, id jsonrpcid, method string, params map[string]interface{}) (RPCRequest, error) { 98 var params_ = make(map[string]json.RawMessage, len(params)) 99 for name, value := range params { 100 valueJSON, err := cdc.MarshalJSON(value) 101 if err != nil { 102 return RPCRequest{}, err 103 } 104 params_[name] = valueJSON 105 } 106 payload, err := json.Marshal(params_) // NOTE: Amino doesn't handle maps yet. 107 if err != nil { 108 return RPCRequest{}, err 109 } 110 request := NewRPCRequest(id, method, payload) 111 return request, nil 112 } 113 114 func ArrayToRequest(cdc *amino.Codec, id jsonrpcid, method string, params []interface{}) (RPCRequest, error) { 115 var params_ = make([]json.RawMessage, len(params)) 116 for i, value := range params { 117 valueJSON, err := cdc.MarshalJSON(value) 118 if err != nil { 119 return RPCRequest{}, err 120 } 121 params_[i] = valueJSON 122 } 123 payload, err := json.Marshal(params_) // NOTE: Amino doesn't handle maps yet. 124 if err != nil { 125 return RPCRequest{}, err 126 } 127 request := NewRPCRequest(id, method, payload) 128 return request, nil 129 } 130 131 //---------------------------------------- 132 // RESPONSE 133 134 type RPCError struct { 135 Code int `json:"code"` 136 Message string `json:"message"` 137 Data string `json:"data,omitempty"` 138 } 139 140 func (err RPCError) Error() string { 141 const baseFormat = "RPC error %v - %s" 142 if err.Data != "" { 143 return fmt.Sprintf(baseFormat+": %s", err.Code, err.Message, err.Data) 144 } 145 return fmt.Sprintf(baseFormat, err.Code, err.Message) 146 } 147 148 type RPCResponse struct { 149 JSONRPC string `json:"jsonrpc"` 150 ID jsonrpcid `json:"id"` 151 Result json.RawMessage `json:"result,omitempty"` 152 Error *RPCError `json:"error,omitempty"` 153 } 154 155 // UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int 156 func (response *RPCResponse) UnmarshalJSON(data []byte) error { 157 unsafeResp := &struct { 158 JSONRPC string `json:"jsonrpc"` 159 ID interface{} `json:"id"` 160 Result json.RawMessage `json:"result,omitempty"` 161 Error *RPCError `json:"error,omitempty"` 162 }{} 163 err := json.Unmarshal(data, &unsafeResp) 164 if err != nil { 165 return err 166 } 167 response.JSONRPC = unsafeResp.JSONRPC 168 response.Error = unsafeResp.Error 169 response.Result = unsafeResp.Result 170 if unsafeResp.ID == nil { 171 return nil 172 } 173 id, err := idFromInterface(unsafeResp.ID) 174 if err != nil { 175 return err 176 } 177 response.ID = id 178 return nil 179 } 180 181 func NewRPCSuccessResponse(cdc *amino.Codec, id jsonrpcid, res interface{}) RPCResponse { 182 var rawMsg json.RawMessage 183 184 if res != nil { 185 var js []byte 186 js, err := cdc.MarshalJSON(res) 187 if err != nil { 188 return RPCInternalError(id, errors.Wrap(err, "Error marshalling response")) 189 } 190 rawMsg = json.RawMessage(js) 191 } 192 193 return RPCResponse{JSONRPC: "2.0", ID: id, Result: rawMsg} 194 } 195 196 func NewRPCErrorResponse(id jsonrpcid, code int, msg string, data string) RPCResponse { 197 return RPCResponse{ 198 JSONRPC: "2.0", 199 ID: id, 200 Error: &RPCError{Code: code, Message: msg, Data: data}, 201 } 202 } 203 204 func (resp RPCResponse) String() string { 205 if resp.Error == nil { 206 return fmt.Sprintf("[%s %v]", resp.ID, resp.Result) 207 } 208 return fmt.Sprintf("[%s %s]", resp.ID, resp.Error) 209 } 210 211 func RPCParseError(id jsonrpcid, err error) RPCResponse { 212 return NewRPCErrorResponse(id, -32700, "Parse error. Invalid JSON", err.Error()) 213 } 214 215 func RPCInvalidRequestError(id jsonrpcid, err error) RPCResponse { 216 return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error()) 217 } 218 219 func RPCMethodNotFoundError(id jsonrpcid) RPCResponse { 220 return NewRPCErrorResponse(id, -32601, "Method not found", "") 221 } 222 223 func RPCInvalidParamsError(id jsonrpcid, err error) RPCResponse { 224 return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error()) 225 } 226 227 func RPCInternalError(id jsonrpcid, err error) RPCResponse { 228 return NewRPCErrorResponse(id, -32603, "Internal error", err.Error()) 229 } 230 231 func RPCServerError(id jsonrpcid, err error) RPCResponse { 232 return NewRPCErrorResponse(id, -32000, "Server error", err.Error()) 233 } 234 235 //---------------------------------------- 236 237 // WSRPCConnection represents a websocket connection. 238 type WSRPCConnection interface { 239 // GetRemoteAddr returns a remote address of the connection. 240 GetRemoteAddr() string 241 // WriteRPCResponse writes the resp onto connection (BLOCKING). 242 WriteRPCResponse(resp RPCResponse) 243 // TryWriteRPCResponse tries to write the resp onto connection (NON-BLOCKING). 244 TryWriteRPCResponse(resp RPCResponse) bool 245 // Codec returns an Amino codec used. 246 Codec() *amino.Codec 247 // Context returns the connection's context. 248 Context() context.Context 249 } 250 251 // Context is the first parameter for all functions. It carries a json-rpc 252 // request, http request and websocket connection. 253 // 254 // - JSONReq is non-nil when JSONRPC is called over websocket or HTTP. 255 // - WSConn is non-nil when we're connected via a websocket. 256 // - HTTPReq is non-nil when URI or JSONRPC is called over HTTP. 257 type Context struct { 258 // json-rpc request 259 JSONReq *RPCRequest 260 // websocket connection 261 WSConn WSRPCConnection 262 // http request 263 HTTPReq *http.Request 264 } 265 266 // RemoteAddr returns the remote address (usually a string "IP:port"). 267 // If neither HTTPReq nor WSConn is set, an empty string is returned. 268 // HTTP: 269 // http.Request#RemoteAddr 270 // WS: 271 // result of GetRemoteAddr 272 func (ctx *Context) RemoteAddr() string { 273 if ctx.HTTPReq != nil { 274 return ctx.HTTPReq.RemoteAddr 275 } else if ctx.WSConn != nil { 276 return ctx.WSConn.GetRemoteAddr() 277 } 278 return "" 279 } 280 281 // Context returns the request's context. 282 // The returned context is always non-nil; it defaults to the background context. 283 // HTTP: 284 // The context is canceled when the client's connection closes, the request 285 // is canceled (with HTTP/2), or when the ServeHTTP method returns. 286 // WS: 287 // The context is canceled when the client's connections closes. 288 func (ctx *Context) Context() context.Context { 289 if ctx.HTTPReq != nil { 290 return ctx.HTTPReq.Context() 291 } else if ctx.WSConn != nil { 292 return ctx.WSConn.Context() 293 } 294 return context.Background() 295 } 296 297 //---------------------------------------- 298 // SOCKETS 299 300 // 301 // Determine if its a unix or tcp socket. 302 // If tcp, must specify the port; `0.0.0.0` will return incorrectly as "unix" since there's no port 303 // TODO: deprecate 304 func SocketType(listenAddr string) string { 305 socketType := "unix" 306 if len(strings.Split(listenAddr, ":")) >= 2 { 307 socketType = "tcp" 308 } 309 return socketType 310 }