github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/services/rpcsrv/subscription_test.go (about) 1 package rpcsrv 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/gorilla/websocket" 12 "github.com/nspcc-dev/neo-go/internal/testchain" 13 "github.com/nspcc-dev/neo-go/pkg/config" 14 "github.com/nspcc-dev/neo-go/pkg/core" 15 "github.com/nspcc-dev/neo-go/pkg/encoding/address" 16 "github.com/nspcc-dev/neo-go/pkg/neorpc" 17 "github.com/nspcc-dev/neo-go/pkg/util" 18 "github.com/stretchr/testify/require" 19 ) 20 21 const testOverflow = false 22 23 func wsReader(t *testing.T, ws *websocket.Conn, msgCh chan<- []byte, readerStopCh chan struct{}, readerToExitCh chan struct{}) { 24 readLoop: 25 for { 26 select { 27 case <-readerStopCh: 28 break readLoop 29 default: 30 err := ws.SetReadDeadline(time.Now().Add(5 * time.Second)) 31 select { 32 case <-readerStopCh: 33 break readLoop 34 default: 35 require.NoError(t, err) 36 } 37 38 _, body, err := ws.ReadMessage() 39 select { 40 case <-readerStopCh: 41 break readLoop 42 default: 43 require.NoError(t, err) 44 } 45 46 select { 47 case msgCh <- body: 48 case <-time.After(10 * time.Second): 49 t.Log("exiting wsReader loop: unable to send response to receiver") 50 break readLoop 51 } 52 } 53 } 54 close(readerToExitCh) 55 } 56 57 func callWSGetRaw(t *testing.T, ws *websocket.Conn, msg string, respCh <-chan []byte) *neorpc.Response { 58 var resp = new(neorpc.Response) 59 60 require.NoError(t, ws.SetWriteDeadline(time.Now().Add(5*time.Second))) 61 require.NoError(t, ws.WriteMessage(websocket.TextMessage, []byte(msg))) 62 63 body := <-respCh 64 require.NoError(t, json.Unmarshal(body, resp)) 65 return resp 66 } 67 68 func getNotification(t *testing.T, respCh <-chan []byte) *neorpc.Notification { 69 var resp = new(neorpc.Notification) 70 body := <-respCh 71 require.NoError(t, json.Unmarshal(body, resp)) 72 return resp 73 } 74 75 func initCleanServerAndWSClient(t *testing.T, startNetworkServer ...bool) (*core.Blockchain, *Server, *websocket.Conn, chan []byte) { 76 chain, rpcSrv, httpSrv := initClearServerWithInMemoryChain(t) 77 78 dialer := websocket.Dialer{HandshakeTimeout: 5 * time.Second} 79 url := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/ws" 80 ws, r, err := dialer.Dial(url, nil) 81 require.NoError(t, err) 82 defer r.Body.Close() 83 84 // Use buffered channel to read server's messages and then read expected 85 // responses from it. 86 respMsgs := make(chan []byte, 16) 87 readerStopCh := make(chan struct{}) 88 readerToExitCh := make(chan struct{}) 89 go wsReader(t, ws, respMsgs, readerStopCh, readerToExitCh) 90 if len(startNetworkServer) != 0 && startNetworkServer[0] { 91 rpcSrv.coreServer.Start() 92 } 93 t.Cleanup(func() { 94 drainLoop: 95 for { 96 select { 97 case <-respMsgs: 98 default: 99 break drainLoop 100 } 101 } 102 close(readerStopCh) 103 ws.Close() 104 <-readerToExitCh 105 if len(startNetworkServer) != 0 && startNetworkServer[0] { 106 rpcSrv.coreServer.Shutdown() 107 } 108 }) 109 return chain, rpcSrv, ws, respMsgs 110 } 111 112 func callSubscribe(t *testing.T, ws *websocket.Conn, msgs <-chan []byte, params string) string { 113 var s string 114 resp := callWSGetRaw(t, ws, fmt.Sprintf(`{"jsonrpc": "2.0","method": "subscribe","params": %s,"id": 1}`, params), msgs) 115 require.Nil(t, resp.Error) 116 require.NotNil(t, resp.Result) 117 require.NoError(t, json.Unmarshal(resp.Result, &s)) 118 return s 119 } 120 121 func callUnsubscribe(t *testing.T, ws *websocket.Conn, msgs <-chan []byte, id string) { 122 var b bool 123 resp := callWSGetRaw(t, ws, fmt.Sprintf(`{"jsonrpc": "2.0","method": "unsubscribe","params": ["%s"],"id": 1}`, id), msgs) 124 require.Nil(t, resp.Error) 125 require.NotNil(t, resp.Result) 126 require.NoError(t, json.Unmarshal(resp.Result, &b)) 127 require.Equal(t, true, b) 128 } 129 130 func TestSubscriptions(t *testing.T) { 131 var subIDs = make([]string, 0) 132 var subFeeds = []string{"block_added", "transaction_added", "notification_from_execution", "transaction_executed", "notary_request_event", "header_of_added_block"} 133 134 chain, rpcSrv, c, respMsgs := initCleanServerAndWSClient(t, true) 135 136 for _, feed := range subFeeds { 137 s := callSubscribe(t, c, respMsgs, fmt.Sprintf(`["%s"]`, feed)) 138 subIDs = append(subIDs, s) 139 } 140 141 for _, b := range getTestBlocks(t) { 142 require.NoError(t, chain.AddBlock(b)) 143 resp := getNotification(t, respMsgs) 144 require.Equal(t, neorpc.ExecutionEventID, resp.Event) 145 for { 146 resp = getNotification(t, respMsgs) 147 if resp.Event != neorpc.NotificationEventID { 148 break 149 } 150 } 151 for i := 0; i < len(b.Transactions); i++ { 152 if i > 0 { 153 resp = getNotification(t, respMsgs) 154 } 155 require.Equal(t, neorpc.ExecutionEventID, resp.Event) 156 for { 157 resp := getNotification(t, respMsgs) 158 if resp.Event == neorpc.NotificationEventID { 159 continue 160 } 161 require.Equal(t, neorpc.TransactionEventID, resp.Event) 162 break 163 } 164 } 165 resp = getNotification(t, respMsgs) 166 require.Equal(t, neorpc.ExecutionEventID, resp.Event) 167 for { 168 resp = getNotification(t, respMsgs) 169 if resp.Event != neorpc.NotificationEventID { 170 break 171 } 172 } 173 require.Equal(t, neorpc.HeaderOfAddedBlockEventID, resp.Event) 174 resp = getNotification(t, respMsgs) 175 require.Equal(t, neorpc.BlockEventID, resp.Event) 176 } 177 178 // We should manually add NotaryRequest to test notification. 179 sender := testchain.PrivateKeyByID(0) 180 err := rpcSrv.coreServer.RelayP2PNotaryRequest(createValidNotaryRequest(chain, sender, 1, 2_0000_0000, nil)) 181 require.NoError(t, err) 182 for { 183 resp := getNotification(t, respMsgs) 184 if resp.Event == neorpc.NotaryRequestEventID { 185 break 186 } 187 } 188 189 for _, id := range subIDs { 190 callUnsubscribe(t, c, respMsgs, id) 191 } 192 } 193 194 func TestFilteredSubscriptions(t *testing.T) { 195 priv0 := testchain.PrivateKeyByID(0) 196 var goodSender = priv0.GetScriptHash() 197 198 var cases = map[string]struct { 199 params string 200 check func(*testing.T, *neorpc.Notification) 201 }{ 202 "tx matching sender": { 203 params: `["transaction_added", {"sender":"` + goodSender.StringLE() + `"}]`, 204 check: func(t *testing.T, resp *neorpc.Notification) { 205 rmap := resp.Payload[0].(map[string]any) 206 require.Equal(t, neorpc.TransactionEventID, resp.Event) 207 sender := rmap["sender"].(string) 208 require.Equal(t, address.Uint160ToString(goodSender), sender) 209 }, 210 }, 211 "tx matching signer": { 212 params: `["transaction_added", {"signer":"` + goodSender.StringLE() + `"}]`, 213 check: func(t *testing.T, resp *neorpc.Notification) { 214 rmap := resp.Payload[0].(map[string]any) 215 require.Equal(t, neorpc.TransactionEventID, resp.Event) 216 signers := rmap["signers"].([]any) 217 signer0 := signers[0].(map[string]any) 218 signer0acc := signer0["account"].(string) 219 require.Equal(t, "0x"+goodSender.StringLE(), signer0acc) 220 }, 221 }, 222 "tx matching sender and signer": { 223 params: `["transaction_added", {"sender":"` + goodSender.StringLE() + `", "signer":"` + goodSender.StringLE() + `"}]`, 224 check: func(t *testing.T, resp *neorpc.Notification) { 225 rmap := resp.Payload[0].(map[string]any) 226 require.Equal(t, neorpc.TransactionEventID, resp.Event) 227 sender := rmap["sender"].(string) 228 require.Equal(t, address.Uint160ToString(goodSender), sender) 229 signers := rmap["signers"].([]any) 230 signer0 := signers[0].(map[string]any) 231 signer0acc := signer0["account"].(string) 232 require.Equal(t, "0x"+goodSender.StringLE(), signer0acc) 233 }, 234 }, 235 "notification matching contract hash": { 236 params: `["notification_from_execution", {"contract":"` + testContractHash + `"}]`, 237 check: func(t *testing.T, resp *neorpc.Notification) { 238 rmap := resp.Payload[0].(map[string]any) 239 require.Equal(t, neorpc.NotificationEventID, resp.Event) 240 c := rmap["contract"].(string) 241 require.Equal(t, "0x"+testContractHash, c) 242 }, 243 }, 244 "notification matching name": { 245 params: `["notification_from_execution", {"name":"my_pretty_notification"}]`, 246 check: func(t *testing.T, resp *neorpc.Notification) { 247 rmap := resp.Payload[0].(map[string]any) 248 require.Equal(t, neorpc.NotificationEventID, resp.Event) 249 n := rmap["name"].(string) 250 require.Equal(t, "my_pretty_notification", n) 251 }, 252 }, 253 "notification matching contract hash and name": { 254 params: `["notification_from_execution", {"contract":"` + testContractHash + `", "name":"my_pretty_notification"}]`, 255 check: func(t *testing.T, resp *neorpc.Notification) { 256 rmap := resp.Payload[0].(map[string]any) 257 require.Equal(t, neorpc.NotificationEventID, resp.Event) 258 c := rmap["contract"].(string) 259 require.Equal(t, "0x"+testContractHash, c) 260 n := rmap["name"].(string) 261 require.Equal(t, "my_pretty_notification", n) 262 }, 263 }, 264 "execution matching state": { 265 params: `["transaction_executed", {"state":"HALT"}]`, 266 check: func(t *testing.T, resp *neorpc.Notification) { 267 rmap := resp.Payload[0].(map[string]any) 268 require.Equal(t, neorpc.ExecutionEventID, resp.Event) 269 st := rmap["vmstate"].(string) 270 require.Equal(t, "HALT", st) 271 }, 272 }, 273 "execution matching container": { 274 params: `["transaction_executed", {"container":"` + deploymentTxHash + `"}]`, 275 check: func(t *testing.T, resp *neorpc.Notification) { 276 rmap := resp.Payload[0].(map[string]any) 277 require.Equal(t, neorpc.ExecutionEventID, resp.Event) 278 tx := rmap["container"].(string) 279 require.Equal(t, "0x"+deploymentTxHash, tx) 280 }, 281 }, 282 "execution matching state and container": { 283 params: `["transaction_executed", {"state":"HALT", "container":"` + deploymentTxHash + `"}]`, 284 check: func(t *testing.T, resp *neorpc.Notification) { 285 rmap := resp.Payload[0].(map[string]any) 286 require.Equal(t, neorpc.ExecutionEventID, resp.Event) 287 tx := rmap["container"].(string) 288 require.Equal(t, "0x"+deploymentTxHash, tx) 289 st := rmap["vmstate"].(string) 290 require.Equal(t, "HALT", st) 291 }, 292 }, 293 "tx non-matching": { 294 params: `["transaction_added", {"sender":"00112233445566778899aabbccddeeff00112233"}]`, 295 check: func(t *testing.T, _ *neorpc.Notification) { 296 t.Fatal("unexpected match for EnrollmentTransaction") 297 }, 298 }, 299 "notification non-matching": { 300 params: `["notification_from_execution", {"contract":"00112233445566778899aabbccddeeff00112233"}]`, 301 check: func(t *testing.T, _ *neorpc.Notification) { 302 t.Fatal("unexpected match for contract 00112233445566778899aabbccddeeff00112233") 303 }, 304 }, 305 "execution non-matching": { 306 // We have single FAULTed transaction in chain, this, use the wrong hash for this test instead of FAULT state. 307 params: `["transaction_executed", {"container":"0x` + util.Uint256{}.StringLE() + `"}]`, 308 check: func(t *testing.T, n *neorpc.Notification) { 309 t.Fatal("unexpected match for faulted execution") 310 }, 311 }, 312 "header of added block": { 313 params: `["header_of_added_block", {"primary": 0, "since": 5}]`, 314 check: func(t *testing.T, resp *neorpc.Notification) { 315 rmap := resp.Payload[0].(map[string]any) 316 require.Equal(t, neorpc.HeaderOfAddedBlockEventID, resp.Event) 317 primary := rmap["primary"].(float64) 318 require.Equal(t, 0, int(primary)) 319 index := rmap["index"].(float64) 320 require.Less(t, 4, int(index)) 321 }, 322 }, 323 } 324 325 for name, this := range cases { 326 t.Run(name, func(t *testing.T) { 327 chain, _, c, respMsgs := initCleanServerAndWSClient(t) 328 329 // It's used as an end-of-event-stream, so it's always present. 330 blockSubID := callSubscribe(t, c, respMsgs, `["block_added"]`) 331 subID := callSubscribe(t, c, respMsgs, this.params) 332 333 var lastBlock uint32 334 for _, b := range getTestBlocks(t) { 335 require.NoError(t, chain.AddBlock(b)) 336 lastBlock = b.Index 337 } 338 339 for { 340 resp := getNotification(t, respMsgs) 341 rmap := resp.Payload[0].(map[string]any) 342 if resp.Event == neorpc.BlockEventID { 343 index := rmap["index"].(float64) 344 if uint32(index) == lastBlock { 345 break 346 } 347 continue 348 } 349 this.check(t, resp) 350 } 351 352 callUnsubscribe(t, c, respMsgs, subID) 353 callUnsubscribe(t, c, respMsgs, blockSubID) 354 }) 355 } 356 } 357 358 func TestFilteredNotaryRequestSubscriptions(t *testing.T) { 359 // We can't fit this into TestFilteredSubscriptions, because notary requests 360 // event doesn't depend on blocks events. 361 priv0 := testchain.PrivateKeyByID(0) 362 var goodSender = priv0.GetScriptHash() 363 364 var cases = map[string]struct { 365 params string 366 check func(*testing.T, *neorpc.Notification) 367 }{ 368 "matching sender": { 369 params: `["notary_request_event", {"sender":"` + goodSender.StringLE() + `"}]`, 370 check: func(t *testing.T, resp *neorpc.Notification) { 371 rmap := resp.Payload[0].(map[string]any) 372 require.Equal(t, neorpc.NotaryRequestEventID, resp.Event) 373 require.Equal(t, "added", rmap["type"].(string)) 374 req := rmap["notaryrequest"].(map[string]any) 375 fbTx := req["fallbacktx"].(map[string]any) 376 sender := fbTx["signers"].([]any)[1].(map[string]any)["account"].(string) 377 require.Equal(t, "0x"+goodSender.StringLE(), sender) 378 }, 379 }, 380 "matching signer": { 381 params: `["notary_request_event", {"signer":"` + goodSender.StringLE() + `"}]`, 382 check: func(t *testing.T, resp *neorpc.Notification) { 383 rmap := resp.Payload[0].(map[string]any) 384 require.Equal(t, neorpc.NotaryRequestEventID, resp.Event) 385 require.Equal(t, "added", rmap["type"].(string)) 386 req := rmap["notaryrequest"].(map[string]any) 387 mainTx := req["maintx"].(map[string]any) 388 signers := mainTx["signers"].([]any) 389 signer0 := signers[0].(map[string]any) 390 signer0acc := signer0["account"].(string) 391 require.Equal(t, "0x"+goodSender.StringLE(), signer0acc) 392 }, 393 }, 394 "matching type": { 395 params: `["notary_request_event", {"type":"added"}]`, 396 check: func(t *testing.T, resp *neorpc.Notification) { 397 require.Equal(t, neorpc.NotaryRequestEventID, resp.Event) 398 rmap := resp.Payload[0].(map[string]any) 399 require.Equal(t, "added", rmap["type"].(string)) 400 }, 401 }, 402 "matching sender, signer and type": { 403 params: `["notary_request_event", {"sender":"` + goodSender.StringLE() + `", "signer":"` + goodSender.StringLE() + `","type":"added"}]`, 404 check: func(t *testing.T, resp *neorpc.Notification) { 405 rmap := resp.Payload[0].(map[string]any) 406 require.Equal(t, neorpc.NotaryRequestEventID, resp.Event) 407 require.Equal(t, "added", rmap["type"].(string)) 408 req := rmap["notaryrequest"].(map[string]any) 409 mainTx := req["maintx"].(map[string]any) 410 fbTx := req["fallbacktx"].(map[string]any) 411 sender := fbTx["signers"].([]any)[1].(map[string]any)["account"].(string) 412 require.Equal(t, "0x"+goodSender.StringLE(), sender) 413 signers := mainTx["signers"].([]any) 414 signer0 := signers[0].(map[string]any) 415 signer0acc := signer0["account"].(string) 416 require.Equal(t, "0x"+goodSender.StringLE(), signer0acc) 417 }, 418 }, 419 } 420 421 chain, rpcSrv, c, respMsgs := initCleanServerAndWSClient(t, true) 422 423 // blocks are needed to make GAS deposit for priv0 424 blocks := getTestBlocks(t) 425 for _, b := range blocks { 426 require.NoError(t, chain.AddBlock(b)) 427 } 428 429 var nonce uint32 = 100 430 for name, this := range cases { 431 t.Run(name, func(t *testing.T) { 432 subID := callSubscribe(t, c, respMsgs, this.params) 433 434 err := rpcSrv.coreServer.RelayP2PNotaryRequest(createValidNotaryRequest(chain, priv0, nonce, 2_0000_0000, nil)) 435 require.NoError(t, err) 436 nonce++ 437 438 var resp = new(neorpc.Notification) 439 select { 440 case body := <-respMsgs: 441 require.NoError(t, json.Unmarshal(body, resp)) 442 case <-time.After(time.Second): 443 t.Fatal("timeout waiting for event") 444 } 445 446 require.Equal(t, neorpc.NotaryRequestEventID, resp.Event) 447 this.check(t, resp) 448 449 callUnsubscribe(t, c, respMsgs, subID) 450 }) 451 } 452 } 453 454 func TestFilteredBlockSubscriptions(t *testing.T) { 455 // We can't fit this into TestFilteredSubscriptions, because it uses 456 // blocks as EOF events to wait for. 457 const numBlocks = 10 458 chain, _, c, respMsgs := initCleanServerAndWSClient(t) 459 460 blockSubID := callSubscribe(t, c, respMsgs, `["block_added", {"primary":3}]`) 461 462 var expectedCnt int 463 for i := 0; i < numBlocks; i++ { 464 primary := uint32(i % 4) 465 if primary == 3 { 466 expectedCnt++ 467 } 468 b := testchain.NewBlock(t, chain, 1, primary) 469 require.NoError(t, chain.AddBlock(b)) 470 } 471 472 for i := 0; i < expectedCnt; i++ { 473 var resp = new(neorpc.Notification) 474 select { 475 case body := <-respMsgs: 476 require.NoError(t, json.Unmarshal(body, resp)) 477 case <-time.After(time.Second): 478 t.Fatal("timeout waiting for event") 479 } 480 481 require.Equal(t, neorpc.BlockEventID, resp.Event) 482 rmap := resp.Payload[0].(map[string]any) 483 primary := rmap["primary"].(float64) 484 require.Equal(t, 3, int(primary)) 485 } 486 callUnsubscribe(t, c, respMsgs, blockSubID) 487 } 488 489 func TestHeaderOfAddedBlockSubscriptions(t *testing.T) { 490 const numBlocks = 10 491 chain, _, c, respMsgs := initCleanServerAndWSClient(t) 492 493 headerSubID := callSubscribe(t, c, respMsgs, `["header_of_added_block", {"primary":3}]`) 494 495 var expectedCnt int 496 for i := 0; i < numBlocks; i++ { 497 primary := uint32(i % 4) 498 if primary == 3 { 499 expectedCnt++ 500 } 501 b := testchain.NewBlock(t, chain, 1, primary) 502 require.NoError(t, chain.AddBlock(b)) 503 } 504 505 for i := 0; i < expectedCnt; i++ { 506 var resp = new(neorpc.Notification) 507 select { 508 case body := <-respMsgs: 509 require.NoError(t, json.Unmarshal(body, resp)) 510 case <-time.After(time.Second): 511 t.Fatal("timeout waiting for event") 512 } 513 514 require.Equal(t, neorpc.HeaderOfAddedBlockEventID, resp.Event) 515 rmap := resp.Payload[0].(map[string]any) 516 primary := rmap["primary"].(float64) 517 require.Equal(t, 3, int(primary)) 518 } 519 callUnsubscribe(t, c, respMsgs, headerSubID) 520 } 521 522 func TestMaxSubscriptions(t *testing.T) { 523 var subIDs = make([]string, 0) 524 _, _, c, respMsgs := initCleanServerAndWSClient(t) 525 526 for i := 0; i < maxFeeds+1; i++ { 527 var s string 528 resp := callWSGetRaw(t, c, `{"jsonrpc": "2.0", "method": "subscribe", "params": ["block_added"], "id": 1}`, respMsgs) 529 if i < maxFeeds { 530 require.Nil(t, resp.Error) 531 require.NotNil(t, resp.Result) 532 require.NoError(t, json.Unmarshal(resp.Result, &s)) 533 // Each ID must be unique. 534 for _, id := range subIDs { 535 require.NotEqual(t, id, s) 536 } 537 subIDs = append(subIDs, s) 538 } else { 539 require.NotNil(t, resp.Error) 540 require.Nil(t, resp.Result) 541 } 542 } 543 } 544 545 func TestBadSubUnsub(t *testing.T) { 546 var subCases = map[string]string{ 547 "no params": `{"jsonrpc": "2.0", "method": "subscribe", "params": [], "id": 1}`, 548 "bad (non-string) event": `{"jsonrpc": "2.0", "method": "subscribe", "params": [1], "id": 1}`, 549 "bad (wrong) event": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["block_removed"], "id": 1}`, 550 "missed event": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["event_missed"], "id": 1}`, 551 "block invalid filter": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["block_added", 1], "id": 1}`, 552 "tx filter 1": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_added", 1], "id": 1}`, 553 "tx filter 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_added", {"state": "HALT"}], "id": 1}`, 554 "notification filter 1": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["notification_from_execution", "contract"], "id": 1}`, 555 "notification filter 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["notification_from_execution", "name"], "id": 1}`, 556 "execution filter 1": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_executed", "FAULT"], "id": 1}`, 557 "execution filter 2": `{"jsonrpc": "2.0", "method": "subscribe", "params": ["transaction_executed", {"state": "STOP"}], "id": 1}`, 558 } 559 var unsubCases = map[string]string{ 560 "no params": `{"jsonrpc": "2.0", "method": "unsubscribe", "params": [], "id": 1}`, 561 "bad id": `{"jsonrpc": "2.0", "method": "unsubscribe", "params": ["vasiliy"], "id": 1}`, 562 "not subscribed id": `{"jsonrpc": "2.0", "method": "unsubscribe", "params": ["7"], "id": 1}`, 563 } 564 _, _, c, respMsgs := initCleanServerAndWSClient(t) 565 566 testF := func(t *testing.T, cases map[string]string) func(t *testing.T) { 567 return func(t *testing.T) { 568 for n, s := range cases { 569 t.Run(n, func(t *testing.T) { 570 resp := callWSGetRaw(t, c, s, respMsgs) 571 require.NotNil(t, resp.Error) 572 require.Nil(t, resp.Result) 573 }) 574 } 575 } 576 } 577 t.Run("subscribe", testF(t, subCases)) 578 t.Run("unsubscribe", testF(t, unsubCases)) 579 } 580 581 func doSomeWSRequest(t *testing.T, ws *websocket.Conn) { 582 require.NoError(t, ws.SetWriteDeadline(time.Now().Add(5*time.Second))) 583 // It could be just about anything including invalid request, 584 // we only care about server handling being active. 585 require.NoError(t, ws.WriteMessage(websocket.TextMessage, []byte(`{"jsonrpc": "2.0", "method": "getversion", "params": [], "id": 1}`))) 586 err := ws.SetReadDeadline(time.Now().Add(5 * time.Second)) 587 require.NoError(t, err) 588 _, _, err = ws.ReadMessage() 589 require.NoError(t, err) 590 } 591 592 func TestWSClientsLimit(t *testing.T) { 593 for tname, limit := range map[string]int{"default": 0, "8": 8, "disabled": -1} { 594 effectiveClients := limit 595 if limit == 0 { 596 effectiveClients = defaultMaxWebSocketClients 597 } else if limit < 0 { 598 effectiveClients = 0 599 } 600 t.Run(tname, func(t *testing.T) { 601 _, _, httpSrv := initClearServerWithCustomConfig(t, func(cfg *config.Config) { 602 cfg.ApplicationConfiguration.RPC.MaxWebSocketClients = limit 603 }) 604 605 dialer := websocket.Dialer{HandshakeTimeout: 10 * time.Second} 606 url := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/ws" 607 wss := make([]*websocket.Conn, effectiveClients) 608 var wg sync.WaitGroup 609 610 // Dial effectiveClients connections in parallel 611 for i := 0; i < effectiveClients; i++ { 612 wg.Add(1) 613 j := i 614 go func() { 615 defer wg.Done() 616 ws, r, err := dialer.Dial(url, nil) 617 if r != nil { 618 defer r.Body.Close() 619 } 620 require.NoError(t, err) 621 wss[j] = ws 622 doSomeWSRequest(t, ws) 623 }() 624 } 625 626 wg.Wait() 627 628 // Attempt one more connection, which should fail 629 _, r, err := dialer.Dial(url, nil) 630 require.Error(t, err, "The connection beyond the limit should fail") 631 if r != nil { 632 r.Body.Close() 633 } 634 // Check connections are still alive (it actually is necessary to add 635 // some use of wss to keep connections alive). 636 for _, ws := range wss { 637 doSomeWSRequest(t, ws) 638 ws.Close() 639 } 640 }) 641 } 642 } 643 644 // The purpose of this test is to overflow buffers on server side to 645 // receive a 'missed' event. But it's actually hard to tell when exactly 646 // that's going to happen because of network-level buffering, typical 647 // number seen in tests is around ~3500 events, but it's not reliable enough, 648 // thus this test is disabled. 649 func TestSubscriptionOverflow(t *testing.T) { 650 if !testOverflow { 651 return 652 } 653 const blockCnt = notificationBufSize * 5 654 var receivedMiss bool 655 656 chain, _, c, respMsgs := initCleanServerAndWSClient(t) 657 658 resp := callWSGetRaw(t, c, `{"jsonrpc": "2.0","method": "subscribe","params": ["block_added"],"id": 1}`, respMsgs) 659 require.Nil(t, resp.Error) 660 require.NotNil(t, resp.Result) 661 662 // Push a lot of new blocks, but don't read events for them. 663 for i := 0; i < blockCnt; i++ { 664 b := testchain.NewBlock(t, chain, 1, 0) 665 require.NoError(t, chain.AddBlock(b)) 666 } 667 for i := 0; i < blockCnt; i++ { 668 resp := getNotification(t, respMsgs) 669 if resp.Event != neorpc.BlockEventID { 670 require.Equal(t, neorpc.MissedEventID, resp.Event) 671 receivedMiss = true 672 break 673 } 674 } 675 require.Equal(t, true, receivedMiss) 676 // `Missed` is the last event and there is nothing afterwards. 677 require.Equal(t, 0, len(respMsgs)) 678 } 679 680 func TestFilteredSubscriptions_InvalidFilter(t *testing.T) { 681 var cases = map[string]struct { 682 params string 683 }{ 684 "notification with long name": { 685 params: `["notification_from_execution", {"name":"notification_from_execution_with_long_name"}]`, 686 }, 687 "execution with invalid vm state": { 688 params: `["transaction_executed", {"state":"NOTHALT"}]`, 689 }, 690 } 691 _, _, c, respMsgs := initCleanServerAndWSClient(t) 692 693 for name, this := range cases { 694 t.Run(name, func(t *testing.T) { 695 resp := callWSGetRaw(t, c, fmt.Sprintf(`{"jsonrpc": "2.0","method": "subscribe","params": %s,"id": 1}`, this.params), respMsgs) 696 require.NotNil(t, resp.Error) 697 require.Nil(t, resp.Result) 698 require.Contains(t, resp.Error.Error(), neorpc.ErrInvalidSubscriptionFilter.Error()) 699 }) 700 } 701 }