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  }