github.com/onflow/flow-go@v0.33.17/engine/execution/computation/computer/result_collector.go (about) 1 package computer 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 otelTrace "go.opentelemetry.io/otel/trace" 10 11 "github.com/onflow/flow-go/crypto" 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/storehouse" 16 "github.com/onflow/flow-go/fvm" 17 "github.com/onflow/flow-go/fvm/meter" 18 "github.com/onflow/flow-go/fvm/storage/snapshot" 19 "github.com/onflow/flow-go/fvm/storage/state" 20 "github.com/onflow/flow-go/ledger" 21 "github.com/onflow/flow-go/model/flow" 22 "github.com/onflow/flow-go/module" 23 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 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 ) 28 29 // ViewCommitter commits execution snapshot to the ledger and collects 30 // the proofs 31 type ViewCommitter interface { 32 // CommitView commits an execution snapshot and collects proofs 33 CommitView( 34 *snapshot.ExecutionSnapshot, 35 execution.ExtendableStorageSnapshot, 36 ) ( 37 flow.StateCommitment, // TODO(leo): deprecate. see storehouse.ExtendableStorageSnapshot.Commitment() 38 []byte, 39 *ledger.TrieUpdate, 40 execution.ExtendableStorageSnapshot, 41 error, 42 ) 43 } 44 45 type transactionResult struct { 46 TransactionRequest 47 *snapshot.ExecutionSnapshot 48 fvm.ProcedureOutput 49 timeSpent time.Duration 50 numConflictRetries int 51 } 52 53 // TODO(ramtin): move committer and other folks to consumers layer 54 type resultCollector struct { 55 tracer module.Tracer 56 blockSpan otelTrace.Span 57 58 metrics module.ExecutionMetrics 59 60 closeOnce sync.Once 61 processorInputChan chan transactionResult 62 processorDoneChan chan struct{} 63 processorError error 64 65 committer ViewCommitter 66 67 signer module.Local 68 spockHasher hash.Hasher 69 receiptHasher hash.Hasher 70 71 executionDataProvider provider.Provider 72 73 parentBlockExecutionResultID flow.Identifier 74 75 result *execution.ComputationResult 76 consumers []result.ExecutedCollectionConsumer 77 78 spockSignatures []crypto.Signature 79 80 blockStartTime time.Time 81 blockStats module.ExecutionResultStats 82 blockMeter *meter.Meter 83 84 currentCollectionStartTime time.Time 85 currentCollectionState *state.ExecutionState 86 currentCollectionStats module.ExecutionResultStats 87 currentCollectionStorageSnapshot execution.ExtendableStorageSnapshot 88 } 89 90 func newResultCollector( 91 tracer module.Tracer, 92 blockSpan otelTrace.Span, 93 metrics module.ExecutionMetrics, 94 committer ViewCommitter, 95 signer module.Local, 96 executionDataProvider provider.Provider, 97 spockHasher hash.Hasher, 98 receiptHasher hash.Hasher, 99 parentBlockExecutionResultID flow.Identifier, 100 block *entity.ExecutableBlock, 101 numTransactions int, 102 consumers []result.ExecutedCollectionConsumer, 103 previousBlockSnapshot snapshot.StorageSnapshot, 104 ) *resultCollector { 105 numCollections := len(block.Collections()) + 1 106 now := time.Now() 107 collector := &resultCollector{ 108 tracer: tracer, 109 blockSpan: blockSpan, 110 metrics: metrics, 111 processorInputChan: make(chan transactionResult, numTransactions), 112 processorDoneChan: make(chan struct{}), 113 committer: committer, 114 signer: signer, 115 spockHasher: spockHasher, 116 receiptHasher: receiptHasher, 117 executionDataProvider: executionDataProvider, 118 parentBlockExecutionResultID: parentBlockExecutionResultID, 119 result: execution.NewEmptyComputationResult(block), 120 consumers: consumers, 121 spockSignatures: make([]crypto.Signature, 0, numCollections), 122 blockStartTime: now, 123 blockMeter: meter.NewMeter(meter.DefaultParameters()), 124 currentCollectionStartTime: now, 125 currentCollectionState: state.NewExecutionState(nil, state.DefaultParameters()), 126 currentCollectionStats: module.ExecutionResultStats{ 127 NumberOfCollections: 1, 128 }, 129 currentCollectionStorageSnapshot: storehouse.NewExecutingBlockSnapshot( 130 previousBlockSnapshot, 131 *block.StartState, 132 ), 133 } 134 135 go collector.runResultProcessor() 136 137 return collector 138 } 139 140 func (collector *resultCollector) commitCollection( 141 collection collectionInfo, 142 startTime time.Time, 143 collectionExecutionSnapshot *snapshot.ExecutionSnapshot, 144 ) error { 145 defer collector.tracer.StartSpanFromParent( 146 collector.blockSpan, 147 trace.EXECommitDelta).End() 148 149 startState := collector.currentCollectionStorageSnapshot.Commitment() 150 151 _, proof, trieUpdate, newSnapshot, err := collector.committer.CommitView( 152 collectionExecutionSnapshot, 153 collector.currentCollectionStorageSnapshot, 154 ) 155 if err != nil { 156 return fmt.Errorf("commit view failed: %w", err) 157 } 158 159 endState := newSnapshot.Commitment() 160 collector.currentCollectionStorageSnapshot = newSnapshot 161 162 execColRes := collector.result.CollectionExecutionResultAt(collection.collectionIndex) 163 execColRes.UpdateExecutionSnapshot(collectionExecutionSnapshot) 164 165 events := execColRes.Events() 166 eventsHash, err := flow.EventsMerkleRootHash(events) 167 if err != nil { 168 return fmt.Errorf("hash events failed: %w", err) 169 } 170 171 txResults := execColRes.TransactionResults() 172 convertedTxResults := execution_data.ConvertTransactionResults(txResults) 173 174 col := collection.Collection() 175 chunkExecData := &execution_data.ChunkExecutionData{ 176 Collection: &col, 177 Events: events, 178 TrieUpdate: trieUpdate, 179 TransactionResults: convertedTxResults, 180 } 181 182 collector.result.AppendCollectionAttestationResult( 183 startState, 184 endState, 185 proof, 186 eventsHash, 187 chunkExecData, 188 ) 189 190 collector.metrics.ExecutionChunkDataPackGenerated( 191 len(proof), 192 len(collection.Transactions)) 193 194 spock, err := collector.signer.SignFunc( 195 collectionExecutionSnapshot.SpockSecret, 196 collector.spockHasher, 197 crypto.SPOCKProve) 198 if err != nil { 199 return fmt.Errorf("signing spock hash failed: %w", err) 200 } 201 202 collector.spockSignatures = append(collector.spockSignatures, spock) 203 204 collector.currentCollectionStats.EventCounts = len(events) 205 collector.currentCollectionStats.EventSize = events.ByteSize() 206 collector.currentCollectionStats.NumberOfRegistersTouched = len( 207 collectionExecutionSnapshot.AllRegisterIDs()) 208 for _, entry := range collectionExecutionSnapshot.UpdatedRegisters() { 209 collector.currentCollectionStats.NumberOfBytesWrittenToRegisters += len( 210 entry.Value) 211 } 212 213 collector.metrics.ExecutionCollectionExecuted( 214 time.Since(startTime), 215 collector.currentCollectionStats) 216 217 collector.blockStats.Merge(collector.currentCollectionStats) 218 collector.blockMeter.MergeMeter(collectionExecutionSnapshot.Meter) 219 220 collector.currentCollectionStartTime = time.Now() 221 collector.currentCollectionState = state.NewExecutionState(nil, state.DefaultParameters()) 222 collector.currentCollectionStats = module.ExecutionResultStats{ 223 NumberOfCollections: 1, 224 } 225 226 for _, consumer := range collector.consumers { 227 err = consumer.OnExecutedCollection(collector.result.CollectionExecutionResultAt(collection.collectionIndex)) 228 if err != nil { 229 return fmt.Errorf("consumer failed: %w", err) 230 } 231 } 232 233 return nil 234 } 235 236 func (collector *resultCollector) processTransactionResult( 237 txn TransactionRequest, 238 txnExecutionSnapshot *snapshot.ExecutionSnapshot, 239 output fvm.ProcedureOutput, 240 timeSpent time.Duration, 241 numConflictRetries int, 242 ) error { 243 logger := txn.ctx.Logger.With(). 244 Uint64("computation_used", output.ComputationUsed). 245 Uint64("memory_used", output.MemoryEstimate). 246 Int64("time_spent_in_ms", timeSpent.Milliseconds()). 247 Logger() 248 249 if output.Err != nil { 250 logger = logger.With(). 251 Str("error_message", output.Err.Error()). 252 Uint16("error_code", uint16(output.Err.Code())). 253 Logger() 254 logger.Info().Msg("transaction execution failed") 255 256 if txn.isSystemTransaction { 257 // This log is used as the data source for an alert on grafana. 258 // The system_chunk_error field must not be changed without adding 259 // the corresponding changes in grafana. 260 // https://github.com/dapperlabs/flow-internal/issues/1546 261 logger.Error(). 262 Bool("system_chunk_error", true). 263 Bool("system_transaction_error", true). 264 Bool("critical_error", true). 265 Msg("error executing system chunk transaction") 266 } 267 } else { 268 logger.Info().Msg("transaction executed successfully") 269 } 270 271 collector.metrics.ExecutionTransactionExecuted( 272 timeSpent, 273 numConflictRetries, 274 output.ComputationUsed, 275 output.MemoryEstimate, 276 len(output.Events), 277 flow.EventsList(output.Events).ByteSize(), 278 output.Err != nil, 279 ) 280 281 txnResult := flow.TransactionResult{ 282 TransactionID: txn.ID, 283 ComputationUsed: output.ComputationUsed, 284 MemoryUsed: output.MemoryEstimate, 285 } 286 if output.Err != nil { 287 txnResult.ErrorMessage = output.Err.Error() 288 } 289 290 collector.result. 291 CollectionExecutionResultAt(txn.collectionIndex). 292 AppendTransactionResults( 293 output.Events, 294 output.ServiceEvents, 295 output.ConvertedServiceEvents, 296 txnResult, 297 ) 298 299 err := collector.currentCollectionState.Merge(txnExecutionSnapshot) 300 if err != nil { 301 return fmt.Errorf("failed to merge into collection view: %w", err) 302 } 303 304 collector.currentCollectionStats.ComputationUsed += output.ComputationUsed 305 collector.currentCollectionStats.MemoryUsed += output.MemoryEstimate 306 collector.currentCollectionStats.NumberOfTransactions += 1 307 308 if !txn.lastTransactionInCollection { 309 return nil 310 } 311 312 return collector.commitCollection( 313 txn.collectionInfo, 314 collector.currentCollectionStartTime, 315 collector.currentCollectionState.Finalize()) 316 } 317 318 func (collector *resultCollector) AddTransactionResult( 319 request TransactionRequest, 320 snapshot *snapshot.ExecutionSnapshot, 321 output fvm.ProcedureOutput, 322 timeSpent time.Duration, 323 numConflictRetries int, 324 ) { 325 result := transactionResult{ 326 TransactionRequest: request, 327 ExecutionSnapshot: snapshot, 328 ProcedureOutput: output, 329 timeSpent: timeSpent, 330 numConflictRetries: numConflictRetries, 331 } 332 333 select { 334 case collector.processorInputChan <- result: 335 // Do nothing 336 case <-collector.processorDoneChan: 337 // Processor exited (probably due to an error) 338 } 339 } 340 341 func (collector *resultCollector) runResultProcessor() { 342 defer close(collector.processorDoneChan) 343 344 for result := range collector.processorInputChan { 345 err := collector.processTransactionResult( 346 result.TransactionRequest, 347 result.ExecutionSnapshot, 348 result.ProcedureOutput, 349 result.timeSpent, 350 result.numConflictRetries) 351 if err != nil { 352 collector.processorError = err 353 return 354 } 355 } 356 } 357 358 func (collector *resultCollector) Stop() { 359 collector.closeOnce.Do(func() { 360 close(collector.processorInputChan) 361 }) 362 } 363 364 func (collector *resultCollector) Finalize( 365 ctx context.Context, 366 ) ( 367 *execution.ComputationResult, 368 error, 369 ) { 370 collector.Stop() 371 372 <-collector.processorDoneChan 373 374 if collector.processorError != nil { 375 return nil, collector.processorError 376 } 377 378 executionDataID, executionDataRoot, err := collector.executionDataProvider.Provide( 379 ctx, 380 collector.result.Height(), 381 collector.result.BlockExecutionData) 382 if err != nil { 383 return nil, fmt.Errorf("failed to provide execution data: %w", err) 384 } 385 386 executionResult := flow.NewExecutionResult( 387 collector.parentBlockExecutionResultID, 388 collector.result.ExecutableBlock.ID(), 389 collector.result.AllChunks(), 390 collector.result.AllConvertedServiceEvents(), 391 executionDataID) 392 393 executionReceipt, err := GenerateExecutionReceipt( 394 collector.signer, 395 collector.receiptHasher, 396 executionResult, 397 collector.spockSignatures) 398 if err != nil { 399 return nil, fmt.Errorf("could not sign execution result: %w", err) 400 } 401 402 collector.result.ExecutionReceipt = executionReceipt 403 collector.result.ExecutionDataRoot = executionDataRoot 404 405 collector.metrics.ExecutionBlockExecuted( 406 time.Since(collector.blockStartTime), 407 collector.blockStats) 408 409 for kind, intensity := range collector.blockMeter.ComputationIntensities() { 410 collector.metrics.ExecutionBlockExecutionEffortVectorComponent( 411 kind.String(), 412 intensity) 413 } 414 415 return collector.result, nil 416 } 417 418 func GenerateExecutionReceipt( 419 signer module.Local, 420 receiptHasher hash.Hasher, 421 result *flow.ExecutionResult, 422 spockSignatures []crypto.Signature, 423 ) ( 424 *flow.ExecutionReceipt, 425 error, 426 ) { 427 receipt := &flow.ExecutionReceipt{ 428 ExecutionResult: *result, 429 Spocks: spockSignatures, 430 ExecutorSignature: crypto.Signature{}, 431 ExecutorID: signer.NodeID(), 432 } 433 434 // generates a signature over the execution result 435 id := receipt.ID() 436 sig, err := signer.Sign(id[:], receiptHasher) 437 if err != nil { 438 return nil, fmt.Errorf("could not sign execution result: %w", err) 439 } 440 441 receipt.ExecutorSignature = sig 442 443 return receipt, nil 444 }