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  }