github.com/theQRL/go-zond@v0.1.1/node/rpcstack_test.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package node 18 19 import ( 20 "bytes" 21 "fmt" 22 "io" 23 "net/http" 24 "net/http/httptest" 25 "net/url" 26 "strconv" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/golang-jwt/jwt/v4" 32 "github.com/gorilla/websocket" 33 "github.com/stretchr/testify/assert" 34 "github.com/theQRL/go-zond/internal/testlog" 35 "github.com/theQRL/go-zond/log" 36 "github.com/theQRL/go-zond/rpc" 37 ) 38 39 const testMethod = "rpc_modules" 40 41 // TestCorsHandler makes sure CORS are properly handled on the http server. 42 func TestCorsHandler(t *testing.T) { 43 srv := createAndStartServer(t, &httpConfig{CorsAllowedOrigins: []string{"test", "test.com"}}, false, &wsConfig{}, nil) 44 defer srv.stop() 45 url := "http://" + srv.listenAddr() 46 47 resp := rpcRequest(t, url, testMethod, "origin", "test.com") 48 assert.Equal(t, "test.com", resp.Header.Get("Access-Control-Allow-Origin")) 49 50 resp2 := rpcRequest(t, url, testMethod, "origin", "bad") 51 assert.Equal(t, "", resp2.Header.Get("Access-Control-Allow-Origin")) 52 } 53 54 // TestVhosts makes sure vhosts are properly handled on the http server. 55 func TestVhosts(t *testing.T) { 56 srv := createAndStartServer(t, &httpConfig{Vhosts: []string{"test"}}, false, &wsConfig{}, nil) 57 defer srv.stop() 58 url := "http://" + srv.listenAddr() 59 60 resp := rpcRequest(t, url, testMethod, "host", "test") 61 assert.Equal(t, resp.StatusCode, http.StatusOK) 62 63 resp2 := rpcRequest(t, url, testMethod, "host", "bad") 64 assert.Equal(t, resp2.StatusCode, http.StatusForbidden) 65 } 66 67 type originTest struct { 68 spec string 69 expOk []string 70 expFail []string 71 } 72 73 // splitAndTrim splits input separated by a comma 74 // and trims excessive white space from the substrings. 75 // Copied over from flags.go 76 func splitAndTrim(input string) (ret []string) { 77 l := strings.Split(input, ",") 78 for _, r := range l { 79 r = strings.TrimSpace(r) 80 if len(r) > 0 { 81 ret = append(ret, r) 82 } 83 } 84 return ret 85 } 86 87 // TestWebsocketOrigins makes sure the websocket origins are properly handled on the websocket server. 88 func TestWebsocketOrigins(t *testing.T) { 89 tests := []originTest{ 90 { 91 spec: "*", // allow all 92 expOk: []string{"", "http://test", "https://test", "http://test:8540", "https://test:8540", 93 "http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"}, 94 }, 95 { 96 spec: "test", 97 expOk: []string{"http://test", "https://test", "http://test:8540", "https://test:8540"}, 98 expFail: []string{"http://test.com", "https://foo.test", "http://testa", "http://atestb:8540", "https://atestb:8540"}, 99 }, 100 // scheme tests 101 { 102 spec: "https://test", 103 expOk: []string{"https://test", "https://test:9999"}, 104 expFail: []string{ 105 "test", // no scheme, required by spec 106 "http://test", // wrong scheme 107 "http://test.foo", "https://a.test.x", // subdomain variations 108 "http://testx:8540", "https://xtest:8540"}, 109 }, 110 // ip tests 111 { 112 spec: "https://12.34.56.78", 113 expOk: []string{"https://12.34.56.78", "https://12.34.56.78:8540"}, 114 expFail: []string{ 115 "http://12.34.56.78", // wrong scheme 116 "http://12.34.56.78:443", // wrong scheme 117 "http://1.12.34.56.78", // wrong 'domain name' 118 "http://12.34.56.78.a", // wrong 'domain name' 119 "https://87.65.43.21", "http://87.65.43.21:8540", "https://87.65.43.21:8540"}, 120 }, 121 // port tests 122 { 123 spec: "test:8540", 124 expOk: []string{"http://test:8540", "https://test:8540"}, 125 expFail: []string{ 126 "http://test", "https://test", // spec says port required 127 "http://test:8541", "https://test:8541", // wrong port 128 "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, 129 }, 130 // scheme and port 131 { 132 spec: "https://test:8540", 133 expOk: []string{"https://test:8540"}, 134 expFail: []string{ 135 "https://test", // missing port 136 "http://test", // missing port, + wrong scheme 137 "http://test:8540", // wrong scheme 138 "http://test:8541", "https://test:8541", // wrong port 139 "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, 140 }, 141 // several allowed origins 142 { 143 spec: "localhost,http://127.0.0.1", 144 expOk: []string{"localhost", "http://localhost", "https://localhost:8443", 145 "http://127.0.0.1", "http://127.0.0.1:8080"}, 146 expFail: []string{ 147 "https://127.0.0.1", // wrong scheme 148 "http://bad", "https://bad", "http://bad:8540", "https://bad:8540"}, 149 }, 150 } 151 for _, tc := range tests { 152 srv := createAndStartServer(t, &httpConfig{}, true, &wsConfig{Origins: splitAndTrim(tc.spec)}, nil) 153 url := fmt.Sprintf("ws://%v", srv.listenAddr()) 154 for _, origin := range tc.expOk { 155 if err := wsRequest(t, url, "Origin", origin); err != nil { 156 t.Errorf("spec '%v', origin '%v': expected ok, got %v", tc.spec, origin, err) 157 } 158 } 159 for _, origin := range tc.expFail { 160 if err := wsRequest(t, url, "Origin", origin); err == nil { 161 t.Errorf("spec '%v', origin '%v': expected not to allow, got ok", tc.spec, origin) 162 } 163 } 164 srv.stop() 165 } 166 } 167 168 // TestIsWebsocket tests if an incoming websocket upgrade request is handled properly. 169 func TestIsWebsocket(t *testing.T) { 170 r, _ := http.NewRequest(http.MethodGet, "/", nil) 171 172 assert.False(t, isWebsocket(r)) 173 r.Header.Set("upgrade", "websocket") 174 assert.False(t, isWebsocket(r)) 175 r.Header.Set("connection", "upgrade") 176 assert.True(t, isWebsocket(r)) 177 r.Header.Set("connection", "upgrade,keep-alive") 178 assert.True(t, isWebsocket(r)) 179 r.Header.Set("connection", " UPGRADE,keep-alive") 180 assert.True(t, isWebsocket(r)) 181 } 182 183 func Test_checkPath(t *testing.T) { 184 tests := []struct { 185 req *http.Request 186 prefix string 187 expected bool 188 }{ 189 { 190 req: &http.Request{URL: &url.URL{Path: "/test"}}, 191 prefix: "/test", 192 expected: true, 193 }, 194 { 195 req: &http.Request{URL: &url.URL{Path: "/testing"}}, 196 prefix: "/test", 197 expected: true, 198 }, 199 { 200 req: &http.Request{URL: &url.URL{Path: "/"}}, 201 prefix: "/test", 202 expected: false, 203 }, 204 { 205 req: &http.Request{URL: &url.URL{Path: "/fail"}}, 206 prefix: "/test", 207 expected: false, 208 }, 209 { 210 req: &http.Request{URL: &url.URL{Path: "/"}}, 211 prefix: "", 212 expected: true, 213 }, 214 { 215 req: &http.Request{URL: &url.URL{Path: "/fail"}}, 216 prefix: "", 217 expected: false, 218 }, 219 { 220 req: &http.Request{URL: &url.URL{Path: "/"}}, 221 prefix: "/", 222 expected: true, 223 }, 224 { 225 req: &http.Request{URL: &url.URL{Path: "/testing"}}, 226 prefix: "/", 227 expected: true, 228 }, 229 } 230 231 for i, tt := range tests { 232 t.Run(strconv.Itoa(i), func(t *testing.T) { 233 assert.Equal(t, tt.expected, checkPath(tt.req, tt.prefix)) 234 }) 235 } 236 } 237 238 func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsConfig, timeouts *rpc.HTTPTimeouts) *httpServer { 239 t.Helper() 240 241 if timeouts == nil { 242 timeouts = &rpc.DefaultHTTPTimeouts 243 } 244 srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), *timeouts) 245 assert.NoError(t, srv.enableRPC(apis(), *conf)) 246 if ws { 247 assert.NoError(t, srv.enableWS(nil, *wsConf)) 248 } 249 assert.NoError(t, srv.setListenAddr("localhost", 0)) 250 assert.NoError(t, srv.start()) 251 return srv 252 } 253 254 // wsRequest attempts to open a WebSocket connection to the given URL. 255 func wsRequest(t *testing.T, url string, extraHeaders ...string) error { 256 t.Helper() 257 //t.Logf("checking WebSocket on %s (origin %q)", url, browserOrigin) 258 259 headers := make(http.Header) 260 // Apply extra headers. 261 if len(extraHeaders)%2 != 0 { 262 panic("odd extraHeaders length") 263 } 264 for i := 0; i < len(extraHeaders); i += 2 { 265 key, value := extraHeaders[i], extraHeaders[i+1] 266 headers.Set(key, value) 267 } 268 conn, _, err := websocket.DefaultDialer.Dial(url, headers) 269 if conn != nil { 270 conn.Close() 271 } 272 return err 273 } 274 275 // rpcRequest performs a JSON-RPC request to the given URL. 276 func rpcRequest(t *testing.T, url, method string, extraHeaders ...string) *http.Response { 277 t.Helper() 278 279 body := fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"%s","params":[]}`, method) 280 return baseRpcRequest(t, url, body, extraHeaders...) 281 } 282 283 func batchRpcRequest(t *testing.T, url string, methods []string, extraHeaders ...string) *http.Response { 284 reqs := make([]string, len(methods)) 285 for i, m := range methods { 286 reqs[i] = fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"%s","params":[]}`, m) 287 } 288 body := fmt.Sprintf(`[%s]`, strings.Join(reqs, ",")) 289 return baseRpcRequest(t, url, body, extraHeaders...) 290 } 291 292 func baseRpcRequest(t *testing.T, url, bodyStr string, extraHeaders ...string) *http.Response { 293 t.Helper() 294 295 // Create the request. 296 body := bytes.NewReader([]byte(bodyStr)) 297 req, err := http.NewRequest(http.MethodPost, url, body) 298 if err != nil { 299 t.Fatal("could not create http request:", err) 300 } 301 req.Header.Set("content-type", "application/json") 302 req.Header.Set("accept-encoding", "identity") 303 304 // Apply extra headers. 305 if len(extraHeaders)%2 != 0 { 306 panic("odd extraHeaders length") 307 } 308 for i := 0; i < len(extraHeaders); i += 2 { 309 key, value := extraHeaders[i], extraHeaders[i+1] 310 if strings.EqualFold(key, "host") { 311 req.Host = value 312 } else { 313 req.Header.Set(key, value) 314 } 315 } 316 317 // Perform the request. 318 t.Logf("checking RPC/HTTP on %s %v", url, extraHeaders) 319 resp, err := http.DefaultClient.Do(req) 320 if err != nil { 321 t.Fatal(err) 322 } 323 t.Cleanup(func() { resp.Body.Close() }) 324 return resp 325 } 326 327 type testClaim map[string]interface{} 328 329 func (testClaim) Valid() error { 330 return nil 331 } 332 333 func TestJWT(t *testing.T) { 334 var secret = []byte("secret") 335 issueToken := func(secret []byte, method jwt.SigningMethod, input map[string]interface{}) string { 336 if method == nil { 337 method = jwt.SigningMethodHS256 338 } 339 ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret) 340 return ss 341 } 342 cfg := rpcEndpointConfig{jwtSecret: []byte("secret")} 343 httpcfg := &httpConfig{rpcEndpointConfig: cfg} 344 wscfg := &wsConfig{Origins: []string{"*"}, rpcEndpointConfig: cfg} 345 srv := createAndStartServer(t, httpcfg, true, wscfg, nil) 346 wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr()) 347 htUrl := fmt.Sprintf("http://%v", srv.listenAddr()) 348 349 expOk := []func() string{ 350 func() string { 351 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 352 }, 353 func() string { 354 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + 4})) 355 }, 356 func() string { 357 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - 4})) 358 }, 359 func() string { 360 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{ 361 "iat": time.Now().Unix(), 362 "exp": time.Now().Unix() + 2, 363 })) 364 }, 365 func() string { 366 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{ 367 "iat": time.Now().Unix(), 368 "bar": "baz", 369 })) 370 }, 371 } 372 for i, tokenFn := range expOk { 373 token := tokenFn() 374 if err := wsRequest(t, wsUrl, "Authorization", token); err != nil { 375 t.Errorf("test %d-ws, token '%v': expected ok, got %v", i, token, err) 376 } 377 token = tokenFn() 378 if resp := rpcRequest(t, htUrl, testMethod, "Authorization", token); resp.StatusCode != 200 { 379 t.Errorf("test %d-http, token '%v': expected ok, got %v", i, token, resp.StatusCode) 380 } 381 } 382 383 expFail := []func() string{ 384 // future 385 func() string { 386 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + int64(jwtExpiryTimeout.Seconds()) + 1})) 387 }, 388 // stale 389 func() string { 390 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - int64(jwtExpiryTimeout.Seconds()) - 1})) 391 }, 392 // wrong algo 393 func() string { 394 return fmt.Sprintf("Bearer %v", issueToken(secret, jwt.SigningMethodHS512, testClaim{"iat": time.Now().Unix() + 4})) 395 }, 396 // expired 397 func() string { 398 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix(), "exp": time.Now().Unix()})) 399 }, 400 // missing mandatory iat 401 func() string { 402 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{})) 403 }, 404 // wrong secret 405 func() string { 406 return fmt.Sprintf("Bearer %v", issueToken([]byte("wrong"), nil, testClaim{"iat": time.Now().Unix()})) 407 }, 408 func() string { 409 return fmt.Sprintf("Bearer %v", issueToken([]byte{}, nil, testClaim{"iat": time.Now().Unix()})) 410 }, 411 func() string { 412 return fmt.Sprintf("Bearer %v", issueToken(nil, nil, testClaim{"iat": time.Now().Unix()})) 413 }, 414 // Various malformed syntax 415 func() string { 416 return fmt.Sprintf("%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 417 }, 418 func() string { 419 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 420 }, 421 func() string { 422 return fmt.Sprintf("bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 423 }, 424 func() string { 425 return fmt.Sprintf("Bearer: %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 426 }, 427 func() string { 428 return fmt.Sprintf("Bearer:%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 429 }, 430 func() string { 431 return fmt.Sprintf("Bearer\t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 432 }, 433 func() string { 434 return fmt.Sprintf("Bearer \t%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 435 }, 436 } 437 for i, tokenFn := range expFail { 438 token := tokenFn() 439 if err := wsRequest(t, wsUrl, "Authorization", token); err == nil { 440 t.Errorf("tc %d-ws, token '%v': expected not to allow, got ok", i, token) 441 } 442 443 token = tokenFn() 444 resp := rpcRequest(t, htUrl, testMethod, "Authorization", token) 445 if resp.StatusCode != http.StatusUnauthorized { 446 t.Errorf("tc %d-http, token '%v': expected not to allow, got %v", i, token, resp.StatusCode) 447 } 448 } 449 srv.stop() 450 } 451 452 func TestGzipHandler(t *testing.T) { 453 type gzipTest struct { 454 name string 455 handler http.HandlerFunc 456 status int 457 isGzip bool 458 header map[string]string 459 } 460 tests := []gzipTest{ 461 { 462 name: "Write", 463 handler: func(w http.ResponseWriter, r *http.Request) { 464 w.Write([]byte("response")) 465 }, 466 isGzip: true, 467 status: 200, 468 }, 469 { 470 name: "WriteHeader", 471 handler: func(w http.ResponseWriter, r *http.Request) { 472 w.Header().Set("x-foo", "bar") 473 w.WriteHeader(205) 474 w.Write([]byte("response")) 475 }, 476 isGzip: true, 477 status: 205, 478 header: map[string]string{"x-foo": "bar"}, 479 }, 480 { 481 name: "WriteContentLength", 482 handler: func(w http.ResponseWriter, r *http.Request) { 483 w.Header().Set("content-length", "8") 484 w.Write([]byte("response")) 485 }, 486 isGzip: true, 487 status: 200, 488 }, 489 { 490 name: "Flush", 491 handler: func(w http.ResponseWriter, r *http.Request) { 492 w.Write([]byte("res")) 493 w.(http.Flusher).Flush() 494 w.Write([]byte("ponse")) 495 }, 496 isGzip: true, 497 status: 200, 498 }, 499 { 500 name: "disable", 501 handler: func(w http.ResponseWriter, r *http.Request) { 502 w.Header().Set("transfer-encoding", "identity") 503 w.Header().Set("x-foo", "bar") 504 w.Write([]byte("response")) 505 }, 506 isGzip: false, 507 status: 200, 508 header: map[string]string{"x-foo": "bar"}, 509 }, 510 { 511 name: "disable-WriteHeader", 512 handler: func(w http.ResponseWriter, r *http.Request) { 513 w.Header().Set("transfer-encoding", "identity") 514 w.Header().Set("x-foo", "bar") 515 w.WriteHeader(205) 516 w.Write([]byte("response")) 517 }, 518 isGzip: false, 519 status: 205, 520 header: map[string]string{"x-foo": "bar"}, 521 }, 522 } 523 524 for _, test := range tests { 525 test := test 526 t.Run(test.name, func(t *testing.T) { 527 srv := httptest.NewServer(newGzipHandler(test.handler)) 528 defer srv.Close() 529 530 resp, err := http.Get(srv.URL) 531 if err != nil { 532 t.Fatal(err) 533 } 534 defer resp.Body.Close() 535 536 content, err := io.ReadAll(resp.Body) 537 if err != nil { 538 t.Fatal(err) 539 } 540 wasGzip := resp.Uncompressed 541 542 if string(content) != "response" { 543 t.Fatalf("wrong response content %q", content) 544 } 545 if wasGzip != test.isGzip { 546 t.Fatalf("response gzipped == %t, want %t", wasGzip, test.isGzip) 547 } 548 if resp.StatusCode != test.status { 549 t.Fatalf("response status == %d, want %d", resp.StatusCode, test.status) 550 } 551 for name, expectedValue := range test.header { 552 if v := resp.Header.Get(name); v != expectedValue { 553 t.Fatalf("response header %s == %s, want %s", name, v, expectedValue) 554 } 555 } 556 }) 557 } 558 } 559 560 func TestHTTPWriteTimeout(t *testing.T) { 561 const ( 562 timeoutRes = `{"jsonrpc":"2.0","id":1,"error":{"code":-32002,"message":"request timed out"}}` 563 greetRes = `{"jsonrpc":"2.0","id":1,"result":"Hello"}` 564 ) 565 // Set-up server 566 timeouts := rpc.DefaultHTTPTimeouts 567 timeouts.WriteTimeout = time.Second 568 srv := createAndStartServer(t, &httpConfig{Modules: []string{"test"}}, false, &wsConfig{}, &timeouts) 569 url := fmt.Sprintf("http://%v", srv.listenAddr()) 570 571 // Send normal request 572 t.Run("message", func(t *testing.T) { 573 resp := rpcRequest(t, url, "test_sleep") 574 defer resp.Body.Close() 575 body, err := io.ReadAll(resp.Body) 576 if err != nil { 577 t.Fatal(err) 578 } 579 if string(body) != timeoutRes { 580 t.Errorf("wrong response. have %s, want %s", string(body), timeoutRes) 581 } 582 }) 583 584 // Batch request 585 t.Run("batch", func(t *testing.T) { 586 want := fmt.Sprintf("[%s,%s,%s]", greetRes, timeoutRes, timeoutRes) 587 resp := batchRpcRequest(t, url, []string{"test_greet", "test_sleep", "test_greet"}) 588 defer resp.Body.Close() 589 body, err := io.ReadAll(resp.Body) 590 if err != nil { 591 t.Fatal(err) 592 } 593 if string(body) != want { 594 t.Errorf("wrong response. have %s, want %s", string(body), want) 595 } 596 }) 597 } 598 599 func apis() []rpc.API { 600 return []rpc.API{ 601 { 602 Namespace: "test", 603 Service: &testService{}, 604 }, 605 } 606 } 607 608 type testService struct{} 609 610 func (s *testService) Greet() string { 611 return "Hello" 612 } 613 614 func (s *testService) Sleep() { 615 time.Sleep(1500 * time.Millisecond) 616 }