Skip to content

Commit 10436a0

Browse files
Merge pull request #14 from decentrio/netflix/jsonrpc
jsonrpc fixes
2 parents b5a1683 + 3c636c9 commit 10436a0

File tree

5 files changed

+215
-79
lines changed

5 files changed

+215
-79
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ upstream:
2828
# If a port is set to 0, the service of that port won't start.
2929
port:
3030
rpc: 26657
31-
api: 1317
31+
api: 0 # Disable API service
3232
grpc: 9090
3333
jsonrpc: 8545
3434
jsonrpc_ws: 8546
@@ -115,3 +115,16 @@ grpcurl -d '{"height": "123"}' \
115115
grpcurl -plaintext -d '{"hash": "64DFDC0F4B9096ADFC644B2DF087E7B9225C8601719C4C2BB2E979AD83081713"}' \
116116
localhost:5002 cosmos.tx.v1beta1.Service/GetTx
117117
```
118+
119+
### JSON RPC
120+
```bash
121+
curl -X POST "http://localhost:5005" -d '{
122+
"jsonrpc":"2.0",
123+
"method":"eth_getBlockByHash",
124+
"params":[
125+
"0x68f04262ea363216fae99a7498502075c6aacc42bdc4db7c29e7f64c2fab0fda",
126+
true
127+
],
128+
"id":1
129+
}' -H "Content-Type: application/json"
130+
```

config.yaml.example

Lines changed: 0 additions & 23 deletions
This file was deleted.

gateway/json_rpc_server.go

Lines changed: 196 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,46 @@
11
package gateway
22

33
import (
4-
"context"
54
"bytes"
5+
"context"
66
"encoding/json"
77
"fmt"
88
"io"
99
"log"
10+
"math"
1011
"net/http"
1112
"os"
1213
"os/signal"
14+
"strconv"
15+
"strings"
1316
"sync/atomic"
1417
"syscall"
1518
"time"
1619

17-
// "github.com/cometbft/cometbft/rpc/jsonrpc/types"
1820
"github.com/decentrio/gateway/config"
21+
"github.com/decentrio/gateway/utils"
1922
)
2023

24+
// Error type
25+
type JSONRPCError struct {
26+
Code int `json:"code"`
27+
Message string `json:"message"`
28+
}
29+
2130
// JSON-RPC request format
2231
type JSONRPCRequest struct {
23-
JSONRPC string `json:"jsonrpc"`
24-
Method string `json:"method"`
25-
Params interface{} `json:"params"`
26-
ID int `json:"id"`
32+
JSONRPC string `json:"jsonrpc"`
33+
ID int `json:"id"`
34+
Method string `json:"method"`
35+
Params json.RawMessage `json:"params"`
2736
}
2837

2938
// JSON-RPC response format
3039
type JSONRPCResponse struct {
3140
JSONRPC string `json:"jsonrpc"`
32-
Result interface{} `json:"result"`
33-
Error interface{} `json:"error,omitempty"`
3441
ID int `json:"id"`
42+
Result any `json:"result,omitempty"`
43+
Error *JSONRPCError `json:"error,omitempty"`
3544
}
3645

3746
var (
@@ -117,74 +126,213 @@ func trackRequestsMiddleware(next http.HandlerFunc) http.HandlerFunc {
117126
}
118127

119128
func handleJSONRPC(w http.ResponseWriter, r *http.Request) {
120-
var node *config.Node
129+
var req JSONRPCRequest
130+
var res JSONRPCResponse
121131
if r.Method != http.MethodPost {
122-
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
132+
res = JSONRPCResponse{
133+
JSONRPC: "2.0",
134+
Error: &JSONRPCError{Code: -32600, Message: "Invalid request"},
135+
ID: 1,
136+
}
137+
json.NewEncoder(w).Encode(res)
123138
return
124139
}
125140

126141
body, err := io.ReadAll(r.Body)
127142
if err != nil {
128-
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
143+
res = JSONRPCResponse{
144+
JSONRPC: "2.0",
145+
Error: &JSONRPCError{Code: -32600, Message: "Parse error. Invalid JSON: " + err.Error()},
146+
ID: 1,
147+
}
148+
json.NewEncoder(w).Encode(res)
129149
return
130150
}
131-
r.Body.Close()
151+
r.Body = io.NopCloser(bytes.NewReader(body))
152+
r.Header.Set("Content-Type", "application/json")
153+
r.ContentLength = int64(len(body))
132154

133-
var req JSONRPCRequest
134155
err = json.Unmarshal(body, &req)
135156
if err != nil {
136-
http.Error(w, "Invalid JSON-RPC request", http.StatusBadRequest)
157+
res = JSONRPCResponse{
158+
JSONRPC: "2.0",
159+
Error: &JSONRPCError{Code: -32600, Message: "Invalid JSON-RPC request: " + err.Error()},
160+
ID: -32700,
161+
}
162+
json.NewEncoder(w).Encode(res)
137163
return
138164
}
139165

140-
fmt.Printf("Received JSON-RPC request: Method=%s, Params=%v, ID=%d\n", req.Method, req.Params, req.ID)
166+
fmt.Printf("Received JSON-RPC request: Method=%s, Params=%v\n", req.Method, req.Params)
167+
paramsMap := make([]any, len(req.Params))
168+
json.Unmarshal(req.Params, &paramsMap)
169+
var height uint64 = math.MaxUint64
170+
171+
switch req.Method {
172+
case "eth_getTransactionByHash", // tx hash in params
173+
"eth_getTransactionReceipt",
174+
"eth_getBlockByHash", // block hash in params
175+
"eth_getBlockTransactionCountByHash",
176+
"eth_getTransactionByBlockHashAndIndex",
177+
"eth_getUncleByBlockHashAndIndex":
178+
checkRequestManually(w, r)
179+
return
180+
case "eth_newFilter", /// ????
181+
"eth_getLogs":
182+
res = JSONRPCResponse{
183+
JSONRPC: "2.0",
184+
Error: &JSONRPCError{Code: -32600, Message: "Method not supported yet"},
185+
ID: 1,
186+
}
187+
json.NewEncoder(w).Encode(res)
188+
return
189+
case "eth_getBalance",// param 1
190+
"eth_getTransactionCount",
191+
"eth_getCode",
192+
"eth_call":
193+
height, err = getHeightFromParams(paramsMap, 1)
194+
if err != nil {
195+
res = JSONRPCResponse{
196+
JSONRPC: "2.0",
197+
Error: &JSONRPCError{Code: -32600, Message: err.Error()},
198+
ID: 1,
199+
}
200+
json.NewEncoder(w).Encode(res)
201+
return
202+
}
203+
case "eth_getStorageAt": // param 2
204+
height, err = getHeightFromParams(paramsMap, 2)
205+
if err != nil {
206+
res = JSONRPCResponse{
207+
JSONRPC: "2.0",
208+
Error: &JSONRPCError{Code: -32600, Message: err.Error()},
209+
ID: 1,
210+
}
211+
json.NewEncoder(w).Encode(res)
212+
return
213+
}
214+
case "eth_getBlockTransactionCountByNumber", // param 0
215+
"eth_getBlockByNumber",
216+
"eth_getTransactionByBlockNumberAndIndex",
217+
"eth_getUncleByBlockNumberAndIndex":
218+
height, err = getHeightFromParams(paramsMap, 0)
219+
if err != nil {
220+
res = JSONRPCResponse{
221+
JSONRPC: "2.0",
222+
Error: &JSONRPCError{Code: -32600, Message: err.Error()},
223+
ID: 1,
224+
}
225+
json.NewEncoder(w).Encode(res)
226+
return
227+
}
228+
default:
229+
height = 0
230+
}
141231

142-
var height uint64
143-
if params, ok := req.Params.(map[string]interface{}); ok {
144-
if h, ok := params["height"].(float64); ok {
145-
height = uint64(h)
232+
fmt.Printf("Height: %d\n", height)
233+
node := config.GetNodebyHeight(height)
234+
if node == nil {
235+
res = JSONRPCResponse{
236+
JSONRPC: "2.0",
237+
Error: &JSONRPCError{Code: -32602, Message: "No nodes found"},
238+
ID: 1,
146239
}
240+
241+
json.NewEncoder(w).Encode(res)
242+
return
147243
}
244+
fmt.Println("Node called:", node.JSONRPC)
245+
httpUtils.FowardRequest(w, r, node.JSONRPC)
246+
}
148247

149248

150-
if height > 0 {
151-
node = config.GetNodebyHeight(height)
152-
if node == nil {
153-
http.Error(w, "Node not found", http.StatusNotFound)
154-
return
249+
func getHeightFromParams(params []any, index int) (uint64, error) {
250+
if len(params) > index {
251+
height, found := params[index].(string)
252+
if found {
253+
if height == "latest" || height == "pending" {
254+
return 0, nil
255+
} else if height == "earliest" {
256+
return 1, nil // temporary, should be earliest possible
257+
} else if strings.HasPrefix(height, "0x") {
258+
height = strings.TrimPrefix(height, "0x")
259+
if h, err := strconv.ParseUint(height, 16, 64); err == nil {
260+
return h, nil
261+
} else {
262+
return math.MaxUint64, fmt.Errorf("invalid height parameter: %w", err)
263+
}
264+
} else {
265+
return math.MaxUint64, fmt.Errorf("invalid height parameter")
266+
}
267+
} else {
268+
return math.MaxUint64, fmt.Errorf("height not found")
155269
}
270+
} else {
271+
return math.MaxUint64, fmt.Errorf("invalid params")
156272
}
273+
}
157274

158-
if node != nil {
159-
fmt.Printf("Forwarding to Node:", node.JSONRPC)
160275

161-
reqForward, err := http.NewRequest("POST", node.JSONRPC, bytes.NewReader(body))
162-
if err != nil {
163-
http.Error(w, "Failed to create request", http.StatusInternalServerError)
164-
return
165-
}
276+
func checkRequestManually(w http.ResponseWriter, r *http.Request) {
277+
ETH_nodes := config.GetNodesByType("jsonrpc")
278+
var msg JSONRPCResponse
166279

167-
reqForward.Header.Set("Content-Type", "application/json")
280+
bodyBytes, err := io.ReadAll(r.Body)
281+
if err != nil {
282+
msg = JSONRPCResponse{
283+
JSONRPC: "2.0",
284+
Error: &JSONRPCError{Code: -32600, Message: "Parse error. Invalid JSON: " + err.Error()},
285+
ID: 1,
286+
}
287+
json.NewEncoder(w).Encode(msg)
288+
return
289+
}
168290

169-
client := &http.Client{}
170-
resp, err := client.Do(reqForward)
291+
for _, url := range ETH_nodes {
292+
new_r := r.Clone(r.Context())
293+
new_r.Body = io.NopCloser(bytes.NewReader(bodyBytes))
294+
res, err := httpUtils.CheckRequest(new_r, url)
171295
if err != nil {
172-
http.Error(w, "Failed to forward request", http.StatusBadGateway)
173-
return
296+
continue
174297
}
175-
defer resp.Body.Close()
176298

177-
w.WriteHeader(resp.StatusCode)
178-
io.Copy(w, resp.Body)
179-
return
299+
if res == nil {
300+
continue
301+
} else { // node always returns a 200 response
302+
fmt.Println("Node called:", url)
303+
if res.Body != nil {
304+
body, err := io.ReadAll(res.Body)
305+
if err != nil {
306+
msg = JSONRPCResponse{
307+
JSONRPC: "2.0",
308+
Error: &JSONRPCError{Code: -32600, Message: "Parse error. Invalid JSON: " + err.Error()},
309+
ID: 1,
310+
}
311+
json.NewEncoder(w).Encode(msg)
312+
return
313+
}
314+
315+
json.Unmarshal(body, &msg)
316+
defer res.Body.Close()
317+
}
318+
319+
if msg.Error != nil || msg.Result != nil { // errors in handling request, all the nodes will return the same
320+
json.NewEncoder(w).Encode(msg)
321+
return
322+
} else if msg.Result == nil {
323+
fmt.Println("Result is empty")
324+
continue
325+
}
326+
}
180327
}
181328

182-
resp := JSONRPCResponse{
183-
JSONRPC: "2.0",
184-
Result: fmt.Sprintf("Method %s executed successfully", req.Method),
185-
ID: req.ID,
329+
if msg.Result == nil { // after all loop, result is still nil?
330+
nil_msg := map[string]interface{}{
331+
"jsonrpc": msg.JSONRPC,
332+
"id": msg.ID,
333+
"result": msg.Result,
334+
}
335+
json.NewEncoder(w).Encode(nil_msg)
336+
return
186337
}
187-
188-
w.Header().Set("Content-Type", "application/json")
189-
json.NewEncoder(w).Encode(resp)
190-
}
338+
}

gateway/json_rpc_ws_server.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,9 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
148148
fmt.Printf("Received JSON-RPC WS request: Method=%s, Params=%v, ID=%d\n", req.Method, req.Params, req.ID)
149149

150150
var height uint64
151-
if params, ok := req.Params.(map[string]interface{}); ok {
152-
if h, ok := params["height"].(float64); ok {
153-
height = uint64(h)
154-
}
155-
}
151+
// if h, ok := req.Params["height"].(float64); ok {
152+
// height = uint64(h)
153+
// }
156154

157155
if height > 0 {
158156
node = config.GetNodebyHeight(height)

gateway/rpc_server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ func (server *Server) handleJSONRPCRequest(w http.ResponseWriter, r *http.Reques
356356
// node returned a 200 response
357357
fmt.Println("Node called:", url)
358358
if res.Body != nil {
359-
defer res.Body.Close()
359+
res.Body.Close()
360360
}
361361

362362
for key, values := range res.Header {
@@ -375,7 +375,7 @@ func (server *Server) handleJSONRPCRequest(w http.ResponseWriter, r *http.Reques
375375
// node returned a 500 response
376376
fmt.Println("Node called:", url)
377377
if res.Body != nil {
378-
defer res.Body.Close()
378+
res.Body.Close()
379379
}
380380

381381
body, err := io.ReadAll(res.Body)

0 commit comments

Comments
 (0)