github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/computation/computer/computer.go (about) 1 package computer 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/onflow/crypto/hash" 9 "github.com/rs/zerolog" 10 "go.opentelemetry.io/otel/attribute" 11 otelTrace "go.opentelemetry.io/otel/trace" 12 13 "github.com/onflow/flow-go/engine/execution" 14 "github.com/onflow/flow-go/engine/execution/computation/result" 15 "github.com/onflow/flow-go/engine/execution/utils" 16 "github.com/onflow/flow-go/fvm" 17 "github.com/onflow/flow-go/fvm/blueprints" 18 "github.com/onflow/flow-go/fvm/storage/derived" 19 "github.com/onflow/flow-go/fvm/storage/errors" 20 "github.com/onflow/flow-go/fvm/storage/logical" 21 "github.com/onflow/flow-go/fvm/storage/snapshot" 22 "github.com/onflow/flow-go/model/flow" 23 "github.com/onflow/flow-go/module" 24 "github.com/onflow/flow-go/module/executiondatasync/provider" 25 "github.com/onflow/flow-go/module/mempool/entity" 26 "github.com/onflow/flow-go/module/trace" 27 "github.com/onflow/flow-go/state/protocol" 28 "github.com/onflow/flow-go/utils/logging" 29 ) 30 31 const ( 32 SystemChunkEventCollectionMaxSize = 256_000_000 // ~256MB 33 ) 34 35 type collectionInfo struct { 36 blockId flow.Identifier 37 blockIdStr string 38 39 collectionIndex int 40 *entity.CompleteCollection 41 42 isSystemTransaction bool 43 } 44 45 type TransactionRequest struct { 46 collectionInfo 47 48 txnId flow.Identifier 49 txnIdStr string 50 51 txnIndex uint32 52 53 lastTransactionInCollection bool 54 55 ctx fvm.Context 56 *fvm.TransactionProcedure 57 } 58 59 func newTransactionRequest( 60 collection collectionInfo, 61 collectionCtx fvm.Context, 62 collectionLogger zerolog.Logger, 63 txnIndex uint32, 64 txnBody *flow.TransactionBody, 65 lastTransactionInCollection bool, 66 ) TransactionRequest { 67 txnId := txnBody.ID() 68 txnIdStr := txnId.String() 69 70 return TransactionRequest{ 71 collectionInfo: collection, 72 txnId: txnId, 73 txnIdStr: txnIdStr, 74 txnIndex: txnIndex, 75 ctx: fvm.NewContextFromParent( 76 collectionCtx, 77 fvm.WithLogger( 78 collectionLogger.With(). 79 Str("tx_id", txnIdStr). 80 Uint32("tx_index", txnIndex). 81 Logger())), 82 TransactionProcedure: fvm.NewTransaction( 83 txnId, 84 txnIndex, 85 txnBody), 86 lastTransactionInCollection: lastTransactionInCollection, 87 } 88 } 89 90 // A BlockComputer executes the transactions in a block. 91 type BlockComputer interface { 92 ExecuteBlock( 93 ctx context.Context, 94 parentBlockExecutionResultID flow.Identifier, 95 block *entity.ExecutableBlock, 96 snapshot snapshot.StorageSnapshot, 97 derivedBlockData *derived.DerivedBlockData, 98 ) ( 99 *execution.ComputationResult, 100 error, 101 ) 102 } 103 104 type blockComputer struct { 105 vm fvm.VM 106 vmCtx fvm.Context 107 metrics module.ExecutionMetrics 108 tracer module.Tracer 109 log zerolog.Logger 110 systemChunkCtx fvm.Context 111 committer ViewCommitter 112 executionDataProvider provider.Provider 113 signer module.Local 114 spockHasher hash.Hasher 115 receiptHasher hash.Hasher 116 colResCons []result.ExecutedCollectionConsumer 117 protocolState protocol.State 118 maxConcurrency int 119 } 120 121 func SystemChunkContext(vmCtx fvm.Context) fvm.Context { 122 return fvm.NewContextFromParent( 123 vmCtx, 124 fvm.WithContractDeploymentRestricted(false), 125 fvm.WithContractRemovalRestricted(false), 126 fvm.WithAuthorizationChecksEnabled(false), 127 fvm.WithSequenceNumberCheckAndIncrementEnabled(false), 128 fvm.WithTransactionFeesEnabled(false), 129 fvm.WithEventCollectionSizeLimit(SystemChunkEventCollectionMaxSize), 130 fvm.WithMemoryAndInteractionLimitsDisabled(), 131 // only the system transaction is allowed to call the block entropy provider 132 fvm.WithRandomSourceHistoryCallAllowed(true), 133 ) 134 } 135 136 // NewBlockComputer creates a new block executor. 137 func NewBlockComputer( 138 vm fvm.VM, 139 vmCtx fvm.Context, 140 metrics module.ExecutionMetrics, 141 tracer module.Tracer, 142 logger zerolog.Logger, 143 committer ViewCommitter, 144 signer module.Local, 145 executionDataProvider provider.Provider, 146 colResCons []result.ExecutedCollectionConsumer, 147 state protocol.State, 148 maxConcurrency int, 149 ) (BlockComputer, error) { 150 if maxConcurrency < 1 { 151 return nil, fmt.Errorf("invalid maxConcurrency: %d", maxConcurrency) 152 } 153 154 // this is a safeguard to prevent scripts from writing to the program cache on Execution nodes. 155 // writes are only allowed by transactions. 156 if vmCtx.AllowProgramCacheWritesInScripts { 157 return nil, fmt.Errorf("program cache writes are not allowed in scripts on Execution nodes") 158 } 159 160 systemChunkCtx := SystemChunkContext(vmCtx) 161 vmCtx = fvm.NewContextFromParent( 162 vmCtx, 163 fvm.WithMetricsReporter(metrics), 164 fvm.WithTracer(tracer)) 165 return &blockComputer{ 166 vm: vm, 167 vmCtx: vmCtx, 168 metrics: metrics, 169 tracer: tracer, 170 log: logger, 171 systemChunkCtx: systemChunkCtx, 172 committer: committer, 173 executionDataProvider: executionDataProvider, 174 signer: signer, 175 spockHasher: utils.NewSPOCKHasher(), 176 receiptHasher: utils.NewExecutionReceiptHasher(), 177 colResCons: colResCons, 178 protocolState: state, 179 maxConcurrency: maxConcurrency, 180 }, nil 181 } 182 183 // ExecuteBlock executes a block and returns the resulting chunks. 184 func (e *blockComputer) ExecuteBlock( 185 ctx context.Context, 186 parentBlockExecutionResultID flow.Identifier, 187 block *entity.ExecutableBlock, 188 snapshot snapshot.StorageSnapshot, 189 derivedBlockData *derived.DerivedBlockData, 190 ) ( 191 *execution.ComputationResult, 192 error, 193 ) { 194 results, err := e.executeBlock( 195 ctx, 196 parentBlockExecutionResultID, 197 block, 198 snapshot, 199 derivedBlockData) 200 if err != nil { 201 return nil, fmt.Errorf("failed to execute transactions: %w", err) 202 } 203 204 return results, nil 205 } 206 207 func (e *blockComputer) queueTransactionRequests( 208 blockId flow.Identifier, 209 blockIdStr string, 210 blockHeader *flow.Header, 211 rawCollections []*entity.CompleteCollection, 212 systemTxnBody *flow.TransactionBody, 213 requestQueue chan TransactionRequest, 214 numTxns int, 215 ) { 216 txnIndex := uint32(0) 217 218 collectionCtx := fvm.NewContextFromParent( 219 e.vmCtx, 220 fvm.WithBlockHeader(blockHeader), 221 // `protocol.Snapshot` implements `EntropyProvider` interface 222 // Note that `Snapshot` possible errors for RandomSource() are: 223 // - storage.ErrNotFound if the QC is unknown. 224 // - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown 225 // However, at this stage, snapshot reference block should be known and the QC should also be known, 226 // so no error is expected in normal operations, as required by `EntropyProvider`. 227 fvm.WithEntropyProvider(e.protocolState.AtBlockID(blockId)), 228 ) 229 230 for idx, collection := range rawCollections { 231 collectionLogger := collectionCtx.Logger.With(). 232 Str("block_id", blockIdStr). 233 Uint64("height", blockHeader.Height). 234 Bool("system_chunk", false). 235 Bool("system_transaction", false). 236 Logger() 237 238 collectionInfo := collectionInfo{ 239 blockId: blockId, 240 blockIdStr: blockIdStr, 241 collectionIndex: idx, 242 CompleteCollection: collection, 243 isSystemTransaction: false, 244 } 245 246 for i, txnBody := range collection.Transactions { 247 requestQueue <- newTransactionRequest( 248 collectionInfo, 249 collectionCtx, 250 collectionLogger, 251 txnIndex, 252 txnBody, 253 i == len(collection.Transactions)-1) 254 txnIndex += 1 255 } 256 257 } 258 259 systemCtx := fvm.NewContextFromParent( 260 e.systemChunkCtx, 261 fvm.WithBlockHeader(blockHeader), 262 // `protocol.Snapshot` implements `EntropyProvider` interface 263 // Note that `Snapshot` possible errors for RandomSource() are: 264 // - storage.ErrNotFound if the QC is unknown. 265 // - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown 266 // However, at this stage, snapshot reference block should be known and the QC should also be known, 267 // so no error is expected in normal operations, as required by `EntropyProvider`. 268 fvm.WithEntropyProvider(e.protocolState.AtBlockID(blockId)), 269 ) 270 systemCollectionLogger := systemCtx.Logger.With(). 271 Str("block_id", blockIdStr). 272 Uint64("height", blockHeader.Height). 273 Bool("system_chunk", true). 274 Bool("system_transaction", true). 275 Int("num_collections", len(rawCollections)). 276 Int("num_txs", numTxns). 277 Logger() 278 systemCollectionInfo := collectionInfo{ 279 blockId: blockId, 280 blockIdStr: blockIdStr, 281 collectionIndex: len(rawCollections), 282 CompleteCollection: &entity.CompleteCollection{ 283 Transactions: []*flow.TransactionBody{systemTxnBody}, 284 }, 285 isSystemTransaction: true, 286 } 287 288 requestQueue <- newTransactionRequest( 289 systemCollectionInfo, 290 systemCtx, 291 systemCollectionLogger, 292 txnIndex, 293 systemTxnBody, 294 true) 295 } 296 297 func numberOfTransactionsInBlock(collections []*entity.CompleteCollection) int { 298 numTxns := 1 // there's one system transaction per block 299 for _, collection := range collections { 300 numTxns += len(collection.Transactions) 301 } 302 303 return numTxns 304 } 305 306 func (e *blockComputer) executeBlock( 307 ctx context.Context, 308 parentBlockExecutionResultID flow.Identifier, 309 block *entity.ExecutableBlock, 310 baseSnapshot snapshot.StorageSnapshot, 311 derivedBlockData *derived.DerivedBlockData, 312 ) ( 313 *execution.ComputationResult, 314 error, 315 ) { 316 // check the start state is set 317 if !block.HasStartState() { 318 return nil, fmt.Errorf("executable block start state is not set") 319 } 320 321 blockId := block.ID() 322 blockIdStr := blockId.String() 323 324 rawCollections := block.Collections() 325 326 blockSpan := e.tracer.StartSpanFromParent( 327 e.tracer.BlockRootSpan(blockId), 328 trace.EXEComputeBlock) 329 blockSpan.SetAttributes( 330 attribute.String("block_id", blockIdStr), 331 attribute.Int("collection_counts", len(rawCollections))) 332 defer blockSpan.End() 333 334 systemTxn, err := blueprints.SystemChunkTransaction(e.vmCtx.Chain) 335 if err != nil { 336 return nil, fmt.Errorf( 337 "could not get system chunk transaction: %w", 338 err) 339 } 340 341 numTxns := numberOfTransactionsInBlock(rawCollections) 342 343 collector := newResultCollector( 344 e.tracer, 345 blockSpan, 346 e.metrics, 347 e.committer, 348 e.signer, 349 e.executionDataProvider, 350 e.spockHasher, 351 e.receiptHasher, 352 parentBlockExecutionResultID, 353 block, 354 numTxns, 355 e.colResCons, 356 baseSnapshot, 357 ) 358 defer collector.Stop() 359 360 requestQueue := make(chan TransactionRequest, numTxns) 361 362 database := newTransactionCoordinator( 363 e.vm, 364 baseSnapshot, 365 derivedBlockData, 366 collector) 367 368 e.queueTransactionRequests( 369 blockId, 370 blockIdStr, 371 block.Block.Header, 372 rawCollections, 373 systemTxn, 374 requestQueue, 375 numTxns, 376 ) 377 close(requestQueue) 378 379 wg := &sync.WaitGroup{} 380 wg.Add(e.maxConcurrency) 381 382 for i := 0; i < e.maxConcurrency; i++ { 383 go e.executeTransactions( 384 blockSpan, 385 database, 386 requestQueue, 387 wg) 388 } 389 390 wg.Wait() 391 392 err = database.Error() 393 if err != nil { 394 return nil, err 395 } 396 397 res, err := collector.Finalize(ctx) 398 if err != nil { 399 return nil, fmt.Errorf("cannot finalize computation result: %w", err) 400 } 401 402 e.log.Debug(). 403 Hex("block_id", logging.Entity(block)). 404 Msg("all views committed") 405 406 e.metrics.ExecutionBlockCachedPrograms(derivedBlockData.CachedPrograms()) 407 408 return res, nil 409 } 410 411 func (e *blockComputer) executeTransactions( 412 blockSpan otelTrace.Span, 413 database *transactionCoordinator, 414 requestQueue chan TransactionRequest, 415 wg *sync.WaitGroup, 416 ) { 417 defer wg.Done() 418 419 for request := range requestQueue { 420 attempt := 0 421 for { 422 request.ctx.Logger.Info(). 423 Int("attempt", attempt). 424 Msg("executing transaction") 425 426 attempt += 1 427 err := e.executeTransaction(blockSpan, database, request, attempt) 428 429 if errors.IsRetryableConflictError(err) { 430 request.ctx.Logger.Info(). 431 Int("attempt", attempt). 432 Str("conflict_error", err.Error()). 433 Msg("conflict detected. retrying transaction") 434 continue 435 } 436 437 if err != nil { 438 database.AbortAllOutstandingTransactions(err) 439 return 440 } 441 442 break // process next transaction 443 } 444 } 445 } 446 447 func (e *blockComputer) executeTransaction( 448 blockSpan otelTrace.Span, 449 database *transactionCoordinator, 450 request TransactionRequest, 451 attempt int, 452 ) error { 453 txn, err := e.executeTransactionInternal( 454 blockSpan, 455 database, 456 request, 457 attempt) 458 if err != nil { 459 prefix := "" 460 if request.isSystemTransaction { 461 prefix = "system " 462 } 463 464 snapshotTime := logical.Time(0) 465 if txn != nil { 466 snapshotTime = txn.SnapshotTime() 467 } 468 469 return fmt.Errorf( 470 "failed to execute %stransaction %v (%d@%d) for block %s "+ 471 "at height %v: %w", 472 prefix, 473 request.txnIdStr, 474 request.txnIndex, 475 snapshotTime, 476 request.blockIdStr, 477 request.ctx.BlockHeader.Height, 478 err) 479 } 480 481 return nil 482 } 483 484 func (e *blockComputer) executeTransactionInternal( 485 blockSpan otelTrace.Span, 486 database *transactionCoordinator, 487 request TransactionRequest, 488 attempt int, 489 ) ( 490 *transaction, 491 error, 492 ) { 493 txSpan := e.tracer.StartSampledSpanFromParent( 494 blockSpan, 495 request.txnId, 496 trace.EXEComputeTransaction) 497 txSpan.SetAttributes( 498 attribute.String("tx_id", request.txnIdStr), 499 attribute.Int64("tx_index", int64(request.txnIndex)), 500 attribute.Int("col_index", request.collectionIndex), 501 ) 502 defer txSpan.End() 503 504 request.ctx = fvm.NewContextFromParent(request.ctx, fvm.WithSpan(txSpan)) 505 506 txn, err := database.NewTransaction(request, attempt) 507 if err != nil { 508 return nil, err 509 } 510 defer txn.Cleanup() 511 512 err = txn.Preprocess() 513 if err != nil { 514 return txn, err 515 } 516 517 // Validating here gives us an opportunity to early abort/retry the 518 // transaction in case the conflict is detectable after preprocessing. 519 // This is strictly an optimization and hence we don't need to wait for 520 // updates (removing this validate call won't impact correctness). 521 err = txn.Validate() 522 if err != nil { 523 return txn, err 524 } 525 526 err = txn.Execute() 527 if err != nil { 528 return txn, err 529 } 530 531 err = txn.Finalize() 532 if err != nil { 533 return txn, err 534 } 535 536 // Snapshot time smaller than execution time indicates there are outstanding 537 // transaction(s) that must be committed before this transaction can be 538 // committed. 539 for txn.SnapshotTime() < request.ExecutionTime() { 540 err = txn.WaitForUpdates() 541 if err != nil { 542 return txn, err 543 } 544 545 err = txn.Validate() 546 if err != nil { 547 return txn, err 548 } 549 } 550 551 return txn, txn.Commit() 552 }