github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/state_stream/backend/backend.go (about) 1 package backend 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/rs/zerolog" 10 "google.golang.org/grpc/codes" 11 "google.golang.org/grpc/status" 12 13 "github.com/onflow/flow-go/engine/access/index" 14 "github.com/onflow/flow-go/engine/access/state_stream" 15 "github.com/onflow/flow-go/engine/access/subscription" 16 "github.com/onflow/flow-go/model/flow" 17 "github.com/onflow/flow-go/module/execution" 18 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 19 "github.com/onflow/flow-go/module/executiondatasync/execution_data/cache" 20 "github.com/onflow/flow-go/state/protocol" 21 "github.com/onflow/flow-go/storage" 22 ) 23 24 // Config defines the configurable options for the ingress server. 25 type Config struct { 26 state_stream.EventFilterConfig 27 28 // ListenAddr is the address the GRPC server will listen on as host:port 29 ListenAddr string 30 31 // MaxExecutionDataMsgSize is the max message size for block execution data API 32 MaxExecutionDataMsgSize uint 33 34 // RpcMetricsEnabled specifies whether to enable the GRPC metrics 35 RpcMetricsEnabled bool 36 37 // MaxGlobalStreams defines the global max number of streams that can be open at the same time. 38 MaxGlobalStreams uint32 39 40 // RegisterIDsRequestLimit defines the max number of register IDs that can be received in a single request. 41 RegisterIDsRequestLimit uint32 42 43 // ExecutionDataCacheSize is the max number of objects for the execution data cache. 44 ExecutionDataCacheSize uint32 45 46 // ClientSendTimeout is the timeout for sending a message to the client. After the timeout, 47 // the stream is closed with an error. 48 ClientSendTimeout time.Duration 49 50 // ClientSendBufferSize is the size of the response buffer for sending messages to the client. 51 ClientSendBufferSize uint 52 53 // ResponseLimit is the max responses per second allowed on a stream. After exceeding the limit, 54 // the stream is paused until more capacity is available. Searches of past data can be CPU 55 // intensive, so this helps manage the impact. 56 ResponseLimit float64 57 58 // HeartbeatInterval specifies the block interval at which heartbeat messages should be sent. 59 HeartbeatInterval uint64 60 } 61 62 type GetExecutionDataFunc func(context.Context, uint64) (*execution_data.BlockExecutionDataEntity, error) 63 64 type StateStreamBackend struct { 65 subscription.ExecutionDataTracker 66 67 ExecutionDataBackend 68 EventsBackend 69 AccountStatusesBackend 70 71 log zerolog.Logger 72 state protocol.State 73 headers storage.Headers 74 seals storage.Seals 75 results storage.ExecutionResults 76 execDataStore execution_data.ExecutionDataStore 77 execDataCache *cache.ExecutionDataCache 78 registers *execution.RegistersAsyncStore 79 registerRequestLimit int 80 } 81 82 func New( 83 log zerolog.Logger, 84 state protocol.State, 85 headers storage.Headers, 86 seals storage.Seals, 87 results storage.ExecutionResults, 88 execDataStore execution_data.ExecutionDataStore, 89 execDataCache *cache.ExecutionDataCache, 90 registers *execution.RegistersAsyncStore, 91 eventsIndex *index.EventsIndex, 92 useEventsIndex bool, 93 registerIDsRequestLimit int, 94 subscriptionHandler *subscription.SubscriptionHandler, 95 executionDataTracker subscription.ExecutionDataTracker, 96 ) (*StateStreamBackend, error) { 97 logger := log.With().Str("module", "state_stream_api").Logger() 98 99 b := &StateStreamBackend{ 100 ExecutionDataTracker: executionDataTracker, 101 log: logger, 102 state: state, 103 headers: headers, 104 seals: seals, 105 results: results, 106 execDataStore: execDataStore, 107 execDataCache: execDataCache, 108 registers: registers, 109 registerRequestLimit: registerIDsRequestLimit, 110 } 111 112 b.ExecutionDataBackend = ExecutionDataBackend{ 113 log: logger, 114 headers: headers, 115 subscriptionHandler: subscriptionHandler, 116 getExecutionData: b.getExecutionData, 117 executionDataTracker: executionDataTracker, 118 } 119 120 eventsRetriever := EventsRetriever{ 121 log: logger, 122 headers: headers, 123 getExecutionData: b.getExecutionData, 124 useEventsIndex: useEventsIndex, 125 eventsIndex: eventsIndex, 126 } 127 128 b.EventsBackend = EventsBackend{ 129 log: logger, 130 subscriptionHandler: subscriptionHandler, 131 executionDataTracker: executionDataTracker, 132 eventsRetriever: eventsRetriever, 133 } 134 135 b.AccountStatusesBackend = AccountStatusesBackend{ 136 log: logger, 137 subscriptionHandler: subscriptionHandler, 138 executionDataTracker: b.ExecutionDataTracker, 139 eventsRetriever: eventsRetriever, 140 } 141 142 return b, nil 143 } 144 145 // getExecutionData returns the execution data for the given block height. 146 // Expected errors during normal operation: 147 // - subscription.ErrBlockNotReady: execution data for the given block height is not available. 148 func (b *StateStreamBackend) getExecutionData(ctx context.Context, height uint64) (*execution_data.BlockExecutionDataEntity, error) { 149 highestHeight := b.ExecutionDataTracker.GetHighestHeight() 150 // fail early if no notification has been received for the given block height. 151 // note: it's possible for the data to exist in the data store before the notification is 152 // received. this ensures a consistent view is available to all streams. 153 if height > highestHeight { 154 return nil, fmt.Errorf("execution data for block %d is not available yet: %w", height, subscription.ErrBlockNotReady) 155 } 156 157 execData, err := b.execDataCache.ByHeight(ctx, height) 158 if err != nil { 159 if errors.Is(err, storage.ErrNotFound) || 160 execution_data.IsBlobNotFoundError(err) { 161 err = errors.Join(err, subscription.ErrBlockNotReady) 162 return nil, fmt.Errorf("could not get execution data for block %d: %w", height, err) 163 } 164 return nil, fmt.Errorf("could not get execution data for block %d: %w", height, err) 165 } 166 167 return execData, nil 168 } 169 170 // GetRegisterValues returns the register values for the given register IDs at the given block height. 171 func (b *StateStreamBackend) GetRegisterValues(ids flow.RegisterIDs, height uint64) ([]flow.RegisterValue, error) { 172 if len(ids) > b.registerRequestLimit { 173 return nil, status.Errorf(codes.InvalidArgument, "number of register IDs exceeds limit of %d", b.registerRequestLimit) 174 } 175 176 values, err := b.registers.RegisterValues(ids, height) 177 if err != nil { 178 if errors.Is(err, storage.ErrHeightNotIndexed) { 179 return nil, status.Errorf(codes.OutOfRange, "register values for block %d is not available", height) 180 } 181 if errors.Is(err, storage.ErrNotFound) { 182 return nil, status.Errorf(codes.NotFound, "register values for block %d not found", height) 183 } 184 return nil, err 185 } 186 187 return values, nil 188 }