github.com/koko1123/flow-go-1@v0.29.6/engine/collection/ingest/engine.go (about)

     1  // Package ingest implements an engine for receiving transactions that need
     2  // to be packaged into a collection.
     3  package ingest
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  
    10  	"github.com/rs/zerolog"
    11  
    12  	"github.com/koko1123/flow-go-1/access"
    13  	"github.com/koko1123/flow-go-1/engine"
    14  	"github.com/koko1123/flow-go-1/engine/common/fifoqueue"
    15  	"github.com/koko1123/flow-go-1/model/flow"
    16  	"github.com/koko1123/flow-go-1/module"
    17  	"github.com/koko1123/flow-go-1/module/component"
    18  	"github.com/koko1123/flow-go-1/module/irrecoverable"
    19  	"github.com/koko1123/flow-go-1/module/mempool/epochs"
    20  	"github.com/koko1123/flow-go-1/module/metrics"
    21  	"github.com/koko1123/flow-go-1/network"
    22  	"github.com/koko1123/flow-go-1/network/channels"
    23  	"github.com/koko1123/flow-go-1/state/protocol"
    24  	"github.com/koko1123/flow-go-1/utils/logging"
    25  )
    26  
    27  // Engine is the transaction ingestion engine, which ensures that new
    28  // transactions are delegated to the correct collection cluster, and prepared
    29  // to be included in a collection.
    30  type Engine struct {
    31  	*component.ComponentManager
    32  	log                  zerolog.Logger
    33  	engMetrics           module.EngineMetrics
    34  	colMetrics           module.CollectionMetrics
    35  	conduit              network.Conduit
    36  	me                   module.Local
    37  	state                protocol.State
    38  	pendingTransactions  engine.MessageStore
    39  	messageHandler       *engine.MessageHandler
    40  	pools                *epochs.TransactionPools
    41  	transactionValidator *access.TransactionValidator
    42  
    43  	config Config
    44  }
    45  
    46  // New creates a new collection ingest engine.
    47  func New(
    48  	log zerolog.Logger,
    49  	net network.Network,
    50  	state protocol.State,
    51  	engMetrics module.EngineMetrics,
    52  	mempoolMetrics module.MempoolMetrics,
    53  	colMetrics module.CollectionMetrics,
    54  	me module.Local,
    55  	chain flow.Chain,
    56  	pools *epochs.TransactionPools,
    57  	config Config,
    58  ) (*Engine, error) {
    59  
    60  	logger := log.With().Str("engine", "ingest").Logger()
    61  
    62  	transactionValidator := access.NewTransactionValidator(
    63  		access.NewProtocolStateBlocks(state),
    64  		chain,
    65  		access.TransactionValidationOptions{
    66  			Expiry:                 flow.DefaultTransactionExpiry,
    67  			ExpiryBuffer:           config.ExpiryBuffer,
    68  			MaxGasLimit:            config.MaxGasLimit,
    69  			CheckScriptsParse:      config.CheckScriptsParse,
    70  			MaxTransactionByteSize: config.MaxTransactionByteSize,
    71  			MaxCollectionByteSize:  config.MaxCollectionByteSize,
    72  		},
    73  	)
    74  
    75  	// FIFO queue for transactions
    76  	queue, err := fifoqueue.NewFifoQueue(
    77  		int(config.MaxMessageQueueSize),
    78  		fifoqueue.WithLengthObserver(func(len int) {
    79  			mempoolMetrics.MempoolEntries(metrics.ResourceTransactionIngestQueue, uint(len))
    80  		}),
    81  	)
    82  	if err != nil {
    83  		return nil, fmt.Errorf("could not create transaction message queue: %w", err)
    84  	}
    85  	pendingTransactions := &engine.FifoMessageStore{FifoQueue: queue}
    86  
    87  	// define how inbound messages are mapped to message queues
    88  	handler := engine.NewMessageHandler(
    89  		logger,
    90  		engine.NewNotifier(),
    91  		engine.Pattern{
    92  			Match: func(msg *engine.Message) bool {
    93  				_, ok := msg.Payload.(*flow.TransactionBody)
    94  				if ok {
    95  					engMetrics.MessageReceived(metrics.EngineCollectionIngest, metrics.MessageTransaction)
    96  				}
    97  				return ok
    98  			},
    99  			Store: pendingTransactions,
   100  		},
   101  	)
   102  
   103  	e := &Engine{
   104  		log:                  logger,
   105  		engMetrics:           engMetrics,
   106  		colMetrics:           colMetrics,
   107  		me:                   me,
   108  		state:                state,
   109  		pendingTransactions:  pendingTransactions,
   110  		messageHandler:       handler,
   111  		pools:                pools,
   112  		config:               config,
   113  		transactionValidator: transactionValidator,
   114  	}
   115  
   116  	e.ComponentManager = component.NewComponentManagerBuilder().
   117  		AddWorker(e.processQueuedTransactions).
   118  		Build()
   119  
   120  	conduit, err := net.Register(channels.PushTransactions, e)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("could not register engine: %w", err)
   123  	}
   124  	e.conduit = conduit
   125  
   126  	return e, nil
   127  }
   128  
   129  // Process processes a transaction message from the network and enqueues the
   130  // message. Validation and ingestion is performed in the processQueuedTransactions
   131  // worker.
   132  func (e *Engine) Process(channel channels.Channel, originID flow.Identifier, event interface{}) error {
   133  	select {
   134  	case <-e.ComponentManager.ShutdownSignal():
   135  		e.log.Warn().Msgf("received message from %x after shut down", originID)
   136  		return nil
   137  	default:
   138  	}
   139  
   140  	err := e.messageHandler.Process(originID, event)
   141  	if err != nil {
   142  		if engine.IsIncompatibleInputTypeError(err) {
   143  			e.log.Warn().Msgf("%v delivered unsupported message %T through %v", originID, event, channel)
   144  			return nil
   145  		}
   146  		return fmt.Errorf("unexpected error while processing engine message: %w", err)
   147  	}
   148  	return nil
   149  }
   150  
   151  // ProcessTransaction processes a transaction message submitted from another
   152  // local component. The transaction is validated and ingested synchronously.
   153  // This is used by the GRPC API, for transactions from Access nodes.
   154  func (e *Engine) ProcessTransaction(tx *flow.TransactionBody) error {
   155  	// do not process transactions after the engine has shut down
   156  	select {
   157  	case <-e.ComponentManager.ShutdownSignal():
   158  		return component.ErrComponentShutdown
   159  	default:
   160  	}
   161  
   162  	return e.onTransaction(e.me.NodeID(), tx)
   163  }
   164  
   165  // processQueuedTransactions is the main message processing loop for transaction messages.
   166  func (e *Engine) processQueuedTransactions(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
   167  	ready()
   168  
   169  	for {
   170  		select {
   171  		case <-ctx.Done():
   172  			return
   173  		case <-e.messageHandler.GetNotifier():
   174  			err := e.processAvailableMessages(ctx)
   175  			if err != nil {
   176  				// if an error reaches this point, it is unexpected
   177  				ctx.Throw(err)
   178  				return
   179  			}
   180  		}
   181  	}
   182  }
   183  
   184  // processAvailableMessages is called when the message queue is non-empty. It
   185  // will process transactions while the queue is non-empty, then return.
   186  //
   187  // All expected error conditions are handled within this function. Unexpected
   188  // errors which should cause the component to stop are passed up.
   189  func (e *Engine) processAvailableMessages(ctx context.Context) error {
   190  	for {
   191  		select {
   192  		case <-ctx.Done():
   193  			return nil
   194  		default:
   195  		}
   196  
   197  		msg, ok := e.pendingTransactions.Get()
   198  		if ok {
   199  			err := e.onTransaction(msg.OriginID, msg.Payload.(*flow.TransactionBody))
   200  			// log warnings for expected error conditions
   201  			if engine.IsUnverifiableInputError(err) {
   202  				e.log.Warn().Err(err).Msg("unable to process unverifiable transaction")
   203  			} else if engine.IsInvalidInputError(err) {
   204  				e.log.Warn().Err(err).Msg("discarding invalid transaction")
   205  			} else if err != nil {
   206  				// bubble up unexpected error
   207  				return fmt.Errorf("unexpected error handling transaction: %w", err)
   208  			}
   209  			continue
   210  		}
   211  
   212  		// when there is no more messages in the queue, back to the loop to wait
   213  		// for the next incoming message to arrive.
   214  		return nil
   215  	}
   216  }
   217  
   218  // onTransaction handles receipt of a new transaction. This can be submitted
   219  // from outside the system or routed from another collection node.
   220  //
   221  // Returns:
   222  //   - engine.UnverifiableInputError if the reference block is unknown or if the
   223  //     node is not a member of any cluster in the reference epoch.
   224  //   - engine.InvalidInputError if the transaction is invalid.
   225  //   - other error for any other unexpected error condition.
   226  func (e *Engine) onTransaction(originID flow.Identifier, tx *flow.TransactionBody) error {
   227  
   228  	defer e.engMetrics.MessageHandled(metrics.EngineCollectionIngest, metrics.MessageTransaction)
   229  
   230  	txID := tx.ID()
   231  	log := e.log.With().
   232  		Hex("origin_id", originID[:]).
   233  		Hex("tx_id", txID[:]).
   234  		Hex("ref_block_id", tx.ReferenceBlockID[:]).
   235  		Logger()
   236  
   237  	log.Info().Msg("transaction message received")
   238  
   239  	// get the state snapshot w.r.t. the reference block
   240  	refSnapshot := e.state.AtBlockID(tx.ReferenceBlockID)
   241  	// fail fast if this is an unknown reference
   242  	_, err := refSnapshot.Head()
   243  	if err != nil {
   244  		return engine.NewUnverifiableInputError("could not get reference block for transaction (%x): %w", txID, err)
   245  	}
   246  
   247  	// using the transaction's reference block, determine which cluster we're in.
   248  	// if we don't know the reference block, we will fail when attempting to query the epoch.
   249  	refEpoch := refSnapshot.Epochs().Current()
   250  
   251  	localCluster, err := e.getLocalCluster(refEpoch)
   252  	if err != nil {
   253  		return fmt.Errorf("could not get local cluster: %w", err)
   254  	}
   255  	clusters, err := refEpoch.Clustering()
   256  	if err != nil {
   257  		return fmt.Errorf("could not get clusters for reference epoch: %w", err)
   258  	}
   259  	txCluster, ok := clusters.ByTxID(txID)
   260  	if !ok {
   261  		return fmt.Errorf("could not get cluster responsible for tx: %x", txID)
   262  	}
   263  
   264  	localClusterFingerPrint := localCluster.Fingerprint()
   265  	txClusterFingerPrint := txCluster.Fingerprint()
   266  	log = log.With().
   267  		Hex("local_cluster", logging.ID(localClusterFingerPrint)).
   268  		Hex("tx_cluster", logging.ID(txClusterFingerPrint)).
   269  		Logger()
   270  
   271  	// validate and ingest the transaction, so it is eligible for inclusion in
   272  	// a future collection proposed by this node
   273  	err = e.ingestTransaction(log, refEpoch, tx, txID, localClusterFingerPrint, txClusterFingerPrint)
   274  	if err != nil {
   275  		return fmt.Errorf("could not ingest transaction: %w", err)
   276  	}
   277  
   278  	// if the message was submitted internally (ie. via the Access API)
   279  	// propagate it to members of the responsible cluster (either our cluster
   280  	// or a different cluster)
   281  	if originID == e.me.NodeID() {
   282  		e.propagateTransaction(log, tx, txCluster)
   283  	}
   284  
   285  	log.Info().Msg("transaction processed")
   286  	return nil
   287  }
   288  
   289  // getLocalCluster returns the cluster this node is a part of for the given reference epoch.
   290  // In cases where the node is not a part of any cluster, this function will differentiate
   291  // between expected and unexpected cases.
   292  //
   293  // Returns:
   294  //   - engine.UnverifiableInputError when this node is not in any cluster because it is not
   295  //     a member of the reference epoch. This is an expected condition and the transaction
   296  //     should be discarded.
   297  //   - other error for any other, unexpected error condition.
   298  func (e *Engine) getLocalCluster(refEpoch protocol.Epoch) (flow.IdentityList, error) {
   299  	epochCounter, err := refEpoch.Counter()
   300  	if err != nil {
   301  		return nil, fmt.Errorf("could not get counter for reference epoch: %w", err)
   302  	}
   303  	clusters, err := refEpoch.Clustering()
   304  	if err != nil {
   305  		return nil, fmt.Errorf("could not get clusters for reference epoch: %w", err)
   306  	}
   307  
   308  	localCluster, _, ok := clusters.ByNodeID(e.me.NodeID())
   309  	if !ok {
   310  		// if we aren't assigned to a cluster, check that we are a member of
   311  		// the reference epoch
   312  		refIdentities, err := refEpoch.InitialIdentities()
   313  		if err != nil {
   314  			return nil, fmt.Errorf("could not get initial identities for reference epoch: %w", err)
   315  		}
   316  
   317  		if _, ok := refIdentities.ByNodeID(e.me.NodeID()); ok {
   318  			// CAUTION: we are a member of the epoch, but have no assigned cluster!
   319  			// This is an unexpected condition and indicates a protocol state invariant has been broken
   320  			return nil, fmt.Errorf("this node should have an assigned cluster in epoch (counter=%d), but has none", epochCounter)
   321  		}
   322  		return nil, engine.NewUnverifiableInputError("this node is not assigned a cluster in epoch (counter=%d)", epochCounter)
   323  	}
   324  
   325  	return localCluster, nil
   326  }
   327  
   328  // ingestTransaction validates and ingests the transaction, if it is routed to
   329  // our local cluster, is valid, and has not been seen previously.
   330  //
   331  // Returns:
   332  // * engine.InvalidInputError if the transaction is invalid.
   333  // * other error for any other unexpected error condition.
   334  func (e *Engine) ingestTransaction(
   335  	log zerolog.Logger,
   336  	refEpoch protocol.Epoch,
   337  	tx *flow.TransactionBody,
   338  	txID flow.Identifier,
   339  	localClusterFingerprint flow.Identifier,
   340  	txClusterFingerprint flow.Identifier,
   341  ) error {
   342  	epochCounter, err := refEpoch.Counter()
   343  	if err != nil {
   344  		return fmt.Errorf("could not get counter for reference epoch: %w", err)
   345  	}
   346  
   347  	// use the transaction pool for the epoch the reference block is part of
   348  	pool := e.pools.ForEpoch(epochCounter)
   349  
   350  	// short-circuit if we have already stored the transaction
   351  	if pool.Has(txID) {
   352  		log.Debug().Msg("received dupe transaction")
   353  		return nil
   354  	}
   355  
   356  	// check if the transaction is valid
   357  	err = e.transactionValidator.Validate(tx)
   358  	if err != nil {
   359  		return engine.NewInvalidInputErrorf("invalid transaction (%x): %w", txID, err)
   360  	}
   361  
   362  	// if our cluster is responsible for the transaction, add it to our local mempool
   363  	if localClusterFingerprint == txClusterFingerprint {
   364  		_ = pool.Add(tx)
   365  		e.colMetrics.TransactionIngested(txID)
   366  	}
   367  
   368  	return nil
   369  }
   370  
   371  // propagateTransaction propagates the transaction to a number of the responsible
   372  // cluster's members. Any unexpected networking errors are logged.
   373  func (e *Engine) propagateTransaction(log zerolog.Logger, tx *flow.TransactionBody, txCluster flow.IdentityList) {
   374  	log.Debug().Msg("propagating transaction to cluster")
   375  
   376  	err := e.conduit.Multicast(tx, e.config.PropagationRedundancy+1, txCluster.NodeIDs()...)
   377  	if err != nil && !errors.Is(err, network.EmptyTargetList) {
   378  		// if multicast to a target cluster with at least one node failed, log an error and exit
   379  		e.log.Error().Err(err).Msg("could not route transaction to cluster")
   380  	}
   381  	if err == nil {
   382  		e.engMetrics.MessageSent(metrics.EngineCollectionIngest, metrics.MessageTransaction)
   383  	}
   384  }