github.com/koko1123/flow-go-1@v0.29.6/engine/execution/computation/manager.go (about) 1 package computation 2 3 import ( 4 "context" 5 "encoding/hex" 6 "fmt" 7 "math/rand" 8 "strings" 9 "sync" 10 "time" 11 12 jsoncdc "github.com/onflow/cadence/encoding/json" 13 "github.com/onflow/cadence/runtime" 14 "github.com/rs/zerolog" 15 16 "github.com/koko1123/flow-go-1/engine/execution" 17 "github.com/koko1123/flow-go-1/engine/execution/computation/computer" 18 "github.com/koko1123/flow-go-1/fvm" 19 "github.com/koko1123/flow-go-1/fvm/derived" 20 reusableRuntime "github.com/koko1123/flow-go-1/fvm/runtime" 21 "github.com/koko1123/flow-go-1/fvm/state" 22 "github.com/koko1123/flow-go-1/model/flow" 23 "github.com/koko1123/flow-go-1/module" 24 "github.com/koko1123/flow-go-1/module/executiondatasync/provider" 25 "github.com/koko1123/flow-go-1/module/mempool/entity" 26 "github.com/koko1123/flow-go-1/state/protocol" 27 "github.com/koko1123/flow-go-1/utils/debug" 28 "github.com/koko1123/flow-go-1/utils/logging" 29 ) 30 31 const ( 32 DefaultScriptLogThreshold = 1 * time.Second 33 DefaultScriptExecutionTimeLimit = 10 * time.Second 34 35 MaxScriptErrorMessageSize = 1000 // 1000 chars 36 37 ReusableCadenceRuntimePoolSize = 1000 38 ) 39 40 type ComputationManager interface { 41 ExecuteScript(context.Context, []byte, [][]byte, *flow.Header, state.View) ([]byte, error) 42 ComputeBlock( 43 ctx context.Context, 44 block *entity.ExecutableBlock, 45 view state.View, 46 ) (*execution.ComputationResult, error) 47 GetAccount(addr flow.Address, header *flow.Header, view state.View) (*flow.Account, error) 48 } 49 50 type ComputationConfig struct { 51 CadenceTracing bool 52 ExtensiveTracing bool 53 DerivedDataCacheSize uint 54 ScriptLogThreshold time.Duration 55 ScriptExecutionTimeLimit time.Duration 56 57 // When NewCustomVirtualMachine is nil, the manager will create a standard 58 // fvm virtual machine via fvm.NewVirtualMachine. Otherwise, the manager 59 // will create a virtual machine using this function. 60 // 61 // Note that this is primarily used for testing. 62 NewCustomVirtualMachine func() fvm.VM 63 } 64 65 // Manager manages computation and execution 66 type Manager struct { 67 log zerolog.Logger 68 tracer module.Tracer 69 metrics module.ExecutionMetrics 70 me module.Local 71 protoState protocol.State 72 vm fvm.VM 73 vmCtx fvm.Context 74 blockComputer computer.BlockComputer 75 derivedChainData *derived.DerivedChainData 76 scriptLogThreshold time.Duration 77 scriptExecutionTimeLimit time.Duration 78 rngLock *sync.Mutex 79 rng *rand.Rand 80 } 81 82 func New( 83 logger zerolog.Logger, 84 metrics module.ExecutionMetrics, 85 tracer module.Tracer, 86 me module.Local, 87 protoState protocol.State, 88 vmCtx fvm.Context, 89 committer computer.ViewCommitter, 90 executionDataProvider *provider.Provider, 91 params ComputationConfig, 92 ) (*Manager, error) { 93 log := logger.With().Str("engine", "computation").Logger() 94 95 var vm fvm.VM 96 if params.NewCustomVirtualMachine != nil { 97 vm = params.NewCustomVirtualMachine() 98 } else { 99 vm = fvm.NewVirtualMachine() 100 } 101 102 options := []fvm.Option{ 103 fvm.WithReusableCadenceRuntimePool( 104 reusableRuntime.NewReusableCadenceRuntimePool( 105 ReusableCadenceRuntimePoolSize, 106 runtime.Config{ 107 TracingEnabled: params.CadenceTracing, 108 })), 109 } 110 if params.ExtensiveTracing { 111 options = append(options, fvm.WithExtensiveTracing()) 112 } 113 114 vmCtx = fvm.NewContextFromParent(vmCtx, options...) 115 116 blockComputer, err := computer.NewBlockComputer( 117 vm, 118 vmCtx, 119 metrics, 120 tracer, 121 log.With().Str("component", "block_computer").Logger(), 122 committer, 123 me, 124 executionDataProvider, 125 ) 126 127 if err != nil { 128 return nil, fmt.Errorf("cannot create block computer: %w", err) 129 } 130 131 derivedChainData, err := derived.NewDerivedChainData(params.DerivedDataCacheSize) 132 if err != nil { 133 return nil, fmt.Errorf("cannot create derived data cache: %w", err) 134 } 135 136 e := Manager{ 137 log: log, 138 tracer: tracer, 139 metrics: metrics, 140 me: me, 141 protoState: protoState, 142 vm: vm, 143 vmCtx: vmCtx, 144 blockComputer: blockComputer, 145 derivedChainData: derivedChainData, 146 scriptLogThreshold: params.ScriptLogThreshold, 147 scriptExecutionTimeLimit: params.ScriptExecutionTimeLimit, 148 rngLock: &sync.Mutex{}, 149 rng: rand.New(rand.NewSource(time.Now().UnixNano())), 150 } 151 152 return &e, nil 153 } 154 155 func (e *Manager) VM() fvm.VM { 156 return e.vm 157 } 158 159 func (e *Manager) ExecuteScript( 160 ctx context.Context, 161 code []byte, 162 arguments [][]byte, 163 blockHeader *flow.Header, 164 view state.View, 165 ) ([]byte, error) { 166 167 startedAt := time.Now() 168 memAllocBefore := debug.GetHeapAllocsBytes() 169 170 // allocate a random ID to be able to track this script when its done, 171 // scripts might not be unique so we use this extra tracker to follow their logs 172 // TODO: this is a temporary measure, we could remove this in the future 173 if e.log.Debug().Enabled() { 174 e.rngLock.Lock() 175 trackerID := e.rng.Uint32() 176 e.rngLock.Unlock() 177 178 trackedLogger := e.log.With().Hex("script_hex", code).Uint32("trackerID", trackerID).Logger() 179 trackedLogger.Debug().Msg("script is sent for execution") 180 defer func() { 181 trackedLogger.Debug().Msg("script execution is complete") 182 }() 183 } 184 185 requestCtx, cancel := context.WithTimeout(ctx, e.scriptExecutionTimeLimit) 186 defer cancel() 187 188 script := fvm.NewScriptWithContextAndArgs(code, requestCtx, arguments...) 189 blockCtx := fvm.NewContextFromParent( 190 e.vmCtx, 191 fvm.WithBlockHeader(blockHeader), 192 fvm.WithDerivedBlockData( 193 e.derivedChainData.NewDerivedBlockDataForScript(blockHeader.ID()))) 194 195 err := func() (err error) { 196 197 start := time.Now() 198 199 defer func() { 200 201 prepareLog := func() *zerolog.Event { 202 203 args := make([]string, 0, len(arguments)) 204 for _, a := range arguments { 205 args = append(args, hex.EncodeToString(a)) 206 } 207 return e.log.Error(). 208 Hex("script_hex", code). 209 Str("args", strings.Join(args, ",")) 210 } 211 212 elapsed := time.Since(start) 213 214 if r := recover(); r != nil { 215 prepareLog(). 216 Interface("recovered", r). 217 Msg("script execution caused runtime panic") 218 219 err = fmt.Errorf("cadence runtime error: %s", r) 220 return 221 } 222 if elapsed >= e.scriptLogThreshold { 223 prepareLog(). 224 Dur("duration", elapsed). 225 Msg("script execution exceeded threshold") 226 } 227 }() 228 229 return e.vm.Run(blockCtx, script, view) 230 }() 231 if err != nil { 232 return nil, fmt.Errorf("failed to execute script (internal error): %w", err) 233 } 234 235 if script.Err != nil { 236 scriptErrMsg := script.Err.Error() 237 if len(scriptErrMsg) > MaxScriptErrorMessageSize { 238 split := int(MaxScriptErrorMessageSize/2) - 1 239 var sb strings.Builder 240 sb.WriteString(scriptErrMsg[:split]) 241 sb.WriteString(" ... ") 242 sb.WriteString(scriptErrMsg[len(scriptErrMsg)-split:]) 243 scriptErrMsg = sb.String() 244 } 245 246 return nil, fmt.Errorf("failed to execute script at block (%s): %s", blockHeader.ID(), scriptErrMsg) 247 } 248 249 encodedValue, err := jsoncdc.Encode(script.Value) 250 if err != nil { 251 return nil, fmt.Errorf("failed to encode runtime value: %w", err) 252 } 253 254 memAllocAfter := debug.GetHeapAllocsBytes() 255 e.metrics.ExecutionScriptExecuted(time.Since(startedAt), script.GasUsed, memAllocAfter-memAllocBefore, script.MemoryEstimate) 256 257 return encodedValue, nil 258 } 259 260 func (e *Manager) ComputeBlock( 261 ctx context.Context, 262 block *entity.ExecutableBlock, 263 view state.View, 264 ) (*execution.ComputationResult, error) { 265 266 e.log.Debug(). 267 Hex("block_id", logging.Entity(block.Block)). 268 Msg("received complete block") 269 270 derivedBlockData := e.derivedChainData.GetOrCreateDerivedBlockData( 271 block.ID(), 272 block.ParentID()) 273 274 result, err := e.blockComputer.ExecuteBlock(ctx, block, view, derivedBlockData) 275 if err != nil { 276 e.log.Error(). 277 Hex("block_id", logging.Entity(block.Block)). 278 Msg("failed to compute block result") 279 280 return nil, fmt.Errorf("failed to execute block: %w", err) 281 } 282 283 e.log.Debug(). 284 Hex("block_id", logging.Entity(result.ExecutableBlock.Block)). 285 Msg("computed block result") 286 287 return result, nil 288 } 289 290 func (e *Manager) GetAccount(address flow.Address, blockHeader *flow.Header, view state.View) (*flow.Account, error) { 291 blockCtx := fvm.NewContextFromParent( 292 e.vmCtx, 293 fvm.WithBlockHeader(blockHeader), 294 fvm.WithDerivedBlockData( 295 e.derivedChainData.NewDerivedBlockDataForScript(blockHeader.ID()))) 296 297 account, err := e.vm.GetAccount(blockCtx, address, view) 298 if err != nil { 299 return nil, fmt.Errorf("failed to get account (%s) at block (%s): %w", address.String(), blockHeader.ID(), err) 300 } 301 302 return account, nil 303 }