github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/rpcclient/wsclient.go (about)

     1  package rpcclient
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"strconv"
     9  	"sync"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/gorilla/websocket"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/block"
    15  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    16  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    17  	"github.com/nspcc-dev/neo-go/pkg/neorpc"
    18  	"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
    19  	"github.com/nspcc-dev/neo-go/pkg/neorpc/rpcevent"
    20  )
    21  
    22  // WSClient is a websocket-enabled RPC client that can be used with appropriate
    23  // servers. It's supposed to be faster than Client because it has persistent
    24  // connection to the server and at the same time it exposes some functionality
    25  // that is only provided via websockets (like event subscription mechanism).
    26  // WSClient is thread-safe and can be used from multiple goroutines to perform
    27  // RPC requests.
    28  //
    29  // It exposes a set of Receive* methods with the same behaviour pattern that
    30  // is caused by the fact that the client itself receives every message from the
    31  // server via a single channel. This includes any subscriptions and any replies
    32  // to ordinary requests at the same. The client then routes these messages to
    33  // channels provided on subscription (passed to Receive*) or to the respective
    34  // receivers (API callers) if it's an ordinary JSON-RPC reply. While synchronous
    35  // API users are blocked during their calls and wake up on reply, subscription
    36  // channels must be read from to avoid blocking the client. Failure to do so
    37  // will make WSClient wait for the channel reader to get the event and while
    38  // it waits every other messages (subscription-related or request replies)
    39  // will be blocked. This also means that subscription channel must be properly
    40  // drained after unsubscription. If CloseNotificationChannelIfFull option is on
    41  // then the receiver channel will be closed immediately in case if a subsequent
    42  // notification can't be sent to it, which means WSClient's operations are
    43  // unblocking in this mode. No unsubscription is performed in this case, so it's
    44  // still the user responsibility to unsubscribe.
    45  //
    46  // All Receive* methods provide notifications ordering and persistence guarantees.
    47  // See https://github.com/nspcc-dev/neo-go/blob/master/docs/notifications.md#ordering-and-persistence-guarantees
    48  // for more details on this topic.
    49  //
    50  // Any received subscription items (blocks/transactions/nofitications) are passed
    51  // via pointers for efficiency, but the actual structures MUST NOT be changed, as
    52  // it may affect the functionality of other notification receivers. If multiple
    53  // subscriptions share the same receiver channel, then matching notification is
    54  // only sent once per channel. The receiver channel will be closed by the WSClient
    55  // immediately after MissedEvent is received from the server; no unsubscription
    56  // is performed in this case, so it's the user responsibility to unsubscribe. It
    57  // will also be closed on disconnection from server or on situation when it's
    58  // impossible to send a subsequent notification to the subscriber's channel and
    59  // CloseNotificationChannelIfFull option is on.
    60  type WSClient struct {
    61  	Client
    62  
    63  	ws          *websocket.Conn
    64  	wsOpts      WSOptions
    65  	readerDone  chan struct{}
    66  	writerDone  chan struct{}
    67  	requests    chan *neorpc.Request
    68  	shutdown    chan struct{}
    69  	closeCalled atomic.Bool
    70  
    71  	closeErrLock sync.RWMutex
    72  	closeErr     error
    73  
    74  	subscriptionsLock sync.RWMutex
    75  	subscriptions     map[string]notificationReceiver
    76  	// receivers is a mapping from receiver channel to a set of corresponding subscription IDs.
    77  	// It must be accessed with subscriptionsLock taken. Its keys must be used to deliver
    78  	// notifications, if channel is not in the receivers list and corresponding subscription
    79  	// still exists, notification must not be sent.
    80  	receivers map[any][]string
    81  
    82  	respLock     sync.RWMutex
    83  	respChannels map[uint64]chan *neorpc.Response
    84  }
    85  
    86  // WSOptions defines options for the web-socket RPC client. It contains a
    87  // set of options for the underlying standard RPC client as far as
    88  // WSClient-specific options. See Options documentation for more details.
    89  type WSOptions struct {
    90  	Options
    91  	// CloseNotificationChannelIfFull allows WSClient to close a subscriber's
    92  	// receive channel in case if the channel isn't read properly and no more
    93  	// events can be pushed to it. This option, if set, allows to avoid WSClient
    94  	// blocking on a subsequent notification dispatch. However, if enabled, the
    95  	// corresponding subscription is kept even after receiver's channel closing,
    96  	// thus it's still the caller's duty to call Unsubscribe() for this
    97  	// subscription.
    98  	CloseNotificationChannelIfFull bool
    99  }
   100  
   101  // notificationReceiver is an interface aimed to provide WS subscriber functionality
   102  // for different types of subscriptions.
   103  type notificationReceiver interface {
   104  	// Comparator provides notification filtering functionality.
   105  	rpcevent.Comparator
   106  	// Receiver returns notification receiver channel.
   107  	Receiver() any
   108  	// TrySend checks whether notification passes receiver filter and sends it
   109  	// to the underlying channel if so. It is performed under subscriptions lock
   110  	// taken. nonBlocking denotes whether the receiving operation shouldn't block
   111  	// the client's operation. It returns whether notification matches the filter
   112  	// and whether the receiver channel is overflown.
   113  	TrySend(ntf Notification, nonBlocking bool) (bool, bool)
   114  	// Close closes underlying receiver channel.
   115  	Close()
   116  }
   117  
   118  // blockReceiver stores information about block events subscriber.
   119  type blockReceiver struct {
   120  	filter *neorpc.BlockFilter
   121  	ch     chan<- *block.Block
   122  }
   123  
   124  // EventID implements neorpc.Comparator interface.
   125  func (r *blockReceiver) EventID() neorpc.EventID {
   126  	return neorpc.BlockEventID
   127  }
   128  
   129  // Filter implements neorpc.Comparator interface.
   130  func (r *blockReceiver) Filter() neorpc.SubscriptionFilter {
   131  	if r.filter == nil {
   132  		return nil
   133  	}
   134  	return *r.filter
   135  }
   136  
   137  // Receiver implements notificationReceiver interface.
   138  func (r *blockReceiver) Receiver() any {
   139  	return r.ch
   140  }
   141  
   142  // TrySend implements notificationReceiver interface.
   143  func (r *blockReceiver) TrySend(ntf Notification, nonBlocking bool) (bool, bool) {
   144  	if rpcevent.Matches(r, ntf) {
   145  		if nonBlocking {
   146  			select {
   147  			case r.ch <- ntf.Value.(*block.Block):
   148  			default:
   149  				return true, true
   150  			}
   151  		} else {
   152  			r.ch <- ntf.Value.(*block.Block)
   153  		}
   154  
   155  		return true, false
   156  	}
   157  	return false, false
   158  }
   159  
   160  // Close implements notificationReceiver interface.
   161  func (r *blockReceiver) Close() {
   162  	close(r.ch)
   163  }
   164  
   165  // headerOfAddedBlockReceiver stores information about header of added block events subscriber.
   166  type headerOfAddedBlockReceiver struct {
   167  	filter *neorpc.BlockFilter
   168  	ch     chan<- *block.Header
   169  }
   170  
   171  // EventID implements neorpc.Comparator interface.
   172  func (r *headerOfAddedBlockReceiver) EventID() neorpc.EventID {
   173  	return neorpc.HeaderOfAddedBlockEventID
   174  }
   175  
   176  // Filter implements neorpc.Comparator interface.
   177  func (r *headerOfAddedBlockReceiver) Filter() neorpc.SubscriptionFilter {
   178  	if r.filter == nil {
   179  		return nil
   180  	}
   181  	return *r.filter
   182  }
   183  
   184  // Receiver implements notificationReceiver interface.
   185  func (r *headerOfAddedBlockReceiver) Receiver() any {
   186  	return r.ch
   187  }
   188  
   189  // TrySend implements notificationReceiver interface.
   190  func (r *headerOfAddedBlockReceiver) TrySend(ntf Notification, nonBlocking bool) (bool, bool) {
   191  	if rpcevent.Matches(r, ntf) {
   192  		if nonBlocking {
   193  			select {
   194  			case r.ch <- ntf.Value.(*block.Header):
   195  			default:
   196  				return true, true
   197  			}
   198  		} else {
   199  			r.ch <- ntf.Value.(*block.Header)
   200  		}
   201  		return true, false
   202  	}
   203  	return false, false
   204  }
   205  
   206  // Close implements notificationReceiver interface.
   207  func (r *headerOfAddedBlockReceiver) Close() {
   208  	close(r.ch)
   209  }
   210  
   211  // txReceiver stores information about transaction events subscriber.
   212  type txReceiver struct {
   213  	filter *neorpc.TxFilter
   214  	ch     chan<- *transaction.Transaction
   215  }
   216  
   217  // EventID implements neorpc.Comparator interface.
   218  func (r *txReceiver) EventID() neorpc.EventID {
   219  	return neorpc.TransactionEventID
   220  }
   221  
   222  // Filter implements neorpc.Comparator interface.
   223  func (r *txReceiver) Filter() neorpc.SubscriptionFilter {
   224  	if r.filter == nil {
   225  		return nil
   226  	}
   227  	return *r.filter
   228  }
   229  
   230  // Receiver implements notificationReceiver interface.
   231  func (r *txReceiver) Receiver() any {
   232  	return r.ch
   233  }
   234  
   235  // TrySend implements notificationReceiver interface.
   236  func (r *txReceiver) TrySend(ntf Notification, nonBlocking bool) (bool, bool) {
   237  	if rpcevent.Matches(r, ntf) {
   238  		if nonBlocking {
   239  			select {
   240  			case r.ch <- ntf.Value.(*transaction.Transaction):
   241  			default:
   242  				return true, true
   243  			}
   244  		} else {
   245  			r.ch <- ntf.Value.(*transaction.Transaction)
   246  		}
   247  
   248  		return true, false
   249  	}
   250  	return false, false
   251  }
   252  
   253  // Close implements notificationReceiver interface.
   254  func (r *txReceiver) Close() {
   255  	close(r.ch)
   256  }
   257  
   258  // executionNotificationReceiver stores information about execution notifications subscriber.
   259  type executionNotificationReceiver struct {
   260  	filter *neorpc.NotificationFilter
   261  	ch     chan<- *state.ContainedNotificationEvent
   262  }
   263  
   264  // EventID implements neorpc.Comparator interface.
   265  func (r *executionNotificationReceiver) EventID() neorpc.EventID {
   266  	return neorpc.NotificationEventID
   267  }
   268  
   269  // Filter implements neorpc.Comparator interface.
   270  func (r *executionNotificationReceiver) Filter() neorpc.SubscriptionFilter {
   271  	if r.filter == nil {
   272  		return nil
   273  	}
   274  	return *r.filter
   275  }
   276  
   277  // Receiver implements notificationReceiver interface.
   278  func (r *executionNotificationReceiver) Receiver() any {
   279  	return r.ch
   280  }
   281  
   282  // TrySend implements notificationReceiver interface.
   283  func (r *executionNotificationReceiver) TrySend(ntf Notification, nonBlocking bool) (bool, bool) {
   284  	if rpcevent.Matches(r, ntf) {
   285  		if nonBlocking {
   286  			select {
   287  			case r.ch <- ntf.Value.(*state.ContainedNotificationEvent):
   288  			default:
   289  				return true, true
   290  			}
   291  		} else {
   292  			r.ch <- ntf.Value.(*state.ContainedNotificationEvent)
   293  		}
   294  
   295  		return true, false
   296  	}
   297  	return false, false
   298  }
   299  
   300  // Close implements notificationReceiver interface.
   301  func (r *executionNotificationReceiver) Close() {
   302  	close(r.ch)
   303  }
   304  
   305  // executionReceiver stores information about application execution results subscriber.
   306  type executionReceiver struct {
   307  	filter *neorpc.ExecutionFilter
   308  	ch     chan<- *state.AppExecResult
   309  }
   310  
   311  // EventID implements neorpc.Comparator interface.
   312  func (r *executionReceiver) EventID() neorpc.EventID {
   313  	return neorpc.ExecutionEventID
   314  }
   315  
   316  // Filter implements neorpc.Comparator interface.
   317  func (r *executionReceiver) Filter() neorpc.SubscriptionFilter {
   318  	if r.filter == nil {
   319  		return nil
   320  	}
   321  	return *r.filter
   322  }
   323  
   324  // Receiver implements notificationReceiver interface.
   325  func (r *executionReceiver) Receiver() any {
   326  	return r.ch
   327  }
   328  
   329  // TrySend implements notificationReceiver interface.
   330  func (r *executionReceiver) TrySend(ntf Notification, nonBlocking bool) (bool, bool) {
   331  	if rpcevent.Matches(r, ntf) {
   332  		if nonBlocking {
   333  			select {
   334  			case r.ch <- ntf.Value.(*state.AppExecResult):
   335  			default:
   336  				return true, true
   337  			}
   338  		} else {
   339  			r.ch <- ntf.Value.(*state.AppExecResult)
   340  		}
   341  
   342  		return true, false
   343  	}
   344  	return false, false
   345  }
   346  
   347  // Close implements notificationReceiver interface.
   348  func (r *executionReceiver) Close() {
   349  	close(r.ch)
   350  }
   351  
   352  // notaryRequestReceiver stores information about notary requests subscriber.
   353  type notaryRequestReceiver struct {
   354  	filter *neorpc.NotaryRequestFilter
   355  	ch     chan<- *result.NotaryRequestEvent
   356  }
   357  
   358  // EventID implements neorpc.Comparator interface.
   359  func (r *notaryRequestReceiver) EventID() neorpc.EventID {
   360  	return neorpc.NotaryRequestEventID
   361  }
   362  
   363  // Filter implements neorpc.Comparator interface.
   364  func (r *notaryRequestReceiver) Filter() neorpc.SubscriptionFilter {
   365  	if r.filter == nil {
   366  		return nil
   367  	}
   368  	return *r.filter
   369  }
   370  
   371  // Receiver implements notificationReceiver interface.
   372  func (r *notaryRequestReceiver) Receiver() any {
   373  	return r.ch
   374  }
   375  
   376  // TrySend implements notificationReceiver interface.
   377  func (r *notaryRequestReceiver) TrySend(ntf Notification, nonBlocking bool) (bool, bool) {
   378  	if rpcevent.Matches(r, ntf) {
   379  		if nonBlocking {
   380  			select {
   381  			case r.ch <- ntf.Value.(*result.NotaryRequestEvent):
   382  			default:
   383  				return true, true
   384  			}
   385  		} else {
   386  			r.ch <- ntf.Value.(*result.NotaryRequestEvent)
   387  		}
   388  
   389  		return true, false
   390  	}
   391  	return false, false
   392  }
   393  
   394  // Close implements notificationReceiver interface.
   395  func (r *notaryRequestReceiver) Close() {
   396  	close(r.ch)
   397  }
   398  
   399  // Notification represents a server-generated notification for client subscriptions.
   400  // Value can be one of *block.Block, *state.AppExecResult, *state.ContainedNotificationEvent
   401  // *transaction.Transaction or *subscriptions.NotaryRequestEvent based on Type.
   402  type Notification struct {
   403  	Type  neorpc.EventID
   404  	Value any
   405  }
   406  
   407  // EventID implements Container interface and returns notification ID.
   408  func (n Notification) EventID() neorpc.EventID {
   409  	return n.Type
   410  }
   411  
   412  // EventPayload implements Container interface and returns notification
   413  // object.
   414  func (n Notification) EventPayload() any {
   415  	return n.Value
   416  }
   417  
   418  // requestResponse is a combined type for request and response since we can get
   419  // any of them here.
   420  type requestResponse struct {
   421  	neorpc.Response
   422  	Method    string            `json:"method"`
   423  	RawParams []json.RawMessage `json:"params,omitempty"`
   424  }
   425  
   426  const (
   427  	// Message limit for receiving side.
   428  	wsReadLimit = 10 * 1024 * 1024
   429  
   430  	// Disconnection timeout.
   431  	wsPongLimit = 60 * time.Second
   432  
   433  	// Ping period for connection liveness check.
   434  	wsPingPeriod = wsPongLimit / 2
   435  
   436  	// Write deadline.
   437  	wsWriteLimit = wsPingPeriod / 2
   438  )
   439  
   440  // ErrNilNotificationReceiver is returned when notification receiver channel is nil.
   441  var ErrNilNotificationReceiver = errors.New("nil notification receiver")
   442  
   443  // ErrWSConnLost is a WSClient-specific error that will be returned for any
   444  // requests after disconnection (including intentional ones via
   445  // (*WSClient).Close).
   446  var ErrWSConnLost = errors.New("connection lost")
   447  
   448  // errConnClosedByUser is a WSClient error used iff the user calls (*WSClient).Close method by himself.
   449  var errConnClosedByUser = errors.New("connection closed by user")
   450  
   451  // NewWS returns a new WSClient ready to use (with established websocket
   452  // connection). You need to use websocket URL for it like `ws://1.2.3.4/ws`.
   453  // You should call Init method to initialize the network magic the client is
   454  // operating on.
   455  func NewWS(ctx context.Context, endpoint string, opts WSOptions) (*WSClient, error) {
   456  	dialer := websocket.Dialer{HandshakeTimeout: opts.DialTimeout}
   457  	ws, resp, err := dialer.DialContext(ctx, endpoint, nil)
   458  	if resp != nil && resp.Body != nil { // Can be non-nil even with error returned.
   459  		defer resp.Body.Close() // Not exactly required by websocket, but let's do this for bodyclose checker.
   460  	}
   461  	if err != nil {
   462  		if resp != nil && resp.Body != nil {
   463  			var srvErr neorpc.HeaderAndError
   464  
   465  			dec := json.NewDecoder(resp.Body)
   466  			decErr := dec.Decode(&srvErr)
   467  			if decErr == nil && srvErr.Error != nil {
   468  				err = srvErr.Error
   469  			}
   470  		}
   471  		return nil, err
   472  	}
   473  	wsc := &WSClient{
   474  		Client: Client{},
   475  
   476  		ws:            ws,
   477  		wsOpts:        opts,
   478  		shutdown:      make(chan struct{}),
   479  		readerDone:    make(chan struct{}),
   480  		writerDone:    make(chan struct{}),
   481  		respChannels:  make(map[uint64]chan *neorpc.Response),
   482  		requests:      make(chan *neorpc.Request),
   483  		subscriptions: make(map[string]notificationReceiver),
   484  		receivers:     make(map[any][]string),
   485  	}
   486  
   487  	err = initClient(ctx, &wsc.Client, endpoint, opts.Options)
   488  	if err != nil {
   489  		return nil, err
   490  	}
   491  	wsc.Client.cli = nil
   492  
   493  	go wsc.wsReader()
   494  	go wsc.wsWriter()
   495  	wsc.requestF = wsc.makeWsRequest
   496  	return wsc, nil
   497  }
   498  
   499  // Close closes connection to the remote side rendering this client instance
   500  // unusable.
   501  func (c *WSClient) Close() {
   502  	if c.closeCalled.CompareAndSwap(false, true) {
   503  		c.setCloseErr(errConnClosedByUser)
   504  		// Closing shutdown channel sends a signal to wsWriter to break out of the
   505  		// loop. In doing so it does ws.Close() closing the network connection
   506  		// which in turn makes wsReader receive an err from ws.ReadJSON() and also
   507  		// break out of the loop closing c.done channel in its shutdown sequence.
   508  		close(c.shutdown)
   509  		// Call to cancel will send signal to all users of Context().
   510  		c.Client.ctxCancel()
   511  	}
   512  	<-c.readerDone
   513  }
   514  
   515  func (c *WSClient) wsReader() {
   516  	c.ws.SetReadLimit(wsReadLimit)
   517  	c.ws.SetPongHandler(func(string) error {
   518  		err := c.ws.SetReadDeadline(time.Now().Add(wsPongLimit))
   519  		if err != nil {
   520  			c.setCloseErr(fmt.Errorf("failed to set pong read deadline: %w", err))
   521  		}
   522  		return err
   523  	})
   524  	var connCloseErr error
   525  readloop:
   526  	for {
   527  		rr := new(requestResponse)
   528  		err := c.ws.SetReadDeadline(time.Now().Add(wsPongLimit))
   529  		if err != nil {
   530  			connCloseErr = fmt.Errorf("failed to set response read deadline: %w", err)
   531  			break readloop
   532  		}
   533  		err = c.ws.ReadJSON(rr)
   534  		if err != nil {
   535  			// Timeout/connection loss/malformed response.
   536  			connCloseErr = fmt.Errorf("failed to read JSON response (timeout/connection loss/malformed response): %w", err)
   537  			break readloop
   538  		}
   539  		if rr.ID == nil && rr.Method != "" {
   540  			event, err := neorpc.GetEventIDFromString(rr.Method)
   541  			if err != nil {
   542  				// Bad event received.
   543  				connCloseErr = fmt.Errorf("failed to perse event ID from string %s: %w", rr.Method, err)
   544  				break readloop
   545  			}
   546  			if event != neorpc.MissedEventID && len(rr.RawParams) != 1 {
   547  				// Bad event received.
   548  				connCloseErr = fmt.Errorf("bad event received: %s / %d", event, len(rr.RawParams))
   549  				break readloop
   550  			}
   551  			ntf := Notification{Type: event}
   552  			switch event {
   553  			case neorpc.BlockEventID:
   554  				sr, err := c.stateRootInHeader()
   555  				if err != nil {
   556  					// Client is not initialized.
   557  					connCloseErr = fmt.Errorf("failed to fetch StateRootInHeader: %w", err)
   558  					break readloop
   559  				}
   560  				ntf.Value = block.New(sr)
   561  			case neorpc.TransactionEventID:
   562  				ntf.Value = &transaction.Transaction{}
   563  			case neorpc.NotificationEventID:
   564  				ntf.Value = new(state.ContainedNotificationEvent)
   565  			case neorpc.ExecutionEventID:
   566  				ntf.Value = new(state.AppExecResult)
   567  			case neorpc.NotaryRequestEventID:
   568  				ntf.Value = new(result.NotaryRequestEvent)
   569  			case neorpc.HeaderOfAddedBlockEventID:
   570  				sr, err := c.stateRootInHeader()
   571  				if err != nil {
   572  					// Client is not initialized.
   573  					connCloseErr = fmt.Errorf("failed to fetch StateRootInHeader: %w", err)
   574  					break readloop
   575  				}
   576  				ntf.Value = &block.New(sr).Header
   577  			case neorpc.MissedEventID:
   578  				// No value.
   579  			default:
   580  				// Bad event received.
   581  				connCloseErr = fmt.Errorf("unknown event received: %d", event)
   582  				break readloop
   583  			}
   584  			if event != neorpc.MissedEventID {
   585  				err = json.Unmarshal(rr.RawParams[0], ntf.Value)
   586  				if err != nil {
   587  					// Bad event received.
   588  					connCloseErr = fmt.Errorf("failed to unmarshal event of type %s from JSON: %w", event, err)
   589  					break readloop
   590  				}
   591  			}
   592  			c.notifySubscribers(ntf)
   593  		} else if rr.ID != nil && (rr.Error != nil || rr.Result != nil) {
   594  			id, err := strconv.ParseUint(string(rr.ID), 10, 64)
   595  			if err != nil {
   596  				connCloseErr = fmt.Errorf("failed to retrieve response ID from string %s: %w", string(rr.ID), err)
   597  				break readloop // Malformed response (invalid response ID).
   598  			}
   599  			ch := c.getResponseChannel(id)
   600  			if ch == nil {
   601  				connCloseErr = fmt.Errorf("unknown response channel for response %d", id)
   602  				break readloop // Unknown response (unexpected response ID).
   603  			}
   604  			ch <- &rr.Response
   605  		} else {
   606  			// Malformed response, neither valid request, nor valid response.
   607  			connCloseErr = fmt.Errorf("malformed response")
   608  			break readloop
   609  		}
   610  	}
   611  	if connCloseErr != nil {
   612  		c.setCloseErr(connCloseErr)
   613  	}
   614  	close(c.readerDone)
   615  	c.respLock.Lock()
   616  	for _, ch := range c.respChannels {
   617  		close(ch)
   618  	}
   619  	c.respChannels = nil
   620  	c.respLock.Unlock()
   621  	c.subscriptionsLock.Lock()
   622  	for rcvrCh, ids := range c.receivers {
   623  		c.dropSubCh(rcvrCh, ids[0], true)
   624  	}
   625  	c.subscriptionsLock.Unlock()
   626  	c.Client.ctxCancel()
   627  }
   628  
   629  // dropSubCh closes corresponding subscriber's channel and removes it from the
   630  // receivers map. The channel is still being kept in
   631  // the subscribers map as technically the server-side subscription still exists
   632  // and the user is responsible for unsubscription. This method must be called
   633  // under subscriptionsLock taken. It's the caller's duty to ensure dropSubCh
   634  // will be called once per channel, otherwise panic will occur.
   635  func (c *WSClient) dropSubCh(rcvrCh any, id string, ignoreCloseNotificationChannelIfFull bool) {
   636  	if ignoreCloseNotificationChannelIfFull || c.wsOpts.CloseNotificationChannelIfFull {
   637  		c.subscriptions[id].Close()
   638  		delete(c.receivers, rcvrCh)
   639  	}
   640  }
   641  
   642  func (c *WSClient) wsWriter() {
   643  	pingTicker := time.NewTicker(wsPingPeriod)
   644  	defer c.ws.Close()
   645  	defer pingTicker.Stop()
   646  	defer close(c.writerDone)
   647  	var connCloseErr error
   648  writeloop:
   649  	for {
   650  		select {
   651  		case <-c.shutdown:
   652  			return
   653  		case <-c.readerDone:
   654  			return
   655  		case req, ok := <-c.requests:
   656  			if !ok {
   657  				return
   658  			}
   659  			if err := c.ws.SetWriteDeadline(time.Now().Add(c.opts.RequestTimeout)); err != nil {
   660  				connCloseErr = fmt.Errorf("failed to set request write deadline: %w", err)
   661  				break writeloop
   662  			}
   663  			if err := c.ws.WriteJSON(req); err != nil {
   664  				connCloseErr = fmt.Errorf("failed to write JSON request (%s / %d): %w", req.Method, len(req.Params), err)
   665  				break writeloop
   666  			}
   667  		case <-pingTicker.C:
   668  			if err := c.ws.SetWriteDeadline(time.Now().Add(wsWriteLimit)); err != nil {
   669  				connCloseErr = fmt.Errorf("failed to set ping write deadline: %w", err)
   670  				break writeloop
   671  			}
   672  			if err := c.ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
   673  				connCloseErr = fmt.Errorf("failed to write ping message: %w", err)
   674  				break writeloop
   675  			}
   676  		}
   677  	}
   678  	if connCloseErr != nil {
   679  		c.setCloseErr(connCloseErr)
   680  	}
   681  }
   682  
   683  func (c *WSClient) notifySubscribers(ntf Notification) {
   684  	if ntf.Type == neorpc.MissedEventID {
   685  		c.subscriptionsLock.Lock()
   686  		for rcvr, ids := range c.receivers {
   687  			c.subscriptions[ids[0]].Close()
   688  			delete(c.receivers, rcvr)
   689  		}
   690  		c.subscriptionsLock.Unlock()
   691  		return
   692  	}
   693  	c.subscriptionsLock.Lock()
   694  	for rcvrCh, ids := range c.receivers {
   695  		for _, id := range ids {
   696  			ok, dropCh := c.subscriptions[id].TrySend(ntf, c.wsOpts.CloseNotificationChannelIfFull)
   697  			if dropCh {
   698  				c.dropSubCh(rcvrCh, id, false)
   699  				break // strictly single drop per channel
   700  			}
   701  			if ok {
   702  				break // strictly one notification per channel
   703  			}
   704  		}
   705  	}
   706  	c.subscriptionsLock.Unlock()
   707  }
   708  
   709  func (c *WSClient) unregisterRespChannel(id uint64) {
   710  	c.respLock.Lock()
   711  	defer c.respLock.Unlock()
   712  	if ch, ok := c.respChannels[id]; ok {
   713  		delete(c.respChannels, id)
   714  		close(ch)
   715  	}
   716  }
   717  
   718  func (c *WSClient) getResponseChannel(id uint64) chan *neorpc.Response {
   719  	c.respLock.RLock()
   720  	defer c.respLock.RUnlock()
   721  	return c.respChannels[id]
   722  }
   723  
   724  // closeErrOrConnLost returns the error that may occur either in wsReader or wsWriter.
   725  // If wsReader or wsWriter do not set the error, it returns ErrWSConnLost.
   726  func (c *WSClient) closeErrOrConnLost() (err error) {
   727  	err = ErrWSConnLost
   728  	if closeErr := c.GetError(); closeErr != nil {
   729  		err = closeErr
   730  	}
   731  	return
   732  }
   733  
   734  func (c *WSClient) makeWsRequest(r *neorpc.Request) (*neorpc.Response, error) {
   735  	ch := make(chan *neorpc.Response)
   736  	c.respLock.Lock()
   737  	select {
   738  	case <-c.readerDone:
   739  		c.respLock.Unlock()
   740  		return nil, fmt.Errorf("%w: before registering response channel", c.closeErrOrConnLost())
   741  	default:
   742  		c.respChannels[r.ID] = ch
   743  		c.respLock.Unlock()
   744  	}
   745  	select {
   746  	case <-c.readerDone:
   747  		return nil, fmt.Errorf("%w: before sending the request", c.closeErrOrConnLost())
   748  	case <-c.writerDone:
   749  		return nil, fmt.Errorf("%w: before sending the request", c.closeErrOrConnLost())
   750  	case c.requests <- r:
   751  	}
   752  	select {
   753  	case <-c.readerDone:
   754  		return nil, fmt.Errorf("%w: while waiting for the response", c.closeErrOrConnLost())
   755  	case <-c.writerDone:
   756  		return nil, fmt.Errorf("%w: while waiting for the response", c.closeErrOrConnLost())
   757  	case resp, ok := <-ch:
   758  		if !ok {
   759  			return nil, fmt.Errorf("%w: while waiting for the response", c.closeErrOrConnLost())
   760  		}
   761  		c.unregisterRespChannel(r.ID)
   762  		return resp, nil
   763  	}
   764  }
   765  
   766  func (c *WSClient) performSubscription(params []any, rcvr notificationReceiver) (string, error) {
   767  	var resp string
   768  
   769  	if flt := rcvr.Filter(); flt != nil {
   770  		if err := flt.IsValid(); err != nil {
   771  			return "", err
   772  		}
   773  	}
   774  	if err := c.performRequest("subscribe", params, &resp); err != nil {
   775  		return "", err
   776  	}
   777  
   778  	c.subscriptionsLock.Lock()
   779  	defer c.subscriptionsLock.Unlock()
   780  
   781  	c.subscriptions[resp] = rcvr
   782  	ch := rcvr.Receiver()
   783  	c.receivers[ch] = append(c.receivers[ch], resp)
   784  	return resp, nil
   785  }
   786  
   787  // ReceiveBlocks registers provided channel as a receiver for the new block events.
   788  // Events can be filtered by the given BlockFilter, nil value doesn't add any filter.
   789  // See WSClient comments for generic Receive* behaviour details.
   790  func (c *WSClient) ReceiveBlocks(flt *neorpc.BlockFilter, rcvr chan<- *block.Block) (string, error) {
   791  	if rcvr == nil {
   792  		return "", ErrNilNotificationReceiver
   793  	}
   794  	if !c.cache.initDone {
   795  		return "", errNetworkNotInitialized
   796  	}
   797  	params := []any{"block_added"}
   798  	if flt != nil {
   799  		flt = flt.Copy()
   800  		params = append(params, *flt)
   801  	}
   802  	r := &blockReceiver{
   803  		filter: flt,
   804  		ch:     rcvr,
   805  	}
   806  	return c.performSubscription(params, r)
   807  }
   808  
   809  // ReceiveHeadersOfAddedBlocks registers provided channel as a receiver for new
   810  // block's header events. Events can be filtered by the given [neorpc.BlockFilter],
   811  // nil value doesn't add any filter. See WSClient comments for generic
   812  // Receive* behaviour details.
   813  func (c *WSClient) ReceiveHeadersOfAddedBlocks(flt *neorpc.BlockFilter, rcvr chan<- *block.Header) (string, error) {
   814  	if rcvr == nil {
   815  		return "", ErrNilNotificationReceiver
   816  	}
   817  	if !c.cache.initDone {
   818  		return "", errNetworkNotInitialized
   819  	}
   820  	params := []any{"header_of_added_block"}
   821  	if flt != nil {
   822  		flt = flt.Copy()
   823  		params = append(params, *flt)
   824  	}
   825  	r := &headerOfAddedBlockReceiver{
   826  		filter: flt,
   827  		ch:     rcvr,
   828  	}
   829  	return c.performSubscription(params, r)
   830  }
   831  
   832  // ReceiveTransactions registers provided channel as a receiver for new transaction
   833  // events. Events can be filtered by the given TxFilter, nil value doesn't add any
   834  // filter. See WSClient comments for generic Receive* behaviour details.
   835  func (c *WSClient) ReceiveTransactions(flt *neorpc.TxFilter, rcvr chan<- *transaction.Transaction) (string, error) {
   836  	if rcvr == nil {
   837  		return "", ErrNilNotificationReceiver
   838  	}
   839  	params := []any{"transaction_added"}
   840  	if flt != nil {
   841  		flt = flt.Copy()
   842  		params = append(params, *flt)
   843  	}
   844  	r := &txReceiver{
   845  		filter: flt,
   846  		ch:     rcvr,
   847  	}
   848  	return c.performSubscription(params, r)
   849  }
   850  
   851  // ReceiveExecutionNotifications registers provided channel as a receiver for execution
   852  // events. Events can be filtered by the given NotificationFilter, nil value doesn't add
   853  // any filter. See WSClient comments for generic Receive* behaviour details.
   854  func (c *WSClient) ReceiveExecutionNotifications(flt *neorpc.NotificationFilter, rcvr chan<- *state.ContainedNotificationEvent) (string, error) {
   855  	if rcvr == nil {
   856  		return "", ErrNilNotificationReceiver
   857  	}
   858  	params := []any{"notification_from_execution"}
   859  	if flt != nil {
   860  		flt = flt.Copy()
   861  		params = append(params, *flt)
   862  	}
   863  	r := &executionNotificationReceiver{
   864  		filter: flt,
   865  		ch:     rcvr,
   866  	}
   867  	return c.performSubscription(params, r)
   868  }
   869  
   870  // ReceiveExecutions registers provided channel as a receiver for
   871  // application execution result events generated during transaction execution.
   872  // Events can be filtered by the given ExecutionFilter, nil value doesn't add any filter.
   873  // See WSClient comments for generic Receive* behaviour details.
   874  func (c *WSClient) ReceiveExecutions(flt *neorpc.ExecutionFilter, rcvr chan<- *state.AppExecResult) (string, error) {
   875  	if rcvr == nil {
   876  		return "", ErrNilNotificationReceiver
   877  	}
   878  	params := []any{"transaction_executed"}
   879  	if flt != nil {
   880  		flt = flt.Copy()
   881  		params = append(params, *flt)
   882  	}
   883  	r := &executionReceiver{
   884  		filter: flt,
   885  		ch:     rcvr,
   886  	}
   887  	return c.performSubscription(params, r)
   888  }
   889  
   890  // ReceiveNotaryRequests registers provided channel as a receiver for notary request
   891  // payload addition or removal events. Events can be filtered by the given NotaryRequestFilter
   892  // where sender corresponds to notary request sender (the second fallback transaction
   893  // signer), signer corresponds to main transaction signers and type corresponds to the
   894  // [mempoolevent.Type] and denotes whether notary request was added to or removed from
   895  // the notary request pool. nil value doesn't add any filter. See WSClient comments
   896  // for generic Receive* behaviour details.
   897  func (c *WSClient) ReceiveNotaryRequests(flt *neorpc.NotaryRequestFilter, rcvr chan<- *result.NotaryRequestEvent) (string, error) {
   898  	if rcvr == nil {
   899  		return "", ErrNilNotificationReceiver
   900  	}
   901  	params := []any{"notary_request_event"}
   902  	if flt != nil {
   903  		flt = flt.Copy()
   904  		params = append(params, *flt)
   905  	}
   906  	r := &notaryRequestReceiver{
   907  		filter: flt,
   908  		ch:     rcvr,
   909  	}
   910  	return c.performSubscription(params, r)
   911  }
   912  
   913  // Unsubscribe removes subscription for the given event stream. It will return an
   914  // error in case if there's no subscription with the provided ID. Call to Unsubscribe
   915  // doesn't block notifications receive process for given subscriber, thus, ensure
   916  // that subscriber channel is properly drained while unsubscription is being
   917  // performed. Failing to do so will cause WSClient to block even regular requests.
   918  // You may probably need to run unsubscription process in a separate
   919  // routine (in parallel with notification receiver routine) to avoid Client's
   920  // notification dispatcher blocking.
   921  func (c *WSClient) Unsubscribe(id string) error {
   922  	return c.performUnsubscription(id)
   923  }
   924  
   925  // UnsubscribeAll removes all active subscriptions of the current client. It copies
   926  // the list of subscribers in order not to hold the lock for the whole execution
   927  // time and tries to unsubscribe from us many feeds as possible returning the
   928  // chunk of unsubscription errors afterwards. Call to UnsubscribeAll doesn't block
   929  // notifications receive process for given subscribers, thus, ensure that subscribers
   930  // channels are properly drained while unsubscription is being performed. Failing to
   931  // do so will cause WSClient to block even regular requests. You may probably need
   932  // to run unsubscription process in a separate routine (in parallel with notification
   933  // receiver routines) to avoid Client's notification dispatcher blocking.
   934  func (c *WSClient) UnsubscribeAll() error {
   935  	c.subscriptionsLock.Lock()
   936  	subs := make([]string, 0, len(c.subscriptions))
   937  	for id := range c.subscriptions {
   938  		subs = append(subs, id)
   939  	}
   940  	c.subscriptionsLock.Unlock()
   941  
   942  	var resErr error
   943  	for _, id := range subs {
   944  		err := c.performUnsubscription(id)
   945  		if err != nil {
   946  			errFmt := "failed to unsubscribe from feed %d: %w"
   947  			errArgs := []any{err}
   948  			if resErr != nil {
   949  				errFmt = "%w; " + errFmt
   950  				errArgs = append([]any{resErr}, errArgs...)
   951  			}
   952  			resErr = fmt.Errorf(errFmt, errArgs...)
   953  		}
   954  	}
   955  	return resErr
   956  }
   957  
   958  // performUnsubscription is internal method that removes subscription with the given
   959  // ID from the list of subscriptions and receivers. It takes the subscriptions lock
   960  // after WS RPC unsubscription request is completed. Until then the subscriber channel
   961  // may still receive WS notifications.
   962  func (c *WSClient) performUnsubscription(id string) error {
   963  	var resp bool
   964  	if err := c.performRequest("unsubscribe", []any{id}, &resp); err != nil {
   965  		return err
   966  	}
   967  	if !resp {
   968  		return errors.New("unsubscribe method returned false result")
   969  	}
   970  
   971  	c.subscriptionsLock.Lock()
   972  	defer c.subscriptionsLock.Unlock()
   973  
   974  	rcvr, ok := c.subscriptions[id]
   975  	if !ok {
   976  		return errors.New("no subscription with this ID")
   977  	}
   978  	ch := rcvr.Receiver()
   979  	ids := c.receivers[ch]
   980  	for i, rcvrID := range ids {
   981  		if rcvrID == id {
   982  			ids = append(ids[:i], ids[i+1:]...)
   983  			break
   984  		}
   985  	}
   986  	if len(ids) == 0 {
   987  		delete(c.receivers, ch)
   988  	} else {
   989  		c.receivers[ch] = ids
   990  	}
   991  	delete(c.subscriptions, id)
   992  	return nil
   993  }
   994  
   995  // setCloseErr is a thread-safe method setting closeErr in case if it's not yet set.
   996  func (c *WSClient) setCloseErr(err error) {
   997  	c.closeErrLock.Lock()
   998  	defer c.closeErrLock.Unlock()
   999  
  1000  	if c.closeErr == nil {
  1001  		c.closeErr = err
  1002  	}
  1003  }
  1004  
  1005  // GetError returns the reason of WS connection closing. It returns nil in case if connection
  1006  // was closed by the use via Close() method calling.
  1007  func (c *WSClient) GetError() error {
  1008  	c.closeErrLock.RLock()
  1009  	defer c.closeErrLock.RUnlock()
  1010  
  1011  	if c.closeErr != nil && errors.Is(c.closeErr, errConnClosedByUser) {
  1012  		return nil
  1013  	}
  1014  	return c.closeErr
  1015  }
  1016  
  1017  // Context returns WSClient Cancel context that will be terminated on Client shutdown.
  1018  func (c *WSClient) Context() context.Context {
  1019  	return c.Client.ctx
  1020  }