github.com/onflow/flow-go@v0.33.17/engine/access/state_stream/backend/handler.go (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"sync/atomic"
     6  
     7  	"google.golang.org/grpc/codes"
     8  	"google.golang.org/grpc/status"
     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/common/rpc"
    15  	"github.com/onflow/flow-go/engine/common/rpc/convert"
    16  	"github.com/onflow/flow-go/model/flow"
    17  )
    18  
    19  type Handler struct {
    20  	api   state_stream.API
    21  	chain flow.Chain
    22  
    23  	eventFilterConfig state_stream.EventFilterConfig
    24  
    25  	maxStreams               int32
    26  	streamCount              atomic.Int32
    27  	defaultHeartbeatInterval uint64
    28  }
    29  
    30  func NewHandler(api state_stream.API, chain flow.Chain, config Config) *Handler {
    31  	h := &Handler{
    32  		api:                      api,
    33  		chain:                    chain,
    34  		eventFilterConfig:        config.EventFilterConfig,
    35  		maxStreams:               int32(config.MaxGlobalStreams),
    36  		streamCount:              atomic.Int32{},
    37  		defaultHeartbeatInterval: config.HeartbeatInterval,
    38  	}
    39  	return h
    40  }
    41  
    42  func (h *Handler) GetExecutionDataByBlockID(ctx context.Context, request *executiondata.GetExecutionDataByBlockIDRequest) (*executiondata.GetExecutionDataByBlockIDResponse, error) {
    43  	blockID, err := convert.BlockID(request.GetBlockId())
    44  	if err != nil {
    45  		return nil, status.Errorf(codes.InvalidArgument, "could not convert block ID: %v", err)
    46  	}
    47  
    48  	execData, err := h.api.GetExecutionDataByBlockID(ctx, blockID)
    49  	if err != nil {
    50  		return nil, rpc.ConvertError(err, "could no get execution data", codes.Internal)
    51  	}
    52  
    53  	message, err := convert.BlockExecutionDataToMessage(execData)
    54  	if err != nil {
    55  		return nil, status.Errorf(codes.Internal, "could not convert execution data to entity: %v", err)
    56  	}
    57  
    58  	err = convert.BlockExecutionDataEventPayloadsToVersion(message, request.GetEventEncodingVersion())
    59  	if err != nil {
    60  		return nil, status.Errorf(codes.Internal, "could not convert execution data event payloads to JSON: %v", err)
    61  	}
    62  
    63  	return &executiondata.GetExecutionDataByBlockIDResponse{BlockExecutionData: message}, nil
    64  }
    65  
    66  func (h *Handler) SubscribeExecutionData(request *executiondata.SubscribeExecutionDataRequest, stream executiondata.ExecutionDataAPI_SubscribeExecutionDataServer) error {
    67  	// check if the maximum number of streams is reached
    68  	if h.streamCount.Load() >= h.maxStreams {
    69  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
    70  	}
    71  	h.streamCount.Add(1)
    72  	defer h.streamCount.Add(-1)
    73  
    74  	startBlockID := flow.ZeroID
    75  	if request.GetStartBlockId() != nil {
    76  		blockID, err := convert.BlockID(request.GetStartBlockId())
    77  		if err != nil {
    78  			return status.Errorf(codes.InvalidArgument, "could not convert start block ID: %v", err)
    79  		}
    80  		startBlockID = blockID
    81  	}
    82  
    83  	sub := h.api.SubscribeExecutionData(stream.Context(), startBlockID, request.GetStartBlockHeight())
    84  
    85  	for {
    86  		v, ok := <-sub.Channel()
    87  		if !ok {
    88  			if sub.Err() != nil {
    89  				return rpc.ConvertError(sub.Err(), "stream encountered an error", codes.Internal)
    90  			}
    91  			return nil
    92  		}
    93  
    94  		resp, ok := v.(*ExecutionDataResponse)
    95  		if !ok {
    96  			return status.Errorf(codes.Internal, "unexpected response type: %T", v)
    97  		}
    98  
    99  		execData, err := convert.BlockExecutionDataToMessage(resp.ExecutionData)
   100  		if err != nil {
   101  			return status.Errorf(codes.Internal, "could not convert execution data to entity: %v", err)
   102  		}
   103  
   104  		err = convert.BlockExecutionDataEventPayloadsToVersion(execData, request.GetEventEncodingVersion())
   105  		if err != nil {
   106  			return status.Errorf(codes.Internal, "could not convert execution data event payloads to JSON: %v", err)
   107  		}
   108  
   109  		err = stream.Send(&executiondata.SubscribeExecutionDataResponse{
   110  			BlockHeight:        resp.Height,
   111  			BlockExecutionData: execData,
   112  		})
   113  		if err != nil {
   114  			return rpc.ConvertError(err, "could not send response", codes.Internal)
   115  		}
   116  	}
   117  }
   118  
   119  func (h *Handler) SubscribeEvents(request *executiondata.SubscribeEventsRequest, stream executiondata.ExecutionDataAPI_SubscribeEventsServer) error {
   120  	// check if the maximum number of streams is reached
   121  	if h.streamCount.Load() >= h.maxStreams {
   122  		return status.Errorf(codes.ResourceExhausted, "maximum number of streams reached")
   123  	}
   124  	h.streamCount.Add(1)
   125  	defer h.streamCount.Add(-1)
   126  
   127  	startBlockID := flow.ZeroID
   128  	if request.GetStartBlockId() != nil {
   129  		blockID, err := convert.BlockID(request.GetStartBlockId())
   130  		if err != nil {
   131  			return status.Errorf(codes.InvalidArgument, "could not convert start block ID: %v", err)
   132  		}
   133  		startBlockID = blockID
   134  	}
   135  
   136  	filter := state_stream.EventFilter{}
   137  	if request.GetFilter() != nil {
   138  		var err error
   139  		reqFilter := request.GetFilter()
   140  		filter, err = state_stream.NewEventFilter(
   141  			h.eventFilterConfig,
   142  			h.chain,
   143  			reqFilter.GetEventType(),
   144  			reqFilter.GetAddress(),
   145  			reqFilter.GetContract(),
   146  		)
   147  		if err != nil {
   148  			return status.Errorf(codes.InvalidArgument, "invalid event filter: %v", err)
   149  		}
   150  	}
   151  
   152  	sub := h.api.SubscribeEvents(stream.Context(), startBlockID, request.GetStartBlockHeight(), filter)
   153  
   154  	heartbeatInterval := request.HeartbeatInterval
   155  	if heartbeatInterval == 0 {
   156  		heartbeatInterval = h.defaultHeartbeatInterval
   157  	}
   158  
   159  	blocksSinceLastMessage := uint64(0)
   160  	for {
   161  		v, ok := <-sub.Channel()
   162  		if !ok {
   163  			if sub.Err() != nil {
   164  				return rpc.ConvertError(sub.Err(), "stream encountered an error", codes.Internal)
   165  			}
   166  			return nil
   167  		}
   168  
   169  		resp, ok := v.(*EventsResponse)
   170  		if !ok {
   171  			return status.Errorf(codes.Internal, "unexpected response type: %T", v)
   172  		}
   173  
   174  		// check if there are any events in the response. if not, do not send a message unless the last
   175  		// response was more than HeartbeatInterval blocks ago
   176  		if len(resp.Events) == 0 {
   177  			blocksSinceLastMessage++
   178  			if blocksSinceLastMessage < heartbeatInterval {
   179  				continue
   180  			}
   181  			blocksSinceLastMessage = 0
   182  		}
   183  
   184  		// BlockExecutionData contains CCF encoded events, and the Access API returns JSON-CDC events.
   185  		// convert event payload formats.
   186  		// This is a temporary solution until the Access API supports specifying the encoding in the request
   187  		events, err := convert.EventsToMessagesWithEncodingConversion(resp.Events, entities.EventEncodingVersion_CCF_V0, request.GetEventEncodingVersion())
   188  		if err != nil {
   189  			return status.Errorf(codes.Internal, "could not convert events to entity: %v", err)
   190  		}
   191  
   192  		err = stream.Send(&executiondata.SubscribeEventsResponse{
   193  			BlockHeight: resp.Height,
   194  			BlockId:     convert.IdentifierToMessage(resp.BlockID),
   195  			Events:      events,
   196  		})
   197  		if err != nil {
   198  			return rpc.ConvertError(err, "could not send response", codes.Internal)
   199  		}
   200  	}
   201  }
   202  
   203  func (h *Handler) GetRegisterValues(_ context.Context, request *executiondata.GetRegisterValuesRequest) (*executiondata.GetRegisterValuesResponse, error) {
   204  	// Convert data
   205  	registerIDs, err := convert.MessagesToRegisterIDs(request.GetRegisterIds(), h.chain)
   206  	if err != nil {
   207  		return nil, status.Errorf(codes.InvalidArgument, "could not convert register IDs: %v", err)
   208  	}
   209  
   210  	// get payload from store
   211  	values, err := h.api.GetRegisterValues(registerIDs, request.GetBlockHeight())
   212  	if err != nil {
   213  		return nil, rpc.ConvertError(err, "could not get register values", codes.Internal)
   214  	}
   215  
   216  	return &executiondata.GetRegisterValuesResponse{Values: values}, nil
   217  }