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

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/rs/zerolog"
     9  	"google.golang.org/grpc/codes"
    10  	"google.golang.org/grpc/status"
    11  
    12  	"github.com/onflow/flow-go/engine"
    13  	"github.com/onflow/flow-go/engine/access/rpc/backend"
    14  	"github.com/onflow/flow-go/engine/access/state_stream"
    15  	"github.com/onflow/flow-go/engine/common/rpc"
    16  	"github.com/onflow/flow-go/fvm/errors"
    17  	"github.com/onflow/flow-go/model/flow"
    18  	"github.com/onflow/flow-go/module/counters"
    19  	"github.com/onflow/flow-go/module/execution"
    20  	"github.com/onflow/flow-go/module/executiondatasync/execution_data"
    21  	"github.com/onflow/flow-go/module/executiondatasync/execution_data/cache"
    22  	"github.com/onflow/flow-go/module/state_synchronization"
    23  	"github.com/onflow/flow-go/module/state_synchronization/indexer"
    24  	"github.com/onflow/flow-go/state/protocol"
    25  	"github.com/onflow/flow-go/storage"
    26  )
    27  
    28  // Config defines the configurable options for the ingress server.
    29  type Config struct {
    30  	state_stream.EventFilterConfig
    31  
    32  	// ListenAddr is the address the GRPC server will listen on as host:port
    33  	ListenAddr string
    34  
    35  	// MaxExecutionDataMsgSize is the max message size for block execution data API
    36  	MaxExecutionDataMsgSize uint
    37  
    38  	// RpcMetricsEnabled specifies whether to enable the GRPC metrics
    39  	RpcMetricsEnabled bool
    40  
    41  	// MaxGlobalStreams defines the global max number of streams that can be open at the same time.
    42  	MaxGlobalStreams uint32
    43  
    44  	// RegisterIDsRequestLimit defines the max number of register IDs that can be received in a single request.
    45  	RegisterIDsRequestLimit uint32
    46  
    47  	// ExecutionDataCacheSize is the max number of objects for the execution data cache.
    48  	ExecutionDataCacheSize uint32
    49  
    50  	// ClientSendTimeout is the timeout for sending a message to the client. After the timeout,
    51  	// the stream is closed with an error.
    52  	ClientSendTimeout time.Duration
    53  
    54  	// ClientSendBufferSize is the size of the response buffer for sending messages to the client.
    55  	ClientSendBufferSize uint
    56  
    57  	// ResponseLimit is the max responses per second allowed on a stream. After exceeding the limit,
    58  	// the stream is paused until more capacity is available. Searches of past data can be CPU
    59  	// intensive, so this helps manage the impact.
    60  	ResponseLimit float64
    61  
    62  	// HeartbeatInterval specifies the block interval at which heartbeat messages should be sent.
    63  	HeartbeatInterval uint64
    64  }
    65  
    66  type GetExecutionDataFunc func(context.Context, uint64) (*execution_data.BlockExecutionDataEntity, error)
    67  type GetStartHeightFunc func(flow.Identifier, uint64) (uint64, error)
    68  
    69  type StateStreamBackend struct {
    70  	ExecutionDataBackend
    71  	EventsBackend
    72  
    73  	log                  zerolog.Logger
    74  	state                protocol.State
    75  	headers              storage.Headers
    76  	seals                storage.Seals
    77  	results              storage.ExecutionResults
    78  	execDataStore        execution_data.ExecutionDataStore
    79  	execDataCache        *cache.ExecutionDataCache
    80  	broadcaster          *engine.Broadcaster
    81  	rootBlockHeight      uint64
    82  	rootBlockID          flow.Identifier
    83  	registers            *execution.RegistersAsyncStore
    84  	indexReporter        state_synchronization.IndexReporter
    85  	registerRequestLimit int
    86  
    87  	// highestHeight contains the highest consecutive block height for which we have received a
    88  	// new Execution Data notification.
    89  	highestHeight counters.StrictMonotonousCounter
    90  }
    91  
    92  func New(
    93  	log zerolog.Logger,
    94  	config Config,
    95  	state protocol.State,
    96  	headers storage.Headers,
    97  	seals storage.Seals,
    98  	results storage.ExecutionResults,
    99  	execDataStore execution_data.ExecutionDataStore,
   100  	execDataCache *cache.ExecutionDataCache,
   101  	broadcaster *engine.Broadcaster,
   102  	rootHeight uint64,
   103  	highestAvailableHeight uint64,
   104  	registers *execution.RegistersAsyncStore,
   105  	eventsIndex *backend.EventsIndex,
   106  	useEventsIndex bool,
   107  ) (*StateStreamBackend, error) {
   108  	logger := log.With().Str("module", "state_stream_api").Logger()
   109  
   110  	// cache the root block height and ID for runtime lookups.
   111  	rootBlockID, err := headers.BlockIDByHeight(rootHeight)
   112  	if err != nil {
   113  		return nil, fmt.Errorf("could not get root block ID: %w", err)
   114  	}
   115  
   116  	b := &StateStreamBackend{
   117  		log:                  logger,
   118  		state:                state,
   119  		headers:              headers,
   120  		seals:                seals,
   121  		results:              results,
   122  		execDataStore:        execDataStore,
   123  		execDataCache:        execDataCache,
   124  		broadcaster:          broadcaster,
   125  		rootBlockHeight:      rootHeight,
   126  		rootBlockID:          rootBlockID,
   127  		registers:            registers,
   128  		indexReporter:        eventsIndex,
   129  		registerRequestLimit: int(config.RegisterIDsRequestLimit),
   130  		highestHeight:        counters.NewMonotonousCounter(highestAvailableHeight),
   131  	}
   132  
   133  	b.ExecutionDataBackend = ExecutionDataBackend{
   134  		log:              logger,
   135  		headers:          headers,
   136  		broadcaster:      broadcaster,
   137  		sendTimeout:      config.ClientSendTimeout,
   138  		responseLimit:    config.ResponseLimit,
   139  		sendBufferSize:   int(config.ClientSendBufferSize),
   140  		getExecutionData: b.getExecutionData,
   141  		getStartHeight:   b.getStartHeight,
   142  	}
   143  
   144  	b.EventsBackend = EventsBackend{
   145  		log:              logger,
   146  		headers:          headers,
   147  		broadcaster:      broadcaster,
   148  		sendTimeout:      config.ClientSendTimeout,
   149  		responseLimit:    config.ResponseLimit,
   150  		sendBufferSize:   int(config.ClientSendBufferSize),
   151  		getExecutionData: b.getExecutionData,
   152  		getStartHeight:   b.getStartHeight,
   153  		useIndex:         useEventsIndex,
   154  		eventsIndex:      eventsIndex,
   155  	}
   156  
   157  	return b, nil
   158  }
   159  
   160  // getExecutionData returns the execution data for the given block height.
   161  // Expected errors during normal operation:
   162  // - storage.ErrNotFound or execution_data.BlobNotFoundError: execution data for the given block height is not available.
   163  func (b *StateStreamBackend) getExecutionData(ctx context.Context, height uint64) (*execution_data.BlockExecutionDataEntity, error) {
   164  	// fail early if no notification has been received for the given block height.
   165  	// note: it's possible for the data to exist in the data store before the notification is
   166  	// received. this ensures a consistent view is available to all streams.
   167  	if height > b.highestHeight.Value() {
   168  		return nil, fmt.Errorf("execution data for block %d is not available yet: %w", height, storage.ErrNotFound)
   169  	}
   170  
   171  	execData, err := b.execDataCache.ByHeight(ctx, height)
   172  	if err != nil {
   173  		return nil, fmt.Errorf("could not get execution data for block %d: %w", height, err)
   174  	}
   175  
   176  	return execData, nil
   177  }
   178  
   179  // getStartHeight returns the start height to use when searching.
   180  // Only one of startBlockID and startHeight may be set. Otherwise, an InvalidArgument error is returned.
   181  // If a block is provided and does not exist, a NotFound error is returned.
   182  // If neither startBlockID nor startHeight is provided, the latest sealed block is used.
   183  func (b *StateStreamBackend) getStartHeight(startBlockID flow.Identifier, startHeight uint64) (height uint64, err error) {
   184  	// make sure only one of start block ID and start height is provided
   185  	if startBlockID != flow.ZeroID && startHeight > 0 {
   186  		return 0, status.Errorf(codes.InvalidArgument, "only one of start block ID and start height may be provided")
   187  	}
   188  
   189  	// ensure that the resolved start height is available
   190  	defer func() {
   191  		if err == nil {
   192  			height, err = b.checkStartHeight(height)
   193  		}
   194  	}()
   195  
   196  	if startBlockID != flow.ZeroID {
   197  		return b.startHeightFromBlockID(startBlockID)
   198  	}
   199  
   200  	if startHeight > 0 {
   201  		return b.startHeightFromHeight(startHeight)
   202  	}
   203  
   204  	// if no start block was provided, use the latest sealed block
   205  	header, err := b.state.Sealed().Head()
   206  	if err != nil {
   207  		return 0, status.Errorf(codes.Internal, "could not get latest sealed block: %v", err)
   208  	}
   209  	return header.Height, nil
   210  }
   211  
   212  func (b *StateStreamBackend) startHeightFromBlockID(startBlockID flow.Identifier) (uint64, error) {
   213  	header, err := b.headers.ByBlockID(startBlockID)
   214  	if err != nil {
   215  		return 0, rpc.ConvertStorageError(fmt.Errorf("could not get header for block %v: %w", startBlockID, err))
   216  	}
   217  	return header.Height, nil
   218  }
   219  
   220  func (b *StateStreamBackend) startHeightFromHeight(startHeight uint64) (uint64, error) {
   221  	if startHeight < b.rootBlockHeight {
   222  		return 0, status.Errorf(codes.InvalidArgument, "start height must be greater than or equal to the root height %d", b.rootBlockHeight)
   223  	}
   224  
   225  	header, err := b.headers.ByHeight(startHeight)
   226  	if err != nil {
   227  		return 0, rpc.ConvertStorageError(fmt.Errorf("could not get header for height %d: %w", startHeight, err))
   228  	}
   229  	return header.Height, nil
   230  }
   231  
   232  func (b *StateStreamBackend) checkStartHeight(height uint64) (uint64, error) {
   233  	// if the start block is the root block, there will not be an execution data. skip it and
   234  	// begin from the next block.
   235  	if height == b.rootBlockHeight {
   236  		height = b.rootBlockHeight + 1
   237  	}
   238  
   239  	if !b.useIndex {
   240  		return height, nil
   241  	}
   242  
   243  	lowestHeight, highestHeight, err := b.getIndexerHeights()
   244  	if err != nil {
   245  		return 0, err
   246  	}
   247  
   248  	if height < lowestHeight {
   249  		return 0, status.Errorf(codes.InvalidArgument, "start height %d is lower than lowest indexed height %d", height, lowestHeight)
   250  	}
   251  
   252  	if height > highestHeight {
   253  		return 0, status.Errorf(codes.InvalidArgument, "start height %d is higher than highest indexed height %d", height, highestHeight)
   254  	}
   255  
   256  	return height, nil
   257  }
   258  
   259  // getIndexerHeights returns the lowest and highest indexed block heights
   260  // Expected errors during normal operation:
   261  // - codes.FailedPrecondition: if the index reporter is not ready yet.
   262  // - codes.Internal: if there was any other error getting the heights.
   263  func (b *StateStreamBackend) getIndexerHeights() (uint64, uint64, error) {
   264  	lowestHeight, err := b.indexReporter.LowestIndexedHeight()
   265  	if err != nil {
   266  		if errors.Is(err, storage.ErrHeightNotIndexed) || errors.Is(err, indexer.ErrIndexNotInitialized) {
   267  			// the index is not ready yet, but likely will be eventually
   268  			return 0, 0, status.Errorf(codes.FailedPrecondition, "failed to get lowest indexed height: %v", err)
   269  		}
   270  		return 0, 0, rpc.ConvertError(err, "failed to get lowest indexed height", codes.Internal)
   271  	}
   272  
   273  	highestHeight, err := b.indexReporter.HighestIndexedHeight()
   274  	if err != nil {
   275  		if errors.Is(err, storage.ErrHeightNotIndexed) || errors.Is(err, indexer.ErrIndexNotInitialized) {
   276  			// the index is not ready yet, but likely will be eventually
   277  			return 0, 0, status.Errorf(codes.FailedPrecondition, "failed to get highest indexed height: %v", err)
   278  		}
   279  		return 0, 0, rpc.ConvertError(err, "failed to get highest indexed height", codes.Internal)
   280  	}
   281  
   282  	return lowestHeight, highestHeight, nil
   283  }
   284  
   285  // setHighestHeight sets the highest height for which execution data is available.
   286  func (b *StateStreamBackend) setHighestHeight(height uint64) bool {
   287  	return b.highestHeight.Set(height)
   288  }
   289  
   290  // GetRegisterValues returns the register values for the given register IDs at the given block height.
   291  func (b *StateStreamBackend) GetRegisterValues(ids flow.RegisterIDs, height uint64) ([]flow.RegisterValue, error) {
   292  	if len(ids) > b.registerRequestLimit {
   293  		return nil, status.Errorf(codes.InvalidArgument, "number of register IDs exceeds limit of %d", b.registerRequestLimit)
   294  	}
   295  
   296  	values, err := b.registers.RegisterValues(ids, height)
   297  	if err != nil {
   298  		if errors.Is(err, storage.ErrHeightNotIndexed) {
   299  			return nil, status.Errorf(codes.OutOfRange, "register values for block %d is not available", height)
   300  		}
   301  		if errors.Is(err, storage.ErrNotFound) {
   302  			return nil, status.Errorf(codes.NotFound, "register values for block %d not found", height)
   303  		}
   304  		return nil, err
   305  	}
   306  
   307  	return values, nil
   308  }