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 }