github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/rpcclient/wsclient_test.go (about) 1 package rpcclient 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "net/http/httptest" 9 "sort" 10 "strconv" 11 "strings" 12 "sync" 13 "sync/atomic" 14 "testing" 15 "time" 16 17 "github.com/gorilla/websocket" 18 "github.com/nspcc-dev/neo-go/pkg/config/netmode" 19 "github.com/nspcc-dev/neo-go/pkg/core/block" 20 "github.com/nspcc-dev/neo-go/pkg/core/mempoolevent" 21 "github.com/nspcc-dev/neo-go/pkg/core/state" 22 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 23 "github.com/nspcc-dev/neo-go/pkg/neorpc" 24 "github.com/nspcc-dev/neo-go/pkg/neorpc/result" 25 "github.com/nspcc-dev/neo-go/pkg/network/payload" 26 "github.com/nspcc-dev/neo-go/pkg/services/rpcsrv/params" 27 "github.com/nspcc-dev/neo-go/pkg/util" 28 "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 ) 32 33 func TestWSClientClose(t *testing.T) { 34 srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": "55aaff00"}`) 35 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 36 require.NoError(t, err) 37 wsc.cache.initDone = true 38 wsc.getNextRequestID = getTestRequestID 39 bCh := make(chan *block.Block) 40 _, err = wsc.ReceiveBlocks(nil, bCh) 41 require.NoError(t, err) 42 wsc.Close() 43 // Subscriber channel must be closed by server. 44 _, ok := <-bCh 45 require.False(t, ok) 46 } 47 48 func TestWSClientSubscription(t *testing.T) { 49 bCh := make(chan *block.Block) 50 txCh := make(chan *transaction.Transaction) 51 aerCh := make(chan *state.AppExecResult) 52 ntfCh := make(chan *state.ContainedNotificationEvent) 53 ntrCh := make(chan *result.NotaryRequestEvent) 54 var cases = map[string]func(*WSClient) (string, error){ 55 "blocks": func(wsc *WSClient) (string, error) { 56 return wsc.ReceiveBlocks(nil, bCh) 57 }, 58 "transactions": func(wsc *WSClient) (string, error) { 59 return wsc.ReceiveTransactions(nil, txCh) 60 }, 61 "notifications": func(wsc *WSClient) (string, error) { 62 return wsc.ReceiveExecutionNotifications(nil, ntfCh) 63 }, 64 "executions": func(wsc *WSClient) (string, error) { 65 return wsc.ReceiveExecutions(nil, aerCh) 66 }, 67 "notary requests": func(wsc *WSClient) (string, error) { 68 return wsc.ReceiveNotaryRequests(nil, ntrCh) 69 }, 70 } 71 t.Run("good", func(t *testing.T) { 72 for name, f := range cases { 73 t.Run(name, func(t *testing.T) { 74 srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": "55aaff00"}`) 75 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 76 require.NoError(t, err) 77 wsc.getNextRequestID = getTestRequestID 78 require.NoError(t, wsc.Init()) 79 id, err := f(wsc) 80 require.NoError(t, err) 81 require.Equal(t, "55aaff00", id) 82 }) 83 } 84 }) 85 t.Run("bad", func(t *testing.T) { 86 for name, f := range cases { 87 t.Run(name, func(t *testing.T) { 88 srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "error":{"code":-32602,"message":"Invalid params"}}`) 89 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 90 require.NoError(t, err) 91 wsc.getNextRequestID = getTestRequestID 92 require.NoError(t, wsc.Init()) 93 _, err = f(wsc) 94 require.Error(t, err) 95 }) 96 } 97 }) 98 } 99 100 func TestWSClientUnsubscription(t *testing.T) { 101 type responseCheck struct { 102 response string 103 code func(*testing.T, *WSClient) 104 } 105 var cases = map[string]responseCheck{ 106 "good": {`{"jsonrpc": "2.0", "id": 1, "result": true}`, func(t *testing.T, wsc *WSClient) { 107 // We can't really subscribe using this stub server, so set up wsc internals. 108 wsc.subscriptions["0"] = &blockReceiver{} 109 err := wsc.Unsubscribe("0") 110 require.NoError(t, err) 111 }}, 112 "all": {`{"jsonrpc": "2.0", "id": 1, "result": true}`, func(t *testing.T, wsc *WSClient) { 113 // We can't really subscribe using this stub server, so set up wsc internals. 114 wsc.subscriptions["0"] = &blockReceiver{} 115 err := wsc.UnsubscribeAll() 116 require.NoError(t, err) 117 require.Equal(t, 0, len(wsc.subscriptions)) 118 }}, 119 "not subscribed": {`{"jsonrpc": "2.0", "id": 1, "result": true}`, func(t *testing.T, wsc *WSClient) { 120 err := wsc.Unsubscribe("0") 121 require.Error(t, err) 122 }}, 123 "error returned": {`{"jsonrpc": "2.0", "id": 1, "error":{"code":-32602,"message":"Invalid params"}}`, func(t *testing.T, wsc *WSClient) { 124 // We can't really subscribe using this stub server, so set up wsc internals. 125 wsc.subscriptions["0"] = &blockReceiver{} 126 err := wsc.Unsubscribe("0") 127 require.Error(t, err) 128 }}, 129 "false returned": {`{"jsonrpc": "2.0", "id": 1, "result": false}`, func(t *testing.T, wsc *WSClient) { 130 // We can't really subscribe using this stub server, so set up wsc internals. 131 wsc.subscriptions["0"] = &blockReceiver{} 132 err := wsc.Unsubscribe("0") 133 require.Error(t, err) 134 }}, 135 } 136 for name, rc := range cases { 137 t.Run(name, func(t *testing.T) { 138 srv := initTestServer(t, rc.response) 139 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 140 require.NoError(t, err) 141 wsc.getNextRequestID = getTestRequestID 142 require.NoError(t, wsc.Init()) 143 rc.code(t, wsc) 144 }) 145 } 146 } 147 148 func TestWSClientEvents(t *testing.T) { 149 var ok bool 150 // Events from RPC server testchain. 151 var events = []string{ 152 `{"jsonrpc":"2.0","method":"transaction_executed","params":[{"container":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","trigger":"Application","vmstate":"HALT","gasconsumed":"22910000","stack":[],"notifications":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}},{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"transfer","state":{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}}]}]}`, 153 `{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"container":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}}]}`, 154 `{"jsonrpc":"2.0","method":"transaction_executed","params":[{"container":"0xf97a72b7722c109f909a8bc16c22368c5023d85828b09b127b237aace33cf099","trigger":"Application","vmstate":"HALT","gasconsumed":"6042610","stack":[],"notifications":[{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteString","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}]}},{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","eventname":"transfer","state":{"type":"Array","value":[{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteString","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}}]}]}`, 155 fmt.Sprintf(`{"jsonrpc":"2.0","method":"block_added","params":[%s]}`, b1Verbose), 156 `{"jsonrpc":"2.0","method":"event_missed","params":[]}`, // the last one, will trigger receiver channels closing. 157 } 158 startSending := make(chan struct{}) 159 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 160 if req.URL.Path == "/ws" && req.Method == "GET" { 161 var upgrader = websocket.Upgrader{} 162 ws, err := upgrader.Upgrade(w, req, nil) 163 require.NoError(t, err) 164 <-startSending 165 for _, event := range events { 166 err = ws.SetWriteDeadline(time.Now().Add(5 * time.Second)) 167 require.NoError(t, err) 168 err = ws.WriteMessage(1, []byte(event)) 169 if err != nil { 170 break 171 } 172 } 173 ws.Close() 174 return 175 } 176 })) 177 t.Cleanup(srv.Close) 178 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 179 require.NoError(t, err) 180 wsc.getNextRequestID = getTestRequestID 181 wsc.cacheLock.Lock() 182 wsc.cache.initDone = true // Our server mock is restricted, so perform initialisation manually. 183 wsc.cache.network = netmode.UnitTestNet 184 wsc.cacheLock.Unlock() 185 186 // Our server mock is restricted, so perform subscriptions manually with default notifications channel. 187 bCh1 := make(chan *block.Block) 188 bCh2 := make(chan *block.Block) 189 aerCh1 := make(chan *state.AppExecResult) 190 aerCh2 := make(chan *state.AppExecResult) 191 aerCh3 := make(chan *state.AppExecResult) 192 ntfCh := make(chan *state.ContainedNotificationEvent) 193 halt := "HALT" 194 fault := "FAULT" 195 wsc.subscriptionsLock.Lock() 196 wsc.subscriptions["0"] = &blockReceiver{ch: bCh1} 197 wsc.receivers[chan<- *block.Block(bCh1)] = []string{"0"} 198 wsc.subscriptions["1"] = &blockReceiver{ch: bCh2} // two different channels subscribed for same notifications 199 wsc.receivers[chan<- *block.Block(bCh2)] = []string{"1"} 200 201 wsc.subscriptions["2"] = &executionNotificationReceiver{ch: ntfCh} 202 wsc.subscriptions["3"] = &executionNotificationReceiver{ch: ntfCh} // check duplicating subscriptions 203 wsc.receivers[chan<- *state.ContainedNotificationEvent(ntfCh)] = []string{"2", "3"} 204 205 wsc.subscriptions["4"] = &executionReceiver{ch: aerCh1} 206 wsc.receivers[chan<- *state.AppExecResult(aerCh1)] = []string{"4"} 207 wsc.subscriptions["5"] = &executionReceiver{filter: &neorpc.ExecutionFilter{State: &halt}, ch: aerCh2} 208 wsc.receivers[chan<- *state.AppExecResult(aerCh2)] = []string{"5"} 209 wsc.subscriptions["6"] = &executionReceiver{filter: &neorpc.ExecutionFilter{State: &fault}, ch: aerCh3} 210 wsc.receivers[chan<- *state.AppExecResult(aerCh3)] = []string{"6"} 211 // MissedEvent must close the channels above. 212 213 wsc.subscriptionsLock.Unlock() 214 close(startSending) 215 216 var ( 217 b1Cnt, b2Cnt int 218 aer1Cnt, aer2Cnt, aer3Cnt int 219 ntfCnt int 220 expectedb1Cnt, expectedb2Cnt = 1, 1 // single Block event 221 expectedaer1Cnt, expectedaer2Cnt, expectedaer3Cnt = 2, 2, 0 // two HALTED AERs 222 expectedntfCnt = 1 // single notification event 223 aer *state.AppExecResult 224 ) 225 for b1Cnt+b2Cnt+ 226 aer1Cnt+aer2Cnt+aer3Cnt+ 227 ntfCnt != 228 expectedb1Cnt+expectedb2Cnt+ 229 expectedaer1Cnt+expectedaer2Cnt+expectedaer3Cnt+ 230 expectedntfCnt { 231 select { 232 case _, ok = <-bCh1: 233 if ok { 234 b1Cnt++ 235 } 236 case _, ok = <-bCh2: 237 if ok { 238 b2Cnt++ 239 } 240 case _, ok = <-aerCh1: 241 if ok { 242 aer1Cnt++ 243 } 244 case aer, ok = <-aerCh2: 245 if ok { 246 require.Equal(t, vmstate.Halt, aer.VMState) 247 aer2Cnt++ 248 } 249 case _, ok = <-aerCh3: 250 if ok { 251 aer3Cnt++ 252 } 253 case _, ok = <-ntfCh: 254 if ok { 255 ntfCnt++ 256 } 257 case <-time.After(time.Second): 258 t.Fatal("timeout waiting for event") 259 } 260 } 261 assert.Equal(t, expectedb1Cnt, b1Cnt) 262 assert.Equal(t, expectedb2Cnt, b2Cnt) 263 assert.Equal(t, expectedaer1Cnt, aer1Cnt) 264 assert.Equal(t, expectedaer2Cnt, aer2Cnt) 265 assert.Equal(t, expectedaer3Cnt, aer3Cnt) 266 assert.Equal(t, expectedntfCnt, ntfCnt) 267 268 // Channels must be closed by server 269 _, ok = <-bCh1 270 require.False(t, ok) 271 _, ok = <-bCh2 272 require.False(t, ok) 273 _, ok = <-aerCh1 274 require.False(t, ok) 275 _, ok = <-aerCh2 276 require.False(t, ok) 277 _, ok = <-aerCh3 278 require.False(t, ok) 279 _, ok = <-ntfCh 280 require.False(t, ok) 281 _, ok = <-ntfCh 282 require.False(t, ok) 283 } 284 285 func TestWSClientNonBlockingEvents(t *testing.T) { 286 // Use buffered channel as a receiver to check it will be closed by WSClient 287 // after overflow if CloseNotificationChannelIfFull option is enabled. 288 const chCap = 3 289 bCh := make(chan *block.Block, chCap) 290 291 // Events from RPC server testchain. Require events len to be larger than chCap to reach 292 // subscriber's chanel overflow. 293 var events = []string{ 294 fmt.Sprintf(`{"jsonrpc":"2.0","method":"block_added","params":[%s]}`, b1Verbose), 295 fmt.Sprintf(`{"jsonrpc":"2.0","method":"block_added","params":[%s]}`, b1Verbose), 296 fmt.Sprintf(`{"jsonrpc":"2.0","method":"block_added","params":[%s]}`, b1Verbose), 297 fmt.Sprintf(`{"jsonrpc":"2.0","method":"block_added","params":[%s]}`, b1Verbose), 298 fmt.Sprintf(`{"jsonrpc":"2.0","method":"block_added","params":[%s]}`, b1Verbose), 299 } 300 require.True(t, chCap < len(events)) 301 302 var blocksSent atomic.Bool 303 startSending := make(chan struct{}) 304 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 305 if req.URL.Path == "/ws" && req.Method == "GET" { 306 var upgrader = websocket.Upgrader{} 307 ws, err := upgrader.Upgrade(w, req, nil) 308 require.NoError(t, err) 309 <-startSending 310 for _, event := range events { 311 err = ws.SetWriteDeadline(time.Now().Add(5 * time.Second)) 312 require.NoError(t, err) 313 err = ws.WriteMessage(1, []byte(event)) 314 if err != nil { 315 break 316 } 317 } 318 blocksSent.Store(true) 319 ws.Close() 320 return 321 } 322 })) 323 t.Cleanup(srv.Close) 324 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{CloseNotificationChannelIfFull: true}) 325 require.NoError(t, err) 326 wsc.getNextRequestID = getTestRequestID 327 wsc.cacheLock.Lock() 328 wsc.cache.initDone = true // Our server mock is restricted, so perform initialisation manually. 329 wsc.cache.network = netmode.UnitTestNet 330 wsc.cacheLock.Unlock() 331 332 // Our server mock is restricted, so perform subscriptions manually. 333 wsc.subscriptionsLock.Lock() 334 wsc.subscriptions["0"] = &blockReceiver{ch: bCh} 335 wsc.subscriptions["1"] = &blockReceiver{ch: bCh} 336 wsc.receivers[chan<- *block.Block(bCh)] = []string{"0", "1"} 337 wsc.subscriptionsLock.Unlock() 338 339 close(startSending) 340 // Check that events are sent to WSClient. 341 require.Eventually(t, func() bool { 342 return blocksSent.Load() 343 }, time.Second, 100*time.Millisecond) 344 345 // Check that block receiver channel was removed from the receivers list due to overflow. 346 require.Eventually(t, func() bool { 347 wsc.subscriptionsLock.RLock() 348 defer wsc.subscriptionsLock.RUnlock() 349 return len(wsc.receivers) == 0 350 }, 2*time.Second, 200*time.Millisecond) 351 352 // Check that subscriptions are still there and waiting for the call to Unsubscribe() 353 // to be excluded from the subscriptions map. 354 wsc.subscriptionsLock.RLock() 355 require.True(t, len(wsc.subscriptions) == 2) 356 wsc.subscriptionsLock.RUnlock() 357 358 // Check that receiver was closed after overflow. 359 for i := 0; i < chCap; i++ { 360 _, ok := <-bCh 361 require.True(t, ok) 362 } 363 select { 364 case _, ok := <-bCh: 365 require.False(t, ok) 366 default: 367 t.Fatal("channel wasn't closed by WSClient") 368 } 369 } 370 371 func TestWSExecutionVMStateCheck(t *testing.T) { 372 // Will answer successfully if request slips through. 373 srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": "55aaff00"}`) 374 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 375 require.NoError(t, err) 376 wsc.getNextRequestID = getTestRequestID 377 require.NoError(t, wsc.Init()) 378 filter := "NONE" 379 _, err = wsc.ReceiveExecutions(&neorpc.ExecutionFilter{State: &filter}, make(chan *state.AppExecResult)) 380 require.ErrorIs(t, err, neorpc.ErrInvalidSubscriptionFilter) 381 wsc.Close() 382 } 383 384 func TestWSExecutionNotificationNameCheck(t *testing.T) { 385 // Will answer successfully if request slips through. 386 srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": "55aaff00"}`) 387 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 388 require.NoError(t, err) 389 wsc.getNextRequestID = getTestRequestID 390 require.NoError(t, wsc.Init()) 391 filter := "notification_from_execution_with_long_name" 392 _, err = wsc.ReceiveExecutionNotifications(&neorpc.NotificationFilter{Name: &filter}, make(chan *state.ContainedNotificationEvent)) 393 require.ErrorIs(t, err, neorpc.ErrInvalidSubscriptionFilter) 394 wsc.Close() 395 } 396 397 func TestWSFilteredSubscriptions(t *testing.T) { 398 var cases = []struct { 399 name string 400 clientCode func(*testing.T, *WSClient) 401 serverCode func(*testing.T, *params.Params) 402 }{ 403 {"block header primary", 404 func(t *testing.T, wsc *WSClient) { 405 primary := byte(3) 406 _, err := wsc.ReceiveHeadersOfAddedBlocks(&neorpc.BlockFilter{Primary: &primary}, make(chan *block.Header)) 407 require.NoError(t, err) 408 }, 409 func(t *testing.T, p *params.Params) { 410 param := p.Value(1) 411 filt := new(neorpc.BlockFilter) 412 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 413 require.Equal(t, byte(3), *filt.Primary) 414 require.Equal(t, (*uint32)(nil), filt.Since) 415 require.Equal(t, (*uint32)(nil), filt.Till) 416 }, 417 }, 418 {"header since", 419 func(t *testing.T, wsc *WSClient) { 420 var since uint32 = 3 421 _, err := wsc.ReceiveHeadersOfAddedBlocks(&neorpc.BlockFilter{Since: &since}, make(chan *block.Header)) 422 require.NoError(t, err) 423 }, 424 func(t *testing.T, p *params.Params) { 425 param := p.Value(1) 426 filt := new(neorpc.BlockFilter) 427 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 428 require.Equal(t, (*byte)(nil), filt.Primary) 429 require.Equal(t, uint32(3), *filt.Since) 430 require.Equal(t, (*uint32)(nil), filt.Till) 431 }, 432 }, 433 {"header till", 434 func(t *testing.T, wsc *WSClient) { 435 var till uint32 = 3 436 _, err := wsc.ReceiveHeadersOfAddedBlocks(&neorpc.BlockFilter{Till: &till}, make(chan *block.Header)) 437 require.NoError(t, err) 438 }, 439 func(t *testing.T, p *params.Params) { 440 param := p.Value(1) 441 filt := new(neorpc.BlockFilter) 442 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 443 require.Equal(t, (*byte)(nil), filt.Primary) 444 require.Equal(t, (*uint32)(nil), filt.Since) 445 require.Equal(t, (uint32)(3), *filt.Till) 446 }, 447 }, 448 {"header primary, since and till", 449 func(t *testing.T, wsc *WSClient) { 450 var ( 451 since uint32 = 3 452 primary = byte(2) 453 till uint32 = 5 454 ) 455 _, err := wsc.ReceiveHeadersOfAddedBlocks(&neorpc.BlockFilter{ 456 Primary: &primary, 457 Since: &since, 458 Till: &till, 459 }, make(chan *block.Header)) 460 require.NoError(t, err) 461 }, 462 func(t *testing.T, p *params.Params) { 463 param := p.Value(1) 464 filt := new(neorpc.BlockFilter) 465 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 466 require.Equal(t, byte(2), *filt.Primary) 467 require.Equal(t, uint32(3), *filt.Since) 468 require.Equal(t, uint32(5), *filt.Till) 469 }, 470 }, 471 {"blocks primary", 472 func(t *testing.T, wsc *WSClient) { 473 primary := byte(3) 474 _, err := wsc.ReceiveBlocks(&neorpc.BlockFilter{Primary: &primary}, make(chan *block.Block)) 475 require.NoError(t, err) 476 }, 477 func(t *testing.T, p *params.Params) { 478 param := p.Value(1) 479 filt := new(neorpc.BlockFilter) 480 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 481 require.Equal(t, byte(3), *filt.Primary) 482 require.Equal(t, (*uint32)(nil), filt.Since) 483 require.Equal(t, (*uint32)(nil), filt.Till) 484 }, 485 }, 486 {"blocks since", 487 func(t *testing.T, wsc *WSClient) { 488 var since uint32 = 3 489 _, err := wsc.ReceiveBlocks(&neorpc.BlockFilter{Since: &since}, make(chan *block.Block)) 490 require.NoError(t, err) 491 }, 492 func(t *testing.T, p *params.Params) { 493 param := p.Value(1) 494 filt := new(neorpc.BlockFilter) 495 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 496 require.Equal(t, (*byte)(nil), filt.Primary) 497 require.Equal(t, uint32(3), *filt.Since) 498 require.Equal(t, (*uint32)(nil), filt.Till) 499 }, 500 }, 501 {"blocks till", 502 func(t *testing.T, wsc *WSClient) { 503 var till uint32 = 3 504 _, err := wsc.ReceiveBlocks(&neorpc.BlockFilter{Till: &till}, make(chan *block.Block)) 505 require.NoError(t, err) 506 }, 507 func(t *testing.T, p *params.Params) { 508 param := p.Value(1) 509 filt := new(neorpc.BlockFilter) 510 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 511 require.Equal(t, (*byte)(nil), filt.Primary) 512 require.Equal(t, (*uint32)(nil), filt.Since) 513 require.Equal(t, (uint32)(3), *filt.Till) 514 }, 515 }, 516 {"blocks primary, since and till", 517 func(t *testing.T, wsc *WSClient) { 518 var ( 519 since uint32 = 3 520 primary = byte(2) 521 till uint32 = 5 522 ) 523 _, err := wsc.ReceiveBlocks(&neorpc.BlockFilter{ 524 Primary: &primary, 525 Since: &since, 526 Till: &till, 527 }, make(chan *block.Block)) 528 require.NoError(t, err) 529 }, 530 func(t *testing.T, p *params.Params) { 531 param := p.Value(1) 532 filt := new(neorpc.BlockFilter) 533 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 534 require.Equal(t, byte(2), *filt.Primary) 535 require.Equal(t, uint32(3), *filt.Since) 536 require.Equal(t, uint32(5), *filt.Till) 537 }, 538 }, 539 {"transactions sender", 540 func(t *testing.T, wsc *WSClient) { 541 sender := util.Uint160{1, 2, 3, 4, 5} 542 _, err := wsc.ReceiveTransactions(&neorpc.TxFilter{Sender: &sender}, make(chan *transaction.Transaction)) 543 require.NoError(t, err) 544 }, 545 func(t *testing.T, p *params.Params) { 546 param := p.Value(1) 547 filt := new(neorpc.TxFilter) 548 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 549 require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender) 550 require.Nil(t, filt.Signer) 551 }, 552 }, 553 {"transactions signer", 554 func(t *testing.T, wsc *WSClient) { 555 signer := util.Uint160{0, 42} 556 _, err := wsc.ReceiveTransactions(&neorpc.TxFilter{Signer: &signer}, make(chan *transaction.Transaction)) 557 require.NoError(t, err) 558 }, 559 func(t *testing.T, p *params.Params) { 560 param := p.Value(1) 561 filt := new(neorpc.TxFilter) 562 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 563 require.Nil(t, filt.Sender) 564 require.Equal(t, util.Uint160{0, 42}, *filt.Signer) 565 }, 566 }, 567 {"transactions sender and signer", 568 func(t *testing.T, wsc *WSClient) { 569 sender := util.Uint160{1, 2, 3, 4, 5} 570 signer := util.Uint160{0, 42} 571 _, err := wsc.ReceiveTransactions(&neorpc.TxFilter{Sender: &sender, Signer: &signer}, make(chan *transaction.Transaction)) 572 require.NoError(t, err) 573 }, 574 func(t *testing.T, p *params.Params) { 575 param := p.Value(1) 576 filt := new(neorpc.TxFilter) 577 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 578 require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender) 579 require.Equal(t, util.Uint160{0, 42}, *filt.Signer) 580 }, 581 }, 582 {"notifications contract hash", 583 func(t *testing.T, wsc *WSClient) { 584 contract := util.Uint160{1, 2, 3, 4, 5} 585 _, err := wsc.ReceiveExecutionNotifications(&neorpc.NotificationFilter{Contract: &contract}, make(chan *state.ContainedNotificationEvent)) 586 require.NoError(t, err) 587 }, 588 func(t *testing.T, p *params.Params) { 589 param := p.Value(1) 590 filt := new(neorpc.NotificationFilter) 591 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 592 require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Contract) 593 require.Nil(t, filt.Name) 594 }, 595 }, 596 {"notifications name", 597 func(t *testing.T, wsc *WSClient) { 598 name := "my_pretty_notification" 599 _, err := wsc.ReceiveExecutionNotifications(&neorpc.NotificationFilter{Name: &name}, make(chan *state.ContainedNotificationEvent)) 600 require.NoError(t, err) 601 }, 602 func(t *testing.T, p *params.Params) { 603 param := p.Value(1) 604 filt := new(neorpc.NotificationFilter) 605 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 606 require.Equal(t, "my_pretty_notification", *filt.Name) 607 require.Nil(t, filt.Contract) 608 }, 609 }, 610 {"notifications contract hash and name", 611 func(t *testing.T, wsc *WSClient) { 612 contract := util.Uint160{1, 2, 3, 4, 5} 613 name := "my_pretty_notification" 614 _, err := wsc.ReceiveExecutionNotifications(&neorpc.NotificationFilter{Contract: &contract, Name: &name}, make(chan *state.ContainedNotificationEvent)) 615 require.NoError(t, err) 616 }, 617 func(t *testing.T, p *params.Params) { 618 param := p.Value(1) 619 filt := new(neorpc.NotificationFilter) 620 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 621 require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Contract) 622 require.Equal(t, "my_pretty_notification", *filt.Name) 623 }, 624 }, 625 {"executions state", 626 func(t *testing.T, wsc *WSClient) { 627 vmstate := "FAULT" 628 _, err := wsc.ReceiveExecutions(&neorpc.ExecutionFilter{State: &vmstate}, make(chan *state.AppExecResult)) 629 require.NoError(t, err) 630 }, 631 func(t *testing.T, p *params.Params) { 632 param := p.Value(1) 633 filt := new(neorpc.ExecutionFilter) 634 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 635 require.Equal(t, "FAULT", *filt.State) 636 require.Equal(t, (*util.Uint256)(nil), filt.Container) 637 }, 638 }, 639 {"executions container", 640 func(t *testing.T, wsc *WSClient) { 641 container := util.Uint256{1, 2, 3} 642 _, err := wsc.ReceiveExecutions(&neorpc.ExecutionFilter{Container: &container}, make(chan *state.AppExecResult)) 643 require.NoError(t, err) 644 }, 645 func(t *testing.T, p *params.Params) { 646 param := p.Value(1) 647 filt := new(neorpc.ExecutionFilter) 648 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 649 require.Equal(t, (*string)(nil), filt.State) 650 require.Equal(t, util.Uint256{1, 2, 3}, *filt.Container) 651 }, 652 }, 653 {"executions state and container", 654 func(t *testing.T, wsc *WSClient) { 655 vmstate := "FAULT" 656 container := util.Uint256{1, 2, 3} 657 _, err := wsc.ReceiveExecutions(&neorpc.ExecutionFilter{State: &vmstate, Container: &container}, make(chan *state.AppExecResult)) 658 require.NoError(t, err) 659 }, 660 func(t *testing.T, p *params.Params) { 661 param := p.Value(1) 662 filt := new(neorpc.ExecutionFilter) 663 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 664 require.Equal(t, "FAULT", *filt.State) 665 require.Equal(t, util.Uint256{1, 2, 3}, *filt.Container) 666 }, 667 }, 668 { 669 "notary request sender", 670 func(t *testing.T, wsc *WSClient) { 671 sender := util.Uint160{1, 2, 3, 4, 5} 672 _, err := wsc.ReceiveNotaryRequests(&neorpc.NotaryRequestFilter{Sender: &sender}, make(chan *result.NotaryRequestEvent)) 673 require.NoError(t, err) 674 }, 675 func(t *testing.T, p *params.Params) { 676 param := p.Value(1) 677 filt := new(neorpc.NotaryRequestFilter) 678 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 679 require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender) 680 require.Nil(t, filt.Signer) 681 require.Nil(t, filt.Type) 682 }, 683 }, 684 { 685 "notary request signer", 686 func(t *testing.T, wsc *WSClient) { 687 signer := util.Uint160{0, 42} 688 _, err := wsc.ReceiveNotaryRequests(&neorpc.NotaryRequestFilter{Signer: &signer}, make(chan *result.NotaryRequestEvent)) 689 require.NoError(t, err) 690 }, 691 func(t *testing.T, p *params.Params) { 692 param := p.Value(1) 693 filt := new(neorpc.NotaryRequestFilter) 694 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 695 require.Nil(t, filt.Sender) 696 require.Equal(t, util.Uint160{0, 42}, *filt.Signer) 697 require.Nil(t, filt.Type) 698 }, 699 }, 700 { 701 "notary request type", 702 func(t *testing.T, wsc *WSClient) { 703 mempoolType := mempoolevent.TransactionAdded 704 _, err := wsc.ReceiveNotaryRequests(&neorpc.NotaryRequestFilter{Type: &mempoolType}, make(chan *result.NotaryRequestEvent)) 705 require.NoError(t, err) 706 }, 707 func(t *testing.T, p *params.Params) { 708 param := p.Value(1) 709 filt := new(neorpc.NotaryRequestFilter) 710 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 711 require.Equal(t, mempoolevent.TransactionAdded, *filt.Type) 712 require.Nil(t, filt.Sender) 713 require.Nil(t, filt.Signer) 714 }, 715 }, 716 {"notary request sender, signer and type", 717 func(t *testing.T, wsc *WSClient) { 718 sender := util.Uint160{1, 2, 3, 4, 5} 719 signer := util.Uint160{0, 42} 720 mempoolType := mempoolevent.TransactionAdded 721 _, err := wsc.ReceiveNotaryRequests(&neorpc.NotaryRequestFilter{Type: &mempoolType, Signer: &signer, Sender: &sender}, make(chan *result.NotaryRequestEvent)) 722 require.NoError(t, err) 723 }, 724 func(t *testing.T, p *params.Params) { 725 param := p.Value(1) 726 filt := new(neorpc.NotaryRequestFilter) 727 require.NoError(t, json.Unmarshal(param.RawMessage, filt)) 728 require.Equal(t, util.Uint160{1, 2, 3, 4, 5}, *filt.Sender) 729 require.Equal(t, util.Uint160{0, 42}, *filt.Signer) 730 require.Equal(t, mempoolevent.TransactionAdded, *filt.Type) 731 }, 732 }, 733 } 734 for _, c := range cases { 735 t.Run(c.name, func(t *testing.T) { 736 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 737 if req.URL.Path == "/ws" && req.Method == "GET" { 738 var upgrader = websocket.Upgrader{} 739 ws, err := upgrader.Upgrade(w, req, nil) 740 require.NoError(t, err) 741 err = ws.SetReadDeadline(time.Now().Add(5 * time.Second)) 742 require.NoError(t, err) 743 req := params.In{} 744 err = ws.ReadJSON(&req) 745 require.NoError(t, err) 746 params := params.Params(req.RawParams) 747 c.serverCode(t, ¶ms) 748 err = ws.SetWriteDeadline(time.Now().Add(5 * time.Second)) 749 require.NoError(t, err) 750 err = ws.WriteMessage(1, []byte(`{"jsonrpc": "2.0", "id": 1, "result": "0"}`)) 751 require.NoError(t, err) 752 ws.Close() 753 } 754 })) 755 t.Cleanup(srv.Close) 756 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 757 require.NoError(t, err) 758 wsc.getNextRequestID = getTestRequestID 759 wsc.cache.network = netmode.UnitTestNet 760 wsc.cache.initDone = true 761 c.clientCode(t, wsc) 762 wsc.Close() 763 }) 764 } 765 } 766 767 func TestNewWS(t *testing.T) { 768 srv := initTestServer(t, "") 769 770 t.Run("good", func(t *testing.T) { 771 c, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 772 require.NoError(t, err) 773 c.getNextRequestID = getTestRequestID 774 c.cache.network = netmode.UnitTestNet 775 require.NoError(t, c.Init()) 776 }) 777 t.Run("bad URL", func(t *testing.T) { 778 _, err := NewWS(context.TODO(), strings.TrimPrefix(srv.URL, "http://"), WSOptions{}) 779 require.Error(t, err) 780 }) 781 } 782 783 func TestWSConcurrentAccess(t *testing.T) { 784 var ids struct { 785 lock sync.RWMutex 786 m map[int]struct{} 787 } 788 ids.m = make(map[int]struct{}) 789 790 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 791 if req.URL.Path == "/ws" && req.Method == "GET" { 792 var upgrader = websocket.Upgrader{} 793 ws, err := upgrader.Upgrade(w, req, nil) 794 require.NoError(t, err) 795 for { 796 err = ws.SetReadDeadline(time.Now().Add(5 * time.Second)) 797 require.NoError(t, err) 798 _, p, err := ws.ReadMessage() 799 if err != nil { 800 break 801 } 802 r := params.NewIn() 803 err = json.Unmarshal(p, r) 804 if err != nil { 805 t.Fatalf("Cannot decode request body: %s", req.Body) 806 } 807 i, err := strconv.Atoi(string(r.RawID)) 808 require.NoError(t, err) 809 ids.lock.Lock() 810 ids.m[i] = struct{}{} 811 ids.lock.Unlock() 812 var response string 813 // Different responses to catch possible unmarshalling errors connected with invalid IDs distribution. 814 switch r.Method { 815 case "getblockcount": 816 response = fmt.Sprintf(`{"id":%s,"jsonrpc":"2.0","result":123}`, r.RawID) 817 case "getversion": 818 response = fmt.Sprintf(`{"id":%s,"jsonrpc":"2.0","result":{"network":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`, r.RawID) 819 case "getblockhash": 820 response = fmt.Sprintf(`{"id":%s,"jsonrpc":"2.0","result":"0x157ca5e5b8cf8f84c9660502a3270b346011612bded1514a6847f877c433a9bb"}`, r.RawID) 821 } 822 err = ws.SetWriteDeadline(time.Now().Add(5 * time.Second)) 823 require.NoError(t, err) 824 err = ws.WriteMessage(1, []byte(response)) 825 if err != nil { 826 break 827 } 828 } 829 ws.Close() 830 return 831 } 832 })) 833 t.Cleanup(srv.Close) 834 835 wsc, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 836 require.NoError(t, err) 837 batchCount := 100 838 completed := &atomic.Int32{} 839 for i := 0; i < batchCount; i++ { 840 go func() { 841 _, err := wsc.GetBlockCount() 842 require.NoError(t, err) 843 completed.Add(1) 844 }() 845 go func() { 846 _, err := wsc.GetBlockHash(123) 847 require.NoError(t, err) 848 completed.Add(1) 849 }() 850 851 go func() { 852 _, err := wsc.GetVersion() 853 require.NoError(t, err) 854 completed.Add(1) 855 }() 856 } 857 require.Eventually(t, func() bool { 858 return int(completed.Load()) == batchCount*3 859 }, time.Second, 100*time.Millisecond) 860 861 ids.lock.RLock() 862 require.True(t, len(ids.m) > batchCount) 863 idsList := make([]int, 0, len(ids.m)) 864 for i := range ids.m { 865 idsList = append(idsList, i) 866 } 867 ids.lock.RUnlock() 868 869 sort.Ints(idsList) 870 require.Equal(t, 1, idsList[0]) 871 require.Less(t, idsList[len(idsList)-1], 872 batchCount*3+1) // batchCount*requestsPerBatch+1 873 wsc.Close() 874 } 875 876 func TestWSDoubleClose(t *testing.T) { 877 srv := initTestServer(t, "") 878 879 c, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 880 require.NoError(t, err) 881 882 require.NotPanics(t, func() { 883 c.Close() 884 c.Close() 885 }) 886 } 887 888 func TestWS_RequestAfterClose(t *testing.T) { 889 srv := initTestServer(t, "") 890 891 c, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 892 require.NoError(t, err) 893 894 c.Close() 895 896 require.NotPanics(t, func() { 897 _, err = c.GetBlockCount() 898 }) 899 require.Error(t, err) 900 require.ErrorIs(t, err, ErrWSConnLost) 901 } 902 903 func TestWSClient_ConnClosedError(t *testing.T) { 904 t.Run("standard closing", func(t *testing.T) { 905 srv := initTestServer(t, `{"jsonrpc": "2.0", "id": 1, "result": 123}`) 906 c, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 907 require.NoError(t, err) 908 909 // Check client is working. 910 _, err = c.GetBlockCount() 911 require.NoError(t, err) 912 err = c.GetError() 913 require.NoError(t, err) 914 915 c.Close() 916 err = c.GetError() 917 require.NoError(t, err) 918 }) 919 920 t.Run("malformed request", func(t *testing.T) { 921 srv := initTestServer(t, "") 922 c, err := NewWS(context.TODO(), httpURLtoWS(srv.URL), WSOptions{}) 923 require.NoError(t, err) 924 925 defaultMaxBlockSize := 262144 926 _, err = c.SubmitP2PNotaryRequest(&payload.P2PNotaryRequest{ 927 MainTransaction: &transaction.Transaction{ 928 Script: make([]byte, defaultMaxBlockSize*3), 929 }, 930 FallbackTransaction: &transaction.Transaction{}, 931 }) 932 require.Error(t, err) 933 934 err = c.GetError() 935 require.Error(t, err) 936 require.True(t, strings.Contains(err.Error(), "failed to read JSON response (timeout/connection loss/malformed response)"), err.Error()) 937 }) 938 }