github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/state_stream/backend/handler.go (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  
     6  	"google.golang.org/grpc/codes"
     7  	"google.golang.org/grpc/status"
     8  	"google.golang.org/protobuf/types/known/timestamppb"
     9  
    10  	"github.com/onflow/flow/protobuf/go/flow/entities"
    11  	"github.com/onflow/flow/protobuf/go/flow/executiondata"
    12  
    13  	"github.com/onflow/flow-go/engine/access/state_stream"
    14  	"github.com/onflow/flow-go/engine/access/subscription"
    15  	"github.com/onflow/flow-go/engine/common/rpc"
    16  	"github.com/onflow/flow-go/engine/common/rpc/convert"
    17  	"github.com/onflow/flow-go/model/flow"
    18  	"github.com/onflow/flow-go/module/counters"
    19  )
    20  
    21  type Handler struct {
    22  	subscription.StreamingData
    23  
    24  	api   state_stream.API
    25  	chain flow.Chain
    26  
    27  	eventFilterConfig        state_stream.EventFilterConfig
    28  	defaultHeartbeatInterval uint64
    29  }
    30  
    31  // sendSubscribeEventsResponseFunc is a callback function used to send
    32  // SubscribeEventsResponse to the client stream.
    33  type sendSubscribeEventsResponseFunc func(*executiondata.SubscribeEventsResponse) error
    34  
    35  // sendSubscribeExecutionDataResponseFunc is a callback function used to send
    36  // SubscribeExecutionDataResponse to the client stream.
    37  type sendSubscribeExecutionDataResponseFunc func(*executiondata.SubscribeExecutionDataResponse) error
    38  
    39  var _ executiondata.ExecutionDataAPIServer = (*Handler)(nil)
    40  
    41  func NewHandler(api state_stream.API, chain flow.Chain, config Config) *Handler {
    42  	h := &Handler{
    43  		StreamingData:            subscription.NewStreamingData(config.MaxGlobalStreams),
    44  		api:                      api,
    45  		chain:                    chain,
    46  		eventFilterConfig:        config.EventFilterConfig,
    47  		defaultHeartbeatInterval: config.HeartbeatInterval,
    48  	}
    49  	return h
    50  }
    51  
    52  func (h *Handler) GetExecutionDataByBlockID(ctx context.Context, request *executiondata.GetExecutionDataByBlockIDRequest) (*executiondata.GetExecutionDataByBlockIDResponse, error) {
    53  	blockID, err := convert.BlockID(request.GetBlockId())
    54  	if err != nil {
    55  		return nil, status.Errorf(codes.InvalidArgument, "could not convert block ID: %v", err)
    56  	}
    57  
    58  	execData, err := h.api.GetExecutionDataByBlockID(ctx, blockID)
    59  	if err != nil {
    60  		return nil, rpc.ConvertError(err, "could no get execution data", codes.Internal)
    61  	}
    62  
    63  	message, err := convert.BlockExecutionDataToMessage(execData)
    64  	if err != nil {
    65  		return nil, status.Errorf(codes.Internal, "could not convert execution data to entity: %v", err)
    66  	}
    67  
    68  	err = convert.BlockExecutionDataEventPayloadsToVersion(message, request.GetEventEncodingVersion())
    69  	if err != nil {
    70  		return nil, status.Errorf(codes.Internal, "could not convert execution data event payloads to JSON: %v", err)
    71  	}
    72  
    73  	return &executiondata.GetExecutionDataByBlockIDResponse{BlockExecutionData: message}, nil
    74  }
    75  
    76  // SubscribeExecutionData is deprecated and will be removed in a future version.
    77  // Use SubscribeExecutionDataFromStartBlockID, SubscribeExecutionDataFromStartBlockHeight or SubscribeExecutionDataFromLatest.
    78  //
    79  // SubscribeExecutionData handles subscription requests for execution data starting at the specified block ID or block height.
    80  // The handler manages the subscription and sends the subscribed information to the client via the provided stream.
    81  //
    82  // Expected errors during normal operation:
    83  // - codes.InvalidArgument   - if request contains invalid startBlockID.
    84  // - codes.ResourceExhausted - if the maximum number of streams is reached.
    85  // - codes.Internal          - if stream got unexpected response or could not send response.
    86  func (h *Handler) SubscribeExecutionData(request *executiondata.SubscribeExecutionDataRequest, stream executiondata.ExecutionDataAPI_SubscribeExecutionDataServer) error {
    87  	// check if the maximum number of streams is reached
    88  	if h.StreamCount.Load() >= h.MaxStreams {
    89  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
    90  	}
    91  	h.StreamCount.Add(1)
    92  	defer h.StreamCount.Add(-1)
    93  
    94  	startBlockID := flow.ZeroID
    95  	if request.GetStartBlockId() != nil {
    96  		blockID, err := convert.BlockID(request.GetStartBlockId())
    97  		if err != nil {
    98  			return status.Errorf(codes.InvalidArgument, "could not convert start block ID: %v", err)
    99  		}
   100  		startBlockID = blockID
   101  	}
   102  
   103  	sub := h.api.SubscribeExecutionData(stream.Context(), startBlockID, request.GetStartBlockHeight())
   104  
   105  	return subscription.HandleSubscription(sub, handleSubscribeExecutionData(stream.Send, request.GetEventEncodingVersion()))
   106  }
   107  
   108  // SubscribeExecutionDataFromStartBlockID handles subscription requests for
   109  // execution data starting at the specified block ID. The handler manages the
   110  // subscription and sends the subscribed information to the client via the
   111  // provided stream.
   112  //
   113  // Expected errors during normal operation:
   114  // - codes.InvalidArgument   - if request contains invalid startBlockID.
   115  // - codes.ResourceExhausted - if the maximum number of streams is reached.
   116  // - codes.Internal          - if stream got unexpected response or could not send response.
   117  func (h *Handler) SubscribeExecutionDataFromStartBlockID(request *executiondata.SubscribeExecutionDataFromStartBlockIDRequest, stream executiondata.ExecutionDataAPI_SubscribeExecutionDataFromStartBlockIDServer) error {
   118  	// check if the maximum number of streams is reached
   119  	if h.StreamCount.Load() >= h.MaxStreams {
   120  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   121  	}
   122  	h.StreamCount.Add(1)
   123  	defer h.StreamCount.Add(-1)
   124  
   125  	startBlockID, err := convert.BlockID(request.GetStartBlockId())
   126  	if err != nil {
   127  		return status.Errorf(codes.InvalidArgument, "could not convert start block ID: %v", err)
   128  	}
   129  
   130  	sub := h.api.SubscribeExecutionDataFromStartBlockID(stream.Context(), startBlockID)
   131  
   132  	return subscription.HandleSubscription(sub, handleSubscribeExecutionData(stream.Send, request.GetEventEncodingVersion()))
   133  }
   134  
   135  // SubscribeExecutionDataFromStartBlockHeight handles subscription requests for
   136  // execution data starting at the specified block height. The handler manages the
   137  // subscription and sends the subscribed information to the client via the
   138  // provided stream.
   139  //
   140  // Expected errors during normal operation:
   141  // - codes.ResourceExhausted - if the maximum number of streams is reached.
   142  // - codes.Internal          - if stream got unexpected response or could not send response.
   143  func (h *Handler) SubscribeExecutionDataFromStartBlockHeight(request *executiondata.SubscribeExecutionDataFromStartBlockHeightRequest, stream executiondata.ExecutionDataAPI_SubscribeExecutionDataFromStartBlockHeightServer) error {
   144  	// check if the maximum number of streams is reached
   145  	if h.StreamCount.Load() >= h.MaxStreams {
   146  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   147  	}
   148  	h.StreamCount.Add(1)
   149  	defer h.StreamCount.Add(-1)
   150  
   151  	sub := h.api.SubscribeExecutionDataFromStartBlockHeight(stream.Context(), request.GetStartBlockHeight())
   152  
   153  	return subscription.HandleSubscription(sub, handleSubscribeExecutionData(stream.Send, request.GetEventEncodingVersion()))
   154  }
   155  
   156  // SubscribeExecutionDataFromLatest handles subscription requests for
   157  // execution data starting at the latest block. The handler manages the
   158  // subscription and sends the subscribed information to the client via the
   159  // provided stream.
   160  //
   161  // Expected errors during normal operation:
   162  // - codes.ResourceExhausted - if the maximum number of streams is reached.
   163  // - codes.Internal          - if stream got unexpected response or could not send response.
   164  func (h *Handler) SubscribeExecutionDataFromLatest(request *executiondata.SubscribeExecutionDataFromLatestRequest, stream executiondata.ExecutionDataAPI_SubscribeExecutionDataFromLatestServer) error {
   165  	// check if the maximum number of streams is reached
   166  	if h.StreamCount.Load() >= h.MaxStreams {
   167  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   168  	}
   169  	h.StreamCount.Add(1)
   170  	defer h.StreamCount.Add(-1)
   171  
   172  	sub := h.api.SubscribeExecutionDataFromLatest(stream.Context())
   173  
   174  	return subscription.HandleSubscription(sub, handleSubscribeExecutionData(stream.Send, request.GetEventEncodingVersion()))
   175  }
   176  
   177  // SubscribeEvents is deprecated and will be removed in a future version.
   178  // Use SubscribeEventsFromStartBlockID, SubscribeEventsFromStartHeight or SubscribeEventsFromLatest.
   179  //
   180  // SubscribeEvents handles subscription requests for events starting at the specified block ID or block height.
   181  // The handler manages the subscription and sends the subscribed information to the client via the provided stream.
   182  //
   183  // Responses are returned for each block containing at least one event that matches the filter. Additionally,
   184  // heartbeat responses (SubscribeEventsResponse with no events) are returned periodically to allow
   185  // clients to track which blocks were searched. Clients can use this
   186  // information to determine which block to start from when reconnecting.
   187  //
   188  // Expected errors during normal operation:
   189  // - codes.InvalidArgument   - if provided both startBlockID and startHeight, if invalid startBlockID is provided, if invalid event filter is provided.
   190  // - codes.ResourceExhausted - if the maximum number of streams is reached.
   191  // - codes.Internal          - could not convert events to entity, if stream encountered an error, if stream got unexpected response or could not send response.
   192  func (h *Handler) SubscribeEvents(request *executiondata.SubscribeEventsRequest, stream executiondata.ExecutionDataAPI_SubscribeEventsServer) error {
   193  	// check if the maximum number of streams is reached
   194  	if h.StreamCount.Load() >= h.MaxStreams {
   195  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   196  	}
   197  	h.StreamCount.Add(1)
   198  	defer h.StreamCount.Add(-1)
   199  
   200  	startBlockID := flow.ZeroID
   201  	if request.GetStartBlockId() != nil {
   202  		blockID, err := convert.BlockID(request.GetStartBlockId())
   203  		if err != nil {
   204  			return status.Errorf(codes.InvalidArgument, "could not convert start block ID: %v", err)
   205  		}
   206  		startBlockID = blockID
   207  	}
   208  
   209  	filter, err := h.getEventFilter(request.GetFilter())
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	sub := h.api.SubscribeEvents(stream.Context(), startBlockID, request.GetStartBlockHeight(), filter)
   215  
   216  	return subscription.HandleSubscription(sub, h.handleEventsResponse(stream.Send, request.HeartbeatInterval, request.GetEventEncodingVersion()))
   217  }
   218  
   219  // SubscribeEventsFromStartBlockID handles subscription requests for events starting at the specified block ID.
   220  // The handler manages the subscription and sends the subscribed information to the client via the provided stream.
   221  //
   222  // Responses are returned for each block containing at least one event that matches the filter. Additionally,
   223  // heartbeat responses (SubscribeEventsResponse with no events) are returned periodically to allow
   224  // clients to track which blocks were searched. Clients can use this
   225  // information to determine which block to start from when reconnecting.
   226  //
   227  // Expected errors during normal operation:
   228  // - codes.InvalidArgument   - if invalid startBlockID is provided, if invalid event filter is provided.
   229  // - codes.ResourceExhausted - if the maximum number of streams is reached.
   230  // - codes.Internal          - could not convert events to entity, if stream encountered an error, if stream got unexpected response or could not send response.
   231  func (h *Handler) SubscribeEventsFromStartBlockID(request *executiondata.SubscribeEventsFromStartBlockIDRequest, stream executiondata.ExecutionDataAPI_SubscribeEventsFromStartBlockIDServer) error {
   232  	// check if the maximum number of streams is reached
   233  	if h.StreamCount.Load() >= h.MaxStreams {
   234  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   235  	}
   236  	h.StreamCount.Add(1)
   237  	defer h.StreamCount.Add(-1)
   238  
   239  	startBlockID, err := convert.BlockID(request.GetStartBlockId())
   240  	if err != nil {
   241  		return status.Errorf(codes.InvalidArgument, "could not convert start block ID: %v", err)
   242  	}
   243  
   244  	filter, err := h.getEventFilter(request.GetFilter())
   245  	if err != nil {
   246  		return err
   247  	}
   248  
   249  	sub := h.api.SubscribeEventsFromStartBlockID(stream.Context(), startBlockID, filter)
   250  
   251  	return subscription.HandleSubscription(sub, h.handleEventsResponse(stream.Send, request.HeartbeatInterval, request.GetEventEncodingVersion()))
   252  }
   253  
   254  // SubscribeEventsFromStartHeight handles subscription requests for events starting at the specified block height.
   255  // The handler manages the subscription and sends the subscribed information to the client via the provided stream.
   256  //
   257  // Responses are returned for each block containing at least one event that matches the filter. Additionally,
   258  // heartbeat responses (SubscribeEventsResponse with no events) are returned periodically to allow
   259  // clients to track which blocks were searched. Clients can use this
   260  // information to determine which block to start from when reconnecting.
   261  //
   262  // Expected errors during normal operation:
   263  // - codes.InvalidArgument   - if invalid event filter is provided.
   264  // - codes.ResourceExhausted - if the maximum number of streams is reached.
   265  // - codes.Internal          - could not convert events to entity, if stream encountered an error, if stream got unexpected response or could not send response.
   266  func (h *Handler) SubscribeEventsFromStartHeight(request *executiondata.SubscribeEventsFromStartHeightRequest, stream executiondata.ExecutionDataAPI_SubscribeEventsFromStartHeightServer) error {
   267  	// check if the maximum number of streams is reached
   268  	if h.StreamCount.Load() >= h.MaxStreams {
   269  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   270  	}
   271  	h.StreamCount.Add(1)
   272  	defer h.StreamCount.Add(-1)
   273  
   274  	filter, err := h.getEventFilter(request.GetFilter())
   275  	if err != nil {
   276  		return err
   277  	}
   278  
   279  	sub := h.api.SubscribeEventsFromStartHeight(stream.Context(), request.GetStartBlockHeight(), filter)
   280  
   281  	return subscription.HandleSubscription(sub, h.handleEventsResponse(stream.Send, request.HeartbeatInterval, request.GetEventEncodingVersion()))
   282  }
   283  
   284  // SubscribeEventsFromLatest handles subscription requests for events started from latest sealed block..
   285  // The handler manages the subscription and sends the subscribed information to the client via the provided stream.
   286  //
   287  // Responses are returned for each block containing at least one event that matches the filter. Additionally,
   288  // heartbeat responses (SubscribeEventsResponse with no events) are returned periodically to allow
   289  // clients to track which blocks were searched. Clients can use this
   290  // information to determine which block to start from when reconnecting.
   291  //
   292  // Expected errors during normal operation:
   293  // - codes.InvalidArgument   - if invalid event filter is provided.
   294  // - codes.ResourceExhausted - if the maximum number of streams is reached.
   295  // - codes.Internal          - could not convert events to entity, if stream encountered an error, if stream got unexpected response or could not send response.
   296  func (h *Handler) SubscribeEventsFromLatest(request *executiondata.SubscribeEventsFromLatestRequest, stream executiondata.ExecutionDataAPI_SubscribeEventsFromLatestServer) error {
   297  	// check if the maximum number of streams is reached
   298  	if h.StreamCount.Load() >= h.MaxStreams {
   299  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   300  	}
   301  	h.StreamCount.Add(1)
   302  	defer h.StreamCount.Add(-1)
   303  
   304  	filter, err := h.getEventFilter(request.GetFilter())
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	sub := h.api.SubscribeEventsFromLatest(stream.Context(), filter)
   310  
   311  	return subscription.HandleSubscription(sub, h.handleEventsResponse(stream.Send, request.HeartbeatInterval, request.GetEventEncodingVersion()))
   312  }
   313  
   314  // handleSubscribeExecutionData handles the subscription to execution data and sends it to the client via the provided stream.
   315  // This function is designed to be used as a callback for execution data updates in a subscription.
   316  //
   317  // Parameters:
   318  // - send: The function responsible for sending execution data in response to the client.
   319  //
   320  // Returns a function that can be used as a callback for execution data updates.
   321  //
   322  // Expected errors during normal operation:
   323  //   - codes.Internal - could not convert execution data to entity or could not convert execution data event payloads to JSON.
   324  func handleSubscribeExecutionData(send sendSubscribeExecutionDataResponseFunc, eventEncodingVersion entities.EventEncodingVersion) func(response *ExecutionDataResponse) error {
   325  	return func(resp *ExecutionDataResponse) error {
   326  		execData, err := convert.BlockExecutionDataToMessage(resp.ExecutionData)
   327  		if err != nil {
   328  			return status.Errorf(codes.Internal, "could not convert execution data to entity: %v", err)
   329  		}
   330  
   331  		err = convert.BlockExecutionDataEventPayloadsToVersion(execData, eventEncodingVersion)
   332  		if err != nil {
   333  			return status.Errorf(codes.Internal, "could not convert execution data event payloads to JSON: %v", err)
   334  		}
   335  
   336  		err = send(&executiondata.SubscribeExecutionDataResponse{
   337  			BlockHeight:        resp.Height,
   338  			BlockExecutionData: execData,
   339  			BlockTimestamp:     timestamppb.New(resp.BlockTimestamp),
   340  		})
   341  
   342  		return err
   343  	}
   344  }
   345  
   346  // handleEventsResponse handles the event subscription and sends subscribed events to the client via the provided stream.
   347  // This function is designed to be used as a callback for events updates in a subscription.
   348  // It takes a EventsResponse, processes it, and sends the corresponding response to the client using the provided send function.
   349  //
   350  // Parameters:
   351  // - send: The function responsible for sending events response to the client.
   352  //
   353  // Returns a function that can be used as a callback for events updates.
   354  //
   355  // Expected errors during normal operation:
   356  //   - codes.Internal - could not convert events to entity or the stream could not send a response.
   357  func (h *Handler) handleEventsResponse(send sendSubscribeEventsResponseFunc, heartbeatInterval uint64, eventEncodingVersion entities.EventEncodingVersion) func(*EventsResponse) error {
   358  	if heartbeatInterval == 0 {
   359  		heartbeatInterval = h.defaultHeartbeatInterval
   360  	}
   361  
   362  	blocksSinceLastMessage := uint64(0)
   363  	messageIndex := counters.NewMonotonousCounter(0)
   364  
   365  	return func(resp *EventsResponse) error {
   366  		// check if there are any events in the response. if not, do not send a message unless the last
   367  		// response was more than HeartbeatInterval blocks ago
   368  		if len(resp.Events) == 0 {
   369  			blocksSinceLastMessage++
   370  			if blocksSinceLastMessage < heartbeatInterval {
   371  				return nil
   372  			}
   373  			blocksSinceLastMessage = 0
   374  		}
   375  
   376  		// BlockExecutionData contains CCF encoded events, and the Access API returns JSON-CDC events.
   377  		// convert event payload formats.
   378  		// This is a temporary solution until the Access API supports specifying the encoding in the request
   379  		events, err := convert.EventsToMessagesWithEncodingConversion(resp.Events, entities.EventEncodingVersion_CCF_V0, eventEncodingVersion)
   380  		if err != nil {
   381  			return status.Errorf(codes.Internal, "could not convert events to entity: %v", err)
   382  		}
   383  
   384  		index := messageIndex.Increment()
   385  
   386  		err = send(&executiondata.SubscribeEventsResponse{
   387  			BlockHeight:    resp.Height,
   388  			BlockId:        convert.IdentifierToMessage(resp.BlockID),
   389  			Events:         events,
   390  			BlockTimestamp: timestamppb.New(resp.BlockTimestamp),
   391  			MessageIndex:   index,
   392  		})
   393  		if err != nil {
   394  			return rpc.ConvertError(err, "could not send response", codes.Internal)
   395  		}
   396  
   397  		return nil
   398  	}
   399  }
   400  
   401  // getEventFilter returns an event filter based on the provided event filter configuration.
   402  // If the event filter is nil, it returns an empty filter.
   403  // Otherwise, it initializes a new event filter using the provided filter parameters,
   404  // including the event type, address, and contract. It then validates the filter configuration
   405  // and returns the constructed event filter or an error if the filter configuration is invalid.
   406  // The event filter is used for subscription to events.
   407  //
   408  // Parameters:
   409  // - eventFilter: executiondata.EventFilter object containing filter parameters.
   410  //
   411  // Expected errors during normal operation:
   412  // - codes.InvalidArgument - if the provided event filter is invalid.
   413  func (h *Handler) getEventFilter(eventFilter *executiondata.EventFilter) (state_stream.EventFilter, error) {
   414  	if eventFilter == nil {
   415  		return state_stream.EventFilter{}, nil
   416  	}
   417  	filter, err := state_stream.NewEventFilter(
   418  		h.eventFilterConfig,
   419  		h.chain,
   420  		eventFilter.GetEventType(),
   421  		eventFilter.GetAddress(),
   422  		eventFilter.GetContract(),
   423  	)
   424  	if err != nil {
   425  		return filter, status.Errorf(codes.InvalidArgument, "invalid event filter: %v", err)
   426  	}
   427  	return filter, nil
   428  }
   429  
   430  func (h *Handler) GetRegisterValues(_ context.Context, request *executiondata.GetRegisterValuesRequest) (*executiondata.GetRegisterValuesResponse, error) {
   431  	// Convert data
   432  	registerIDs, err := convert.MessagesToRegisterIDs(request.GetRegisterIds(), h.chain)
   433  	if err != nil {
   434  		return nil, status.Errorf(codes.InvalidArgument, "could not convert register IDs: %v", err)
   435  	}
   436  
   437  	// get payload from store
   438  	values, err := h.api.GetRegisterValues(registerIDs, request.GetBlockHeight())
   439  	if err != nil {
   440  		return nil, rpc.ConvertError(err, "could not get register values", codes.Internal)
   441  	}
   442  
   443  	return &executiondata.GetRegisterValuesResponse{Values: values}, nil
   444  }
   445  
   446  // convertAccountsStatusesResultsToMessage converts account status responses to the message
   447  func convertAccountsStatusesResultsToMessage(
   448  	eventVersion entities.EventEncodingVersion,
   449  	resp *AccountStatusesResponse,
   450  ) ([]*executiondata.SubscribeAccountStatusesResponse_Result, error) {
   451  	var results []*executiondata.SubscribeAccountStatusesResponse_Result
   452  	for address, events := range resp.AccountEvents {
   453  		convertedEvent, err := convert.EventsToMessagesWithEncodingConversion(events, entities.EventEncodingVersion_CCF_V0, eventVersion)
   454  		if err != nil {
   455  			return nil, status.Errorf(codes.Internal, "could not convert events to entity: %v", err)
   456  		}
   457  
   458  		results = append(results, &executiondata.SubscribeAccountStatusesResponse_Result{
   459  			Address: flow.HexToAddress(address).Bytes(),
   460  			Events:  convertedEvent,
   461  		})
   462  	}
   463  	return results, nil
   464  }
   465  
   466  // sendSubscribeAccountStatusesResponseFunc defines the function signature for sending account status responses
   467  type sendSubscribeAccountStatusesResponseFunc func(*executiondata.SubscribeAccountStatusesResponse) error
   468  
   469  // handleAccountStatusesResponse handles account status responses by converting them to the message and sending them to the subscriber.
   470  func (h *Handler) handleAccountStatusesResponse(
   471  	heartbeatInterval uint64,
   472  	evenVersion entities.EventEncodingVersion,
   473  	send sendSubscribeAccountStatusesResponseFunc,
   474  ) func(resp *AccountStatusesResponse) error {
   475  	if heartbeatInterval == 0 {
   476  		heartbeatInterval = h.defaultHeartbeatInterval
   477  	}
   478  
   479  	blocksSinceLastMessage := uint64(0)
   480  	messageIndex := counters.NewMonotonousCounter(0)
   481  
   482  	return func(resp *AccountStatusesResponse) error {
   483  		// check if there are any events in the response. if not, do not send a message unless the last
   484  		// response was more than HeartbeatInterval blocks ago
   485  		if len(resp.AccountEvents) == 0 {
   486  			blocksSinceLastMessage++
   487  			if blocksSinceLastMessage < heartbeatInterval {
   488  				return nil
   489  			}
   490  			blocksSinceLastMessage = 0
   491  		}
   492  
   493  		results, err := convertAccountsStatusesResultsToMessage(evenVersion, resp)
   494  		if err != nil {
   495  			return err
   496  		}
   497  
   498  		index := messageIndex.Increment()
   499  
   500  		err = send(&executiondata.SubscribeAccountStatusesResponse{
   501  			BlockId:      convert.IdentifierToMessage(resp.BlockID),
   502  			BlockHeight:  resp.Height,
   503  			Results:      results,
   504  			MessageIndex: index,
   505  		})
   506  		if err != nil {
   507  			return rpc.ConvertError(err, "could not send response", codes.Internal)
   508  		}
   509  
   510  		return nil
   511  	}
   512  }
   513  
   514  // SubscribeAccountStatusesFromStartBlockID streams account statuses for all blocks starting at the requested
   515  // start block ID, up until the latest available block. Once the latest is
   516  // reached, the stream will remain open and responses are sent for each new
   517  // block as it becomes available.
   518  func (h *Handler) SubscribeAccountStatusesFromStartBlockID(
   519  	request *executiondata.SubscribeAccountStatusesFromStartBlockIDRequest,
   520  	stream executiondata.ExecutionDataAPI_SubscribeAccountStatusesFromStartBlockIDServer,
   521  ) error {
   522  	// check if the maximum number of streams is reached
   523  	if h.StreamCount.Load() >= h.MaxStreams {
   524  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   525  	}
   526  
   527  	h.StreamCount.Add(1)
   528  	defer h.StreamCount.Add(-1)
   529  
   530  	startBlockID, err := convert.BlockID(request.GetStartBlockId())
   531  	if err != nil {
   532  		return status.Errorf(codes.InvalidArgument, "could not convert start block ID: %v", err)
   533  	}
   534  
   535  	statusFilter := request.GetFilter()
   536  	filter, err := state_stream.NewAccountStatusFilter(h.eventFilterConfig, h.chain, statusFilter.GetEventType(), statusFilter.GetAddress())
   537  	if err != nil {
   538  		return status.Errorf(codes.InvalidArgument, "could not create account status filter: %v", err)
   539  	}
   540  
   541  	sub := h.api.SubscribeAccountStatusesFromStartBlockID(stream.Context(), startBlockID, filter)
   542  
   543  	return subscription.HandleSubscription(sub, h.handleAccountStatusesResponse(request.HeartbeatInterval, request.GetEventEncodingVersion(), stream.Send))
   544  }
   545  
   546  // SubscribeAccountStatusesFromStartHeight streams account statuses for all blocks starting at the requested
   547  // start block height, up until the latest available block. Once the latest is
   548  // reached, the stream will remain open and responses are sent for each new
   549  // block as it becomes available.
   550  func (h *Handler) SubscribeAccountStatusesFromStartHeight(
   551  	request *executiondata.SubscribeAccountStatusesFromStartHeightRequest,
   552  	stream executiondata.ExecutionDataAPI_SubscribeAccountStatusesFromStartHeightServer,
   553  ) error {
   554  	// check if the maximum number of streams is reached
   555  	if h.StreamCount.Load() >= h.MaxStreams {
   556  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   557  	}
   558  
   559  	h.StreamCount.Add(1)
   560  	defer h.StreamCount.Add(-1)
   561  
   562  	statusFilter := request.GetFilter()
   563  	filter, err := state_stream.NewAccountStatusFilter(h.eventFilterConfig, h.chain, statusFilter.GetEventType(), statusFilter.GetAddress())
   564  	if err != nil {
   565  		return status.Errorf(codes.InvalidArgument, "could not create account status filter: %v", err)
   566  	}
   567  
   568  	sub := h.api.SubscribeAccountStatusesFromStartHeight(stream.Context(), request.GetStartBlockHeight(), filter)
   569  
   570  	return subscription.HandleSubscription(sub, h.handleAccountStatusesResponse(request.HeartbeatInterval, request.GetEventEncodingVersion(), stream.Send))
   571  }
   572  
   573  // SubscribeAccountStatusesFromLatestBlock streams account statuses for all blocks starting
   574  // at the last sealed block, up until the latest available block. Once the latest is
   575  // reached, the stream will remain open and responses are sent for each new
   576  // block as it becomes available.
   577  func (h *Handler) SubscribeAccountStatusesFromLatestBlock(
   578  	request *executiondata.SubscribeAccountStatusesFromLatestBlockRequest,
   579  	stream executiondata.ExecutionDataAPI_SubscribeAccountStatusesFromLatestBlockServer,
   580  ) error {
   581  	// check if the maximum number of streams is reached
   582  	if h.StreamCount.Load() >= h.MaxStreams {
   583  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   584  	}
   585  
   586  	h.StreamCount.Add(1)
   587  	defer h.StreamCount.Add(-1)
   588  
   589  	statusFilter := request.GetFilter()
   590  	filter, err := state_stream.NewAccountStatusFilter(h.eventFilterConfig, h.chain, statusFilter.GetEventType(), statusFilter.GetAddress())
   591  	if err != nil {
   592  		return status.Errorf(codes.InvalidArgument, "could not create account status filter: %v", err)
   593  	}
   594  
   595  	sub := h.api.SubscribeAccountStatusesFromLatestBlock(stream.Context(), filter)
   596  
   597  	return subscription.HandleSubscription(sub, h.handleAccountStatusesResponse(request.HeartbeatInterval, request.GetEventEncodingVersion(), stream.Send))
   598  }