github.com/koko1123/flow-go-1@v0.29.6/engine/execution/computation/computer/computer.go (about) 1 package computer 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/rs/zerolog" 9 "go.opentelemetry.io/otel/attribute" 10 otelTrace "go.opentelemetry.io/otel/trace" 11 12 "github.com/koko1123/flow-go-1/engine/execution" 13 "github.com/koko1123/flow-go-1/engine/execution/utils" 14 "github.com/koko1123/flow-go-1/fvm" 15 "github.com/koko1123/flow-go-1/fvm/blueprints" 16 "github.com/koko1123/flow-go-1/fvm/derived" 17 "github.com/koko1123/flow-go-1/fvm/state" 18 "github.com/koko1123/flow-go-1/model/flow" 19 "github.com/koko1123/flow-go-1/module" 20 "github.com/koko1123/flow-go-1/module/executiondatasync/execution_data" 21 "github.com/koko1123/flow-go-1/module/executiondatasync/provider" 22 "github.com/koko1123/flow-go-1/module/mempool/entity" 23 "github.com/koko1123/flow-go-1/module/trace" 24 "github.com/koko1123/flow-go-1/utils/debug" 25 "github.com/koko1123/flow-go-1/utils/logging" 26 "github.com/onflow/flow-go/crypto/hash" 27 ) 28 29 const ( 30 SystemChunkEventCollectionMaxSize = 256_000_000 // ~256MB 31 ) 32 33 type collectionItem struct { 34 blockId string 35 36 collectionIndex int 37 38 *entity.CompleteCollection 39 40 ctx fvm.Context 41 42 isSystemCollection bool 43 } 44 45 func (collection collectionItem) Transactions( 46 startTxIndex uint32, 47 ) []transaction { 48 txns := make( 49 []transaction, 50 0, 51 len(collection.CompleteCollection.Transactions)) 52 53 logger := collection.ctx.Logger.With(). 54 Str("block_id", collection.blockId). 55 Uint64("height", collection.ctx.BlockHeader.Height). 56 Bool("system_chunk", collection.isSystemCollection). 57 Bool("system_transaction", collection.isSystemCollection). 58 Logger() 59 60 for idx, txBody := range collection.CompleteCollection.Transactions { 61 txIndex := startTxIndex + uint32(idx) 62 txns = append( 63 txns, 64 transaction{ 65 blockIdStr: collection.blockId, 66 collectionIndex: collection.collectionIndex, 67 txIndex: txIndex, 68 isSystemTransaction: collection.isSystemCollection, 69 ctx: fvm.NewContextFromParent( 70 collection.ctx, 71 fvm.WithLogger( 72 logger.With(). 73 Str("tx_id", txBody.ID().String()). 74 Uint32("tx_index", txIndex). 75 Logger())), 76 TransactionBody: txBody, 77 }) 78 } 79 80 return txns 81 } 82 83 type transaction struct { 84 blockIdStr string 85 86 collectionIndex int 87 txIndex uint32 88 89 isSystemTransaction bool 90 91 ctx fvm.Context 92 *flow.TransactionBody 93 } 94 95 // A BlockComputer executes the transactions in a block. 96 type BlockComputer interface { 97 ExecuteBlock( 98 context.Context, 99 *entity.ExecutableBlock, 100 state.View, 101 *derived.DerivedBlockData, 102 ) ( 103 *execution.ComputationResult, 104 error, 105 ) 106 } 107 108 type blockComputer struct { 109 vm fvm.VM 110 vmCtx fvm.Context 111 metrics module.ExecutionMetrics 112 tracer module.Tracer 113 log zerolog.Logger 114 systemChunkCtx fvm.Context 115 committer ViewCommitter 116 executionDataProvider *provider.Provider 117 signer module.Local 118 spockHasher hash.Hasher 119 } 120 121 func SystemChunkContext(vmCtx fvm.Context, logger zerolog.Logger) 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 ) 133 } 134 135 // NewBlockComputer creates a new block executor. 136 func NewBlockComputer( 137 vm fvm.VM, 138 vmCtx fvm.Context, 139 metrics module.ExecutionMetrics, 140 tracer module.Tracer, 141 logger zerolog.Logger, 142 committer ViewCommitter, 143 signer module.Local, 144 executionDataProvider *provider.Provider, 145 ) (BlockComputer, error) { 146 systemChunkCtx := SystemChunkContext(vmCtx, logger) 147 vmCtx = fvm.NewContextFromParent( 148 vmCtx, 149 fvm.WithMetricsReporter(metrics), 150 fvm.WithTracer(tracer)) 151 return &blockComputer{ 152 vm: vm, 153 vmCtx: vmCtx, 154 metrics: metrics, 155 tracer: tracer, 156 log: logger, 157 systemChunkCtx: systemChunkCtx, 158 committer: committer, 159 executionDataProvider: executionDataProvider, 160 signer: signer, 161 spockHasher: utils.NewSPOCKHasher(), 162 }, nil 163 } 164 165 // ExecuteBlock executes a block and returns the resulting chunks. 166 func (e *blockComputer) ExecuteBlock( 167 ctx context.Context, 168 block *entity.ExecutableBlock, 169 stateView state.View, 170 derivedBlockData *derived.DerivedBlockData, 171 ) (*execution.ComputationResult, error) { 172 173 span, _ := e.tracer.StartBlockSpan(ctx, block.ID(), trace.EXEComputeBlock) 174 span.SetAttributes(attribute.Int("collection_counts", len(block.CompleteCollections))) 175 defer span.End() 176 177 results, err := e.executeBlock(ctx, span, block, stateView, derivedBlockData) 178 if err != nil { 179 return nil, fmt.Errorf("failed to execute transactions: %w", err) 180 } 181 182 // TODO: compute block fees & reward payments 183 184 return results, nil 185 } 186 187 func (e *blockComputer) getCollections( 188 block *entity.ExecutableBlock, 189 derivedBlockData *derived.DerivedBlockData, 190 ) ( 191 []collectionItem, 192 error, 193 ) { 194 rawCollections := block.Collections() 195 collections := make([]collectionItem, 0, len(rawCollections)+1) 196 197 blockIdStr := block.ID().String() 198 199 blockCtx := fvm.NewContextFromParent( 200 e.vmCtx, 201 fvm.WithBlockHeader(block.Block.Header), 202 fvm.WithDerivedBlockData(derivedBlockData)) 203 204 for idx, collection := range rawCollections { 205 collections = append( 206 collections, 207 collectionItem{ 208 blockId: blockIdStr, 209 collectionIndex: idx, 210 CompleteCollection: collection, 211 ctx: blockCtx, 212 isSystemCollection: false, 213 }) 214 } 215 216 systemTxn, err := blueprints.SystemChunkTransaction(e.vmCtx.Chain) 217 if err != nil { 218 return nil, fmt.Errorf( 219 "could not get system chunk transaction: %w", 220 err) 221 } 222 223 collections = append( 224 collections, 225 collectionItem{ 226 blockId: blockIdStr, 227 collectionIndex: len(collections), 228 CompleteCollection: &entity.CompleteCollection{ 229 Transactions: []*flow.TransactionBody{systemTxn}, 230 }, 231 ctx: fvm.NewContextFromParent( 232 e.systemChunkCtx, 233 fvm.WithBlockHeader(block.Block.Header), 234 fvm.WithDerivedBlockData(derivedBlockData)), 235 isSystemCollection: true, 236 }) 237 238 return collections, nil 239 } 240 241 func (e *blockComputer) executeBlock( 242 ctx context.Context, 243 blockSpan otelTrace.Span, 244 block *entity.ExecutableBlock, 245 stateView state.View, 246 derivedBlockData *derived.DerivedBlockData, 247 ) (*execution.ComputationResult, error) { 248 249 // check the start state is set 250 if !block.HasStartState() { 251 return nil, fmt.Errorf("executable block start state is not set") 252 } 253 254 collections, err := e.getCollections(block, derivedBlockData) 255 if err != nil { 256 return nil, err 257 } 258 259 collector := newResultCollector( 260 e.tracer, 261 blockSpan, 262 e.metrics, 263 e.committer, 264 e.signer, 265 e.spockHasher, 266 block, 267 len(collections)) 268 defer collector.Stop() 269 270 var txIndex uint32 271 for _, collection := range collections { 272 colView := stateView.NewChild() 273 txIndex, err = e.executeCollection( 274 blockSpan, 275 txIndex, 276 colView, 277 collection, 278 collector) 279 if err != nil { 280 collectionPrefix := "" 281 if collection.isSystemCollection { 282 collectionPrefix = "system " 283 } 284 285 return nil, fmt.Errorf( 286 "failed to execute %scollection at txIndex %v: %w", 287 collectionPrefix, 288 txIndex, 289 err) 290 } 291 err = e.mergeView( 292 stateView, 293 colView, 294 blockSpan, 295 trace.EXEMergeCollectionView) 296 if err != nil { 297 return nil, fmt.Errorf("cannot merge view: %w", err) 298 } 299 } 300 301 res, err := collector.Finalize() 302 if err != nil { 303 return nil, fmt.Errorf("cannot finalize computation result: %w", err) 304 } 305 306 e.log.Debug(). 307 Hex("block_id", logging.Entity(block)). 308 Msg("all views committed") 309 310 executionDataID, err := e.executionDataProvider.Provide( 311 ctx, 312 block.Height(), 313 generateExecutionData(res, collections)) 314 if err != nil { 315 return nil, fmt.Errorf("failed to provide execution data: %w", err) 316 } 317 318 res.ExecutionDataID = executionDataID 319 320 return res, nil 321 } 322 323 func generateExecutionData( 324 res *execution.ComputationResult, 325 collections []collectionItem, 326 ) *execution_data.BlockExecutionData { 327 executionData := &execution_data.BlockExecutionData{ 328 BlockID: res.ExecutableBlock.ID(), 329 ChunkExecutionDatas: make( 330 []*execution_data.ChunkExecutionData, 331 0, 332 len(collections)), 333 } 334 335 for i, collection := range collections { 336 col := collection.Collection() 337 executionData.ChunkExecutionDatas = append(executionData.ChunkExecutionDatas, &execution_data.ChunkExecutionData{ 338 Collection: &col, 339 Events: res.Events[i], 340 TrieUpdate: res.TrieUpdates[i], 341 }) 342 } 343 344 return executionData 345 } 346 347 func (e *blockComputer) executeCollection( 348 blockSpan otelTrace.Span, 349 startTxIndex uint32, 350 collectionView state.View, 351 collection collectionItem, 352 collector *resultCollector, 353 ) (uint32, error) { 354 355 // call tracing 356 startedAt := time.Now() 357 358 txns := collection.Transactions(startTxIndex) 359 360 colSpanType := trace.EXEComputeSystemCollection 361 collectionId := "" 362 referenceBlockId := "" 363 if !collection.isSystemCollection { 364 colSpanType = trace.EXEComputeCollection 365 collectionId = collection.Guarantee.CollectionID.String() 366 referenceBlockId = collection.Guarantee.ReferenceBlockID.String() 367 } 368 369 colSpan := e.tracer.StartSpanFromParent(blockSpan, colSpanType) 370 defer colSpan.End() 371 372 colSpan.SetAttributes( 373 attribute.Int("collection.txCount", len(txns)), 374 attribute.String("collection.hash", collectionId)) 375 376 logger := e.log.With(). 377 Str("block_id", collection.blockId). 378 Str("collection_id", collectionId). 379 Str("reference_block_id", referenceBlockId). 380 Int("number_of_transactions", len(txns)). 381 Bool("system_collection", collection.isSystemCollection). 382 Logger() 383 logger.Debug().Msg("executing collection") 384 385 for _, txn := range txns { 386 err := e.executeTransaction(colSpan, txn, collectionView, collector) 387 if err != nil { 388 return txn.txIndex, err 389 } 390 } 391 392 logger.Info(). 393 Int64("time_spent_in_ms", time.Since(startedAt).Milliseconds()). 394 Msg("collection executed") 395 396 stats := collector.CommitCollection( 397 collection, 398 collectionView) 399 400 e.metrics.ExecutionCollectionExecuted(time.Since(startedAt), stats) 401 return startTxIndex + uint32(len(txns)), nil 402 } 403 404 func (e *blockComputer) executeTransaction( 405 parentSpan otelTrace.Span, 406 txn transaction, 407 collectionView state.View, 408 collector *resultCollector, 409 ) error { 410 startedAt := time.Now() 411 memAllocBefore := debug.GetHeapAllocsBytes() 412 txID := txn.ID() 413 414 // we capture two spans one for tx-based view and one for the current context (block-based) view 415 txSpan := e.tracer.StartSpanFromParent(parentSpan, trace.EXEComputeTransaction) 416 txSpan.SetAttributes( 417 attribute.String("tx_id", txID.String()), 418 attribute.Int64("tx_index", int64(txn.txIndex)), 419 attribute.Int("col_index", txn.collectionIndex), 420 ) 421 defer txSpan.End() 422 423 txInternalSpan, _ := e.tracer.StartTransactionSpan(context.Background(), txID, trace.EXERunTransaction) 424 txInternalSpan.SetAttributes(attribute.String("tx_id", txID.String())) 425 defer txInternalSpan.End() 426 427 logger := e.log.With(). 428 Str("tx_id", txID.String()). 429 Uint32("tx_index", txn.txIndex). 430 Str("block_id", txn.blockIdStr). 431 Str("trace_id", txInternalSpan.SpanContext().TraceID().String()). 432 Uint64("height", txn.ctx.BlockHeader.Height). 433 Bool("system_chunk", txn.isSystemTransaction). 434 Bool("system_transaction", txn.isSystemTransaction). 435 Logger() 436 logger.Info().Msg("executing transaction in fvm") 437 438 proc := fvm.Transaction(txn.TransactionBody, txn.txIndex) 439 440 txn.ctx = fvm.NewContextFromParent( 441 txn.ctx, 442 fvm.WithSpan(txInternalSpan)) 443 444 txView := collectionView.NewChild() 445 err := e.vm.Run(txn.ctx, proc, txView) 446 if err != nil { 447 return fmt.Errorf("failed to execute transaction %v for block %s at height %v: %w", 448 txID.String(), 449 txn.blockIdStr, 450 txn.ctx.BlockHeader.Height, 451 err) 452 } 453 454 postProcessSpan := e.tracer.StartSpanFromParent(txSpan, trace.EXEPostProcessTransaction) 455 defer postProcessSpan.End() 456 457 // always merge the view, fvm take cares of reverting changes 458 // of failed transaction invocation 459 460 err = e.mergeView(collectionView, txView, postProcessSpan, trace.EXEMergeTransactionView) 461 if err != nil { 462 return fmt.Errorf("merging tx view to collection view failed for tx %v: %w", 463 txID.String(), err) 464 } 465 466 collector.AddTransactionResult(txn.collectionIndex, proc) 467 468 memAllocAfter := debug.GetHeapAllocsBytes() 469 470 logger = logger.With(). 471 Uint64("computation_used", proc.ComputationUsed). 472 Uint64("memory_used", proc.MemoryEstimate). 473 Uint64("mem_alloc", memAllocAfter-memAllocBefore). 474 Int64("time_spent_in_ms", time.Since(startedAt).Milliseconds()). 475 Logger() 476 477 if proc.Err != nil { 478 logger = logger.With(). 479 Str("error_message", proc.Err.Error()). 480 Uint16("error_code", uint16(proc.Err.Code())). 481 Logger() 482 logger.Info().Msg("transaction execution failed") 483 484 if txn.isSystemTransaction { 485 // This log is used as the data source for an alert on grafana. 486 // The system_chunk_error field must not be changed without adding 487 // the corresponding changes in grafana. 488 // https://github.com/dapperlabs/flow-internal/issues/1546 489 logger.Error(). 490 Bool("system_chunk_error", true). 491 Bool("system_transaction_error", true). 492 Bool("critical_error", true). 493 Msg("error executing system chunk transaction") 494 } 495 } else { 496 logger.Info().Msg("transaction executed successfully") 497 } 498 499 e.metrics.ExecutionTransactionExecuted( 500 time.Since(startedAt), 501 proc.ComputationUsed, 502 proc.MemoryEstimate, 503 memAllocAfter-memAllocBefore, 504 len(proc.Events), 505 flow.EventsList(proc.Events).ByteSize(), 506 proc.Err != nil, 507 ) 508 return nil 509 } 510 511 func (e *blockComputer) mergeView( 512 parent, child state.View, 513 parentSpan otelTrace.Span, 514 mergeSpanName trace.SpanName) error { 515 516 mergeSpan := e.tracer.StartSpanFromParent(parentSpan, mergeSpanName) 517 defer mergeSpan.End() 518 519 return parent.MergeView(child) 520 }