github.com/Oyster-zx/tendermint@v0.34.24-fork/rpc/jsonrpc/server/http_json_handler_test.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "net/http" 8 "net/http/httptest" 9 "strings" 10 "testing" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "github.com/tendermint/tendermint/libs/log" 16 types "github.com/tendermint/tendermint/rpc/jsonrpc/types" 17 ) 18 19 func testMux() *http.ServeMux { 20 funcMap := map[string]*RPCFunc{ 21 "c": NewRPCFunc(func(ctx *types.Context, s string, i int) (string, error) { return "foo", nil }, "s,i"), 22 "block": NewRPCFunc(func(ctx *types.Context, h int) (string, error) { return "block", nil }, "height", Cacheable("height")), 23 } 24 mux := http.NewServeMux() 25 buf := new(bytes.Buffer) 26 logger := log.NewTMLogger(buf) 27 RegisterRPCFuncs(mux, funcMap, logger) 28 29 return mux 30 } 31 32 func statusOK(code int) bool { return code >= 200 && code <= 299 } 33 34 // Ensure that nefarious/unintended inputs to `params` 35 // do not crash our RPC handlers. 36 // See Issue https://github.com/tendermint/tendermint/issues/708. 37 func TestRPCParams(t *testing.T) { 38 mux := testMux() 39 tests := []struct { 40 payload string 41 wantErr string 42 expectedID interface{} 43 }{ 44 // bad 45 {`{"jsonrpc": "2.0", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")}, 46 {`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")}, 47 // id not captured in JSON parsing failures 48 {`{"method": "c", "id": "0", "params": a}`, "invalid character", nil}, 49 {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")}, 50 {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")}, 51 {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")}, 52 53 // no ID - notification 54 // {`{"jsonrpc": "2.0", "method": "c", "params": ["a", "10"]}`, false, nil}, 55 56 // good 57 {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")}, 58 {`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")}, 59 {`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")}, 60 } 61 62 for i, tt := range tests { 63 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 64 rec := httptest.NewRecorder() 65 mux.ServeHTTP(rec, req) 66 res := rec.Result() 67 defer res.Body.Close() 68 // Always expecting back a JSONRPCResponse 69 assert.NotZero(t, res.StatusCode, "#%d: should always return code", i) 70 blob, err := io.ReadAll(res.Body) 71 if err != nil { 72 t.Errorf("#%d: err reading body: %v", i, err) 73 continue 74 } 75 76 recv := new(types.RPCResponse) 77 assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) 78 assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 79 assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i) 80 if tt.wantErr == "" { 81 assert.Nil(t, recv.Error, "#%d: not expecting an error", i) 82 } else { 83 assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i) 84 // The wanted error is either in the message or the data 85 assert.Contains(t, recv.Error.Message+recv.Error.Data, tt.wantErr, "#%d: expected substring", i) 86 } 87 } 88 } 89 90 func TestJSONRPCID(t *testing.T) { 91 mux := testMux() 92 tests := []struct { 93 payload string 94 wantErr bool 95 expectedID interface{} 96 }{ 97 // good id 98 {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": ["a", "10"]}`, false, types.JSONRPCStringID("0")}, 99 {`{"jsonrpc": "2.0", "method": "c", "id": "abc", "params": ["a", "10"]}`, false, types.JSONRPCStringID("abc")}, 100 {`{"jsonrpc": "2.0", "method": "c", "id": 0, "params": ["a", "10"]}`, false, types.JSONRPCIntID(0)}, 101 {`{"jsonrpc": "2.0", "method": "c", "id": 1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)}, 102 {`{"jsonrpc": "2.0", "method": "c", "id": 1.3, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)}, 103 {`{"jsonrpc": "2.0", "method": "c", "id": -1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(-1)}, 104 105 // bad id 106 {`{"jsonrpc": "2.0", "method": "c", "id": {}, "params": ["a", "10"]}`, true, nil}, 107 {`{"jsonrpc": "2.0", "method": "c", "id": [], "params": ["a", "10"]}`, true, nil}, 108 } 109 110 for i, tt := range tests { 111 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 112 rec := httptest.NewRecorder() 113 mux.ServeHTTP(rec, req) 114 res := rec.Result() 115 // Always expecting back a JSONRPCResponse 116 assert.NotZero(t, res.StatusCode, "#%d: should always return code", i) 117 blob, err := io.ReadAll(res.Body) 118 if err != nil { 119 t.Errorf("#%d: err reading body: %v", i, err) 120 continue 121 } 122 res.Body.Close() 123 124 recv := new(types.RPCResponse) 125 err = json.Unmarshal(blob, recv) 126 assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) 127 if !tt.wantErr { 128 assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 129 assert.Equal(t, tt.expectedID, recv.ID, "#%d: expected ID not matched in RPCResponse", i) 130 assert.Nil(t, recv.Error, "#%d: not expecting an error", i) 131 } else { 132 assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i) 133 } 134 } 135 } 136 137 func TestRPCNotification(t *testing.T) { 138 mux := testMux() 139 body := strings.NewReader(`{"jsonrpc": "2.0"}`) 140 req, _ := http.NewRequest("POST", "http://localhost/", body) 141 rec := httptest.NewRecorder() 142 mux.ServeHTTP(rec, req) 143 res := rec.Result() 144 145 // Always expecting back a JSONRPCResponse 146 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 147 blob, err := io.ReadAll(res.Body) 148 res.Body.Close() 149 require.Nil(t, err, "reading from the body should not give back an error") 150 require.Equal(t, len(blob), 0, "a notification SHOULD NOT be responded to by the server") 151 } 152 153 func TestRPCNotificationInBatch(t *testing.T) { 154 mux := testMux() 155 tests := []struct { 156 payload string 157 expectCount int 158 }{ 159 { 160 `[ 161 {"jsonrpc": "2.0"}, 162 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]} 163 ]`, 164 1, 165 }, 166 { 167 `[ 168 {"jsonrpc": "2.0"}, 169 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]}, 170 {"jsonrpc": "2.0"}, 171 {"jsonrpc": "2.0","method":"c","id":"abc","params":["a","10"]} 172 ]`, 173 2, 174 }, 175 } 176 for i, tt := range tests { 177 req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) 178 rec := httptest.NewRecorder() 179 mux.ServeHTTP(rec, req) 180 res := rec.Result() 181 // Always expecting back a JSONRPCResponse 182 assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) 183 blob, err := io.ReadAll(res.Body) 184 if err != nil { 185 t.Errorf("#%d: err reading body: %v", i, err) 186 continue 187 } 188 res.Body.Close() 189 190 var responses []types.RPCResponse 191 // try to unmarshal an array first 192 err = json.Unmarshal(blob, &responses) 193 if err != nil { 194 // if we were actually expecting an array, but got an error 195 if tt.expectCount > 1 { 196 t.Errorf("#%d: expected an array, couldn't unmarshal it\nblob: %s", i, blob) 197 continue 198 } else { 199 // we were expecting an error here, so let's unmarshal a single response 200 var response types.RPCResponse 201 err = json.Unmarshal(blob, &response) 202 if err != nil { 203 t.Errorf("#%d: expected successful parsing of an RPCResponse\nblob: %s", i, blob) 204 continue 205 } 206 // have a single-element result 207 responses = []types.RPCResponse{response} 208 } 209 } 210 if tt.expectCount != len(responses) { 211 t.Errorf("#%d: expected %d response(s), but got %d\nblob: %s", i, tt.expectCount, len(responses), blob) 212 continue 213 } 214 for _, response := range responses { 215 assert.NotEqual(t, response, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) 216 } 217 } 218 } 219 220 func TestUnknownRPCPath(t *testing.T) { 221 mux := testMux() 222 req, _ := http.NewRequest("GET", "http://localhost/unknownrpcpath", nil) 223 rec := httptest.NewRecorder() 224 mux.ServeHTTP(rec, req) 225 res := rec.Result() 226 227 // Always expecting back a 404 error 228 require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404") 229 res.Body.Close() 230 } 231 232 func TestRPCResponseCache(t *testing.T) { 233 mux := testMux() 234 body := strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": ["1"]}`) 235 req, _ := http.NewRequest("Get", "http://localhost/", body) 236 rec := httptest.NewRecorder() 237 mux.ServeHTTP(rec, req) 238 res := rec.Result() 239 240 // Always expecting back a JSONRPCResponse 241 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 242 require.Equal(t, "public, max-age=86400", res.Header.Get("Cache-control")) 243 244 _, err := io.ReadAll(res.Body) 245 res.Body.Close() 246 require.Nil(t, err, "reading from the body should not give back an error") 247 248 // send a request with default height. 249 body = strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": ["0"]}`) 250 req, _ = http.NewRequest("Get", "http://localhost/", body) 251 rec = httptest.NewRecorder() 252 mux.ServeHTTP(rec, req) 253 res = rec.Result() 254 255 // Always expecting back a JSONRPCResponse 256 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 257 require.Equal(t, "", res.Header.Get("Cache-control")) 258 259 _, err = io.ReadAll(res.Body) 260 261 res.Body.Close() 262 require.Nil(t, err, "reading from the body should not give back an error") 263 264 // send a request with default height, but as empty set of parameters. 265 body = strings.NewReader(`{"jsonrpc": "2.0","method":"block","id": 0, "params": []}`) 266 req, _ = http.NewRequest("Get", "http://localhost/", body) 267 rec = httptest.NewRecorder() 268 mux.ServeHTTP(rec, req) 269 res = rec.Result() 270 271 // Always expecting back a JSONRPCResponse 272 require.True(t, statusOK(res.StatusCode), "should always return 2XX") 273 require.Equal(t, "", res.Header.Get("Cache-control")) 274 275 _, err = io.ReadAll(res.Body) 276 277 res.Body.Close() 278 require.Nil(t, err, "reading from the body should not give back an error") 279 }