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  }