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 }