github.com/jimmyx0x/go-ethereum@v1.10.28/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/ethereum/go-ethereum/internal/testlog" 32 "github.com/ethereum/go-ethereum/log" 33 "github.com/ethereum/go-ethereum/rpc" 34 "github.com/golang-jwt/jwt/v4" 35 "github.com/gorilla/websocket" 36 "github.com/stretchr/testify/assert" 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("GET", "/", 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("POST", 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 return resp 324 } 325 326 type testClaim map[string]interface{} 327 328 func (testClaim) Valid() error { 329 return nil 330 } 331 332 func TestJWT(t *testing.T) { 333 var secret = []byte("secret") 334 issueToken := func(secret []byte, method jwt.SigningMethod, input map[string]interface{}) string { 335 if method == nil { 336 method = jwt.SigningMethodHS256 337 } 338 ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret) 339 return ss 340 } 341 srv := createAndStartServer(t, &httpConfig{jwtSecret: []byte("secret")}, 342 true, &wsConfig{Origins: []string{"*"}, jwtSecret: []byte("secret")}, nil) 343 wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr()) 344 htUrl := fmt.Sprintf("http://%v", srv.listenAddr()) 345 346 expOk := []func() string{ 347 func() string { 348 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 349 }, 350 func() string { 351 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + 4})) 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{ 358 "iat": time.Now().Unix(), 359 "exp": time.Now().Unix() + 2, 360 })) 361 }, 362 func() string { 363 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{ 364 "iat": time.Now().Unix(), 365 "bar": "baz", 366 })) 367 }, 368 } 369 for i, tokenFn := range expOk { 370 token := tokenFn() 371 if err := wsRequest(t, wsUrl, "Authorization", token); err != nil { 372 t.Errorf("test %d-ws, token '%v': expected ok, got %v", i, token, err) 373 } 374 token = tokenFn() 375 if resp := rpcRequest(t, htUrl, testMethod, "Authorization", token); resp.StatusCode != 200 { 376 t.Errorf("test %d-http, token '%v': expected ok, got %v", i, token, resp.StatusCode) 377 } 378 } 379 380 expFail := []func() string{ 381 // future 382 func() string { 383 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() + int64(jwtExpiryTimeout.Seconds()) + 1})) 384 }, 385 // stale 386 func() string { 387 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix() - int64(jwtExpiryTimeout.Seconds()) - 1})) 388 }, 389 // wrong algo 390 func() string { 391 return fmt.Sprintf("Bearer %v", issueToken(secret, jwt.SigningMethodHS512, testClaim{"iat": time.Now().Unix() + 4})) 392 }, 393 // expired 394 func() string { 395 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix(), "exp": time.Now().Unix()})) 396 }, 397 // missing mandatory iat 398 func() string { 399 return fmt.Sprintf("Bearer %v", issueToken(secret, nil, testClaim{})) 400 }, 401 // wrong secret 402 func() string { 403 return fmt.Sprintf("Bearer %v", issueToken([]byte("wrong"), nil, testClaim{"iat": time.Now().Unix()})) 404 }, 405 func() string { 406 return fmt.Sprintf("Bearer %v", issueToken([]byte{}, nil, testClaim{"iat": time.Now().Unix()})) 407 }, 408 func() string { 409 return fmt.Sprintf("Bearer %v", issueToken(nil, nil, testClaim{"iat": time.Now().Unix()})) 410 }, 411 // Various malformed syntax 412 func() string { 413 return fmt.Sprintf("%v", issueToken(secret, nil, testClaim{"iat": time.Now().Unix()})) 414 }, 415 func() string { 416 return fmt.Sprintf("Bearer %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\t%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 } 434 for i, tokenFn := range expFail { 435 token := tokenFn() 436 if err := wsRequest(t, wsUrl, "Authorization", token); err == nil { 437 t.Errorf("tc %d-ws, token '%v': expected not to allow, got ok", i, token) 438 } 439 440 token = tokenFn() 441 resp := rpcRequest(t, htUrl, testMethod, "Authorization", token) 442 if resp.StatusCode != http.StatusUnauthorized { 443 t.Errorf("tc %d-http, token '%v': expected not to allow, got %v", i, token, resp.StatusCode) 444 } 445 } 446 srv.stop() 447 } 448 449 func TestGzipHandler(t *testing.T) { 450 type gzipTest struct { 451 name string 452 handler http.HandlerFunc 453 status int 454 isGzip bool 455 header map[string]string 456 } 457 tests := []gzipTest{ 458 { 459 name: "Write", 460 handler: func(w http.ResponseWriter, r *http.Request) { 461 w.Write([]byte("response")) 462 }, 463 isGzip: true, 464 status: 200, 465 }, 466 { 467 name: "WriteHeader", 468 handler: func(w http.ResponseWriter, r *http.Request) { 469 w.Header().Set("x-foo", "bar") 470 w.WriteHeader(205) 471 w.Write([]byte("response")) 472 }, 473 isGzip: true, 474 status: 205, 475 header: map[string]string{"x-foo": "bar"}, 476 }, 477 { 478 name: "WriteContentLength", 479 handler: func(w http.ResponseWriter, r *http.Request) { 480 w.Header().Set("content-length", "8") 481 w.Write([]byte("response")) 482 }, 483 isGzip: true, 484 status: 200, 485 }, 486 { 487 name: "Flush", 488 handler: func(w http.ResponseWriter, r *http.Request) { 489 w.Write([]byte("res")) 490 w.(http.Flusher).Flush() 491 w.Write([]byte("ponse")) 492 }, 493 isGzip: true, 494 status: 200, 495 }, 496 { 497 name: "disable", 498 handler: func(w http.ResponseWriter, r *http.Request) { 499 w.Header().Set("transfer-encoding", "identity") 500 w.Header().Set("x-foo", "bar") 501 w.Write([]byte("response")) 502 }, 503 isGzip: false, 504 status: 200, 505 header: map[string]string{"x-foo": "bar"}, 506 }, 507 { 508 name: "disable-WriteHeader", 509 handler: func(w http.ResponseWriter, r *http.Request) { 510 w.Header().Set("transfer-encoding", "identity") 511 w.Header().Set("x-foo", "bar") 512 w.WriteHeader(205) 513 w.Write([]byte("response")) 514 }, 515 isGzip: false, 516 status: 205, 517 header: map[string]string{"x-foo": "bar"}, 518 }, 519 } 520 521 for _, test := range tests { 522 test := test 523 t.Run(test.name, func(t *testing.T) { 524 srv := httptest.NewServer(newGzipHandler(test.handler)) 525 defer srv.Close() 526 527 resp, err := http.Get(srv.URL) 528 if err != nil { 529 t.Fatal(err) 530 } 531 defer resp.Body.Close() 532 533 content, err := io.ReadAll(resp.Body) 534 if err != nil { 535 t.Fatal(err) 536 } 537 wasGzip := resp.Uncompressed 538 539 if string(content) != "response" { 540 t.Fatalf("wrong response content %q", content) 541 } 542 if wasGzip != test.isGzip { 543 t.Fatalf("response gzipped == %t, want %t", wasGzip, test.isGzip) 544 } 545 if resp.StatusCode != test.status { 546 t.Fatalf("response status == %d, want %d", resp.StatusCode, test.status) 547 } 548 for name, expectedValue := range test.header { 549 if v := resp.Header.Get(name); v != expectedValue { 550 t.Fatalf("response header %s == %s, want %s", name, v, expectedValue) 551 } 552 } 553 }) 554 } 555 } 556 557 func TestHTTPWriteTimeout(t *testing.T) { 558 const ( 559 timeoutRes = `{"jsonrpc":"2.0","id":1,"error":{"code":-32002,"message":"request timed out"}}` 560 greetRes = `{"jsonrpc":"2.0","id":1,"result":"Hello"}` 561 ) 562 // Set-up server 563 timeouts := rpc.DefaultHTTPTimeouts 564 timeouts.WriteTimeout = time.Second 565 srv := createAndStartServer(t, &httpConfig{Modules: []string{"test"}}, false, &wsConfig{}, &timeouts) 566 url := fmt.Sprintf("http://%v", srv.listenAddr()) 567 568 // Send normal request 569 t.Run("message", func(t *testing.T) { 570 resp := rpcRequest(t, url, "test_sleep") 571 defer resp.Body.Close() 572 body, err := io.ReadAll(resp.Body) 573 if err != nil { 574 t.Fatal(err) 575 } 576 if string(body) != timeoutRes { 577 t.Errorf("wrong response. have %s, want %s", string(body), timeoutRes) 578 } 579 }) 580 581 // Batch request 582 t.Run("batch", func(t *testing.T) { 583 want := fmt.Sprintf("[%s,%s,%s]", greetRes, timeoutRes, timeoutRes) 584 resp := batchRpcRequest(t, url, []string{"test_greet", "test_sleep", "test_greet"}) 585 defer resp.Body.Close() 586 body, err := io.ReadAll(resp.Body) 587 if err != nil { 588 t.Fatal(err) 589 } 590 if string(body) != want { 591 t.Errorf("wrong response. have %s, want %s", string(body), want) 592 } 593 }) 594 } 595 596 func apis() []rpc.API { 597 return []rpc.API{ 598 { 599 Namespace: "test", 600 Service: &testService{}, 601 }, 602 } 603 } 604 605 type testService struct{} 606 607 func (s *testService) Greet() string { 608 return "Hello" 609 } 610 611 func (s *testService) Sleep() { 612 time.Sleep(1500 * time.Millisecond) 613 }