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 }