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

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/rs/zerolog"
     8  	"go.uber.org/atomic"
     9  
    10  	"github.com/onflow/flow-go/model/flow"
    11  	"github.com/onflow/flow-go/module/execution"
    12  	"github.com/onflow/flow-go/module/state_synchronization"
    13  	"github.com/onflow/flow-go/storage"
    14  )
    15  
    16  type ScriptExecutor struct {
    17  	log zerolog.Logger
    18  
    19  	// scriptExecutor is used to interact with execution state
    20  	scriptExecutor *execution.Scripts
    21  
    22  	// indexReporter provides information about the current state of the execution state indexer.
    23  	indexReporter state_synchronization.IndexReporter
    24  
    25  	// initialized is used to signal that the index and executor are ready
    26  	initialized *atomic.Bool
    27  
    28  	// minCompatibleHeight and maxCompatibleHeight are used to limit the block range that can be queried using local execution
    29  	// to ensure only blocks that are compatible with the node's current software version are allowed.
    30  	// Note: this is a temporary solution for cadence/fvm upgrades while version beacon support is added
    31  	minCompatibleHeight *atomic.Uint64
    32  	maxCompatibleHeight *atomic.Uint64
    33  }
    34  
    35  func NewScriptExecutor(log zerolog.Logger, minHeight, maxHeight uint64) *ScriptExecutor {
    36  	logger := log.With().Str("component", "script-executor").Logger()
    37  	logger.Info().
    38  		Uint64("min_height", minHeight).
    39  		Uint64("max_height", maxHeight).
    40  		Msg("script executor created")
    41  
    42  	return &ScriptExecutor{
    43  		log:                 logger,
    44  		initialized:         atomic.NewBool(false),
    45  		minCompatibleHeight: atomic.NewUint64(minHeight),
    46  		maxCompatibleHeight: atomic.NewUint64(maxHeight),
    47  	}
    48  }
    49  
    50  // SetMinCompatibleHeight sets the lowest block height (inclusive) that can be queried using local execution
    51  // Use this to limit the executable block range supported by the node's current software version.
    52  func (s *ScriptExecutor) SetMinCompatibleHeight(height uint64) {
    53  	s.minCompatibleHeight.Store(height)
    54  	s.log.Info().Uint64("height", height).Msg("minimum compatible height set")
    55  }
    56  
    57  // SetMaxCompatibleHeight sets the highest block height (inclusive) that can be queried using local execution
    58  // Use this to limit the executable block range supported by the node's current software version.
    59  func (s *ScriptExecutor) SetMaxCompatibleHeight(height uint64) {
    60  	s.maxCompatibleHeight.Store(height)
    61  	s.log.Info().Uint64("height", height).Msg("maximum compatible height set")
    62  }
    63  
    64  // Initialize initializes the indexReporter and script executor
    65  // This method can be called at any time after the ScriptExecutor object is created. Any requests
    66  // made to the other methods will return storage.ErrHeightNotIndexed until this method is called.
    67  func (s *ScriptExecutor) Initialize(indexReporter state_synchronization.IndexReporter, scriptExecutor *execution.Scripts) error {
    68  	if s.initialized.CompareAndSwap(false, true) {
    69  		s.log.Info().Msg("script executor initialized")
    70  		s.indexReporter = indexReporter
    71  		s.scriptExecutor = scriptExecutor
    72  		return nil
    73  	}
    74  	return fmt.Errorf("script executor already initialized")
    75  }
    76  
    77  // ExecuteAtBlockHeight executes provided script at the provided block height against a local execution state.
    78  //
    79  // Expected errors:
    80  //   - storage.ErrNotFound if the register or block height is not found
    81  //   - storage.ErrHeightNotIndexed if the data for the block height is not available. this could be because
    82  //     the height is not within the index block range, or the index is not ready.
    83  func (s *ScriptExecutor) ExecuteAtBlockHeight(ctx context.Context, script []byte, arguments [][]byte, height uint64) ([]byte, error) {
    84  	if err := s.checkDataAvailable(height); err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	return s.scriptExecutor.ExecuteAtBlockHeight(ctx, script, arguments, height)
    89  }
    90  
    91  // GetAccountAtBlockHeight returns the account at the provided block height from a local execution state.
    92  //
    93  // Expected errors:
    94  //   - storage.ErrNotFound if the account or block height is not found
    95  //   - storage.ErrHeightNotIndexed if the data for the block height is not available. this could be because
    96  //     the height is not within the index block range, or the index is not ready.
    97  func (s *ScriptExecutor) GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error) {
    98  	if err := s.checkDataAvailable(height); err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return s.scriptExecutor.GetAccountAtBlockHeight(ctx, address, height)
   103  }
   104  
   105  func (s *ScriptExecutor) checkDataAvailable(height uint64) error {
   106  	if !s.initialized.Load() {
   107  		return fmt.Errorf("%w: script executor not initialized", storage.ErrHeightNotIndexed)
   108  	}
   109  
   110  	highestHeight, err := s.indexReporter.HighestIndexedHeight()
   111  	if err != nil {
   112  		return fmt.Errorf("could not get highest indexed height: %w", err)
   113  	}
   114  	if height > highestHeight {
   115  		return fmt.Errorf("%w: block not indexed yet", storage.ErrHeightNotIndexed)
   116  	}
   117  
   118  	lowestHeight, err := s.indexReporter.LowestIndexedHeight()
   119  	if err != nil {
   120  		return fmt.Errorf("could not get lowest indexed height: %w", err)
   121  	}
   122  	if height < lowestHeight {
   123  		return fmt.Errorf("%w: block is before lowest indexed height", storage.ErrHeightNotIndexed)
   124  	}
   125  
   126  	if height > s.maxCompatibleHeight.Load() || height < s.minCompatibleHeight.Load() {
   127  		return fmt.Errorf("%w: node software is not compatible with version required to executed block", storage.ErrHeightNotIndexed)
   128  	}
   129  
   130  	return nil
   131  }