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