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, &params)
   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  }