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