github.com/onflow/flow-go@v0.33.17/cmd/collection/main.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/spf13/pflag"
     8  
     9  	client "github.com/onflow/flow-go-sdk/access/grpc"
    10  	sdkcrypto "github.com/onflow/flow-go-sdk/crypto"
    11  	"github.com/onflow/flow-go/admin/commands"
    12  	storageCommands "github.com/onflow/flow-go/admin/commands/storage"
    13  	"github.com/onflow/flow-go/cmd"
    14  	"github.com/onflow/flow-go/cmd/util/cmd/common"
    15  	"github.com/onflow/flow-go/consensus"
    16  	"github.com/onflow/flow-go/consensus/hotstuff"
    17  	"github.com/onflow/flow-go/consensus/hotstuff/committees"
    18  	"github.com/onflow/flow-go/consensus/hotstuff/notifications"
    19  	"github.com/onflow/flow-go/consensus/hotstuff/notifications/pubsub"
    20  	"github.com/onflow/flow-go/consensus/hotstuff/pacemaker/timeout"
    21  	hotsignature "github.com/onflow/flow-go/consensus/hotstuff/signature"
    22  	"github.com/onflow/flow-go/consensus/hotstuff/validator"
    23  	"github.com/onflow/flow-go/consensus/hotstuff/verification"
    24  	recovery "github.com/onflow/flow-go/consensus/recovery/protocol"
    25  	"github.com/onflow/flow-go/engine/collection/epochmgr"
    26  	"github.com/onflow/flow-go/engine/collection/epochmgr/factories"
    27  	"github.com/onflow/flow-go/engine/collection/events"
    28  	"github.com/onflow/flow-go/engine/collection/ingest"
    29  	"github.com/onflow/flow-go/engine/collection/pusher"
    30  	"github.com/onflow/flow-go/engine/collection/rpc"
    31  	followereng "github.com/onflow/flow-go/engine/common/follower"
    32  	"github.com/onflow/flow-go/engine/common/provider"
    33  	consync "github.com/onflow/flow-go/engine/common/synchronization"
    34  	"github.com/onflow/flow-go/fvm/systemcontracts"
    35  	"github.com/onflow/flow-go/model/bootstrap"
    36  	"github.com/onflow/flow-go/model/flow"
    37  	"github.com/onflow/flow-go/model/flow/filter"
    38  	"github.com/onflow/flow-go/module"
    39  	builder "github.com/onflow/flow-go/module/builder/collection"
    40  	"github.com/onflow/flow-go/module/chainsync"
    41  	modulecompliance "github.com/onflow/flow-go/module/compliance"
    42  	"github.com/onflow/flow-go/module/epochs"
    43  	confinalizer "github.com/onflow/flow-go/module/finalizer/consensus"
    44  	"github.com/onflow/flow-go/module/mempool"
    45  	epochpool "github.com/onflow/flow-go/module/mempool/epochs"
    46  	"github.com/onflow/flow-go/module/mempool/herocache"
    47  	"github.com/onflow/flow-go/module/mempool/queue"
    48  	"github.com/onflow/flow-go/module/metrics"
    49  	"github.com/onflow/flow-go/network/channels"
    50  	"github.com/onflow/flow-go/state/protocol"
    51  	badgerState "github.com/onflow/flow-go/state/protocol/badger"
    52  	"github.com/onflow/flow-go/state/protocol/blocktimer"
    53  	"github.com/onflow/flow-go/state/protocol/events/gadgets"
    54  	"github.com/onflow/flow-go/storage/badger"
    55  	"github.com/onflow/flow-go/utils/grpcutils"
    56  )
    57  
    58  func main() {
    59  
    60  	var (
    61  		txLimit                           uint
    62  		maxCollectionSize                 uint
    63  		maxCollectionByteSize             uint64
    64  		maxCollectionTotalGas             uint64
    65  		maxCollectionRequestCacheSize     uint32 // collection provider engine
    66  		collectionProviderWorkers         uint   // collection provider engine
    67  		builderExpiryBuffer               uint
    68  		builderPayerRateLimitDryRun       bool
    69  		builderPayerRateLimit             float64
    70  		builderUnlimitedPayers            []string
    71  		hotstuffMinTimeout                time.Duration
    72  		hotstuffTimeoutAdjustmentFactor   float64
    73  		hotstuffHappyPathMaxRoundFailures uint64
    74  		hotstuffProposalDuration          time.Duration
    75  		startupTimeString                 string
    76  		startupTime                       time.Time
    77  
    78  		mainConsensusCommittee  *committees.Consensus
    79  		followerState           protocol.FollowerState
    80  		ingestConf              = ingest.DefaultConfig()
    81  		rpcConf                 rpc.Config
    82  		clusterComplianceConfig modulecompliance.Config
    83  
    84  		pools               *epochpool.TransactionPools // epoch-scoped transaction pools
    85  		followerDistributor *pubsub.FollowerDistributor
    86  
    87  		push              *pusher.Engine
    88  		ing               *ingest.Engine
    89  		mainChainSyncCore *chainsync.Core
    90  		followerCore      *hotstuff.FollowerLoop // follower hotstuff logic
    91  		followerEng       *followereng.ComplianceEngine
    92  		colMetrics        module.CollectionMetrics
    93  		err               error
    94  
    95  		// epoch qc contract client
    96  		machineAccountInfo *bootstrap.NodeMachineAccountInfo
    97  		flowClientConfigs  []*common.FlowClientConfig
    98  		insecureAccessAPI  bool
    99  		accessNodeIDS      []string
   100  		apiRatelimits      map[string]int
   101  		apiBurstlimits     map[string]int
   102  	)
   103  	var deprecatedFlagBlockRateDelay time.Duration
   104  
   105  	nodeBuilder := cmd.FlowNode(flow.RoleCollection.String())
   106  	nodeBuilder.ExtraFlags(func(flags *pflag.FlagSet) {
   107  		flags.UintVar(&txLimit, "tx-limit", 50_000,
   108  			"maximum number of transactions in the memory pool")
   109  		flags.StringVarP(&rpcConf.ListenAddr, "ingress-addr", "i", "localhost:9000",
   110  			"the address the ingress server listens on")
   111  		flags.UintVar(&rpcConf.MaxMsgSize, "rpc-max-message-size", grpcutils.DefaultMaxMsgSize,
   112  			"the maximum message size in bytes for messages sent or received over grpc")
   113  		flags.BoolVar(&rpcConf.RpcMetricsEnabled, "rpc-metrics-enabled", false,
   114  			"whether to enable the rpc metrics")
   115  		flags.Uint64Var(&ingestConf.MaxGasLimit, "ingest-max-gas-limit", flow.DefaultMaxTransactionGasLimit,
   116  			"maximum per-transaction computation limit (gas limit)")
   117  		flags.Uint64Var(&ingestConf.MaxTransactionByteSize, "ingest-max-tx-byte-size", flow.DefaultMaxTransactionByteSize,
   118  			"maximum per-transaction byte size")
   119  		flags.Uint64Var(&ingestConf.MaxCollectionByteSize, "ingest-max-col-byte-size", flow.DefaultMaxCollectionByteSize,
   120  			"maximum per-collection byte size")
   121  		flags.BoolVar(&ingestConf.CheckScriptsParse, "ingest-check-scripts-parse", true,
   122  			"whether we check that inbound transactions are parse-able")
   123  		flags.UintVar(&ingestConf.ExpiryBuffer, "ingest-expiry-buffer", 30,
   124  			"expiry buffer for inbound transactions")
   125  		flags.UintVar(&ingestConf.PropagationRedundancy, "ingest-tx-propagation-redundancy", 10,
   126  			"how many additional cluster members we propagate transactions to")
   127  		flags.UintVar(&builderExpiryBuffer, "builder-expiry-buffer", builder.DefaultExpiryBuffer,
   128  			"expiry buffer for transactions in proposed collections")
   129  		flags.BoolVar(&builderPayerRateLimitDryRun, "builder-rate-limit-dry-run", false,
   130  			"determines whether rate limit configuration should be enforced (false), or only logged (true)")
   131  		flags.Float64Var(&builderPayerRateLimit, "builder-rate-limit", builder.DefaultMaxPayerTransactionRate, // no rate limiting
   132  			"rate limit for each payer (transactions/collection)")
   133  		flags.StringSliceVar(&builderUnlimitedPayers, "builder-unlimited-payers", []string{}, // no unlimited payers
   134  			"set of payer addresses which are omitted from rate limiting")
   135  		flags.UintVar(&maxCollectionSize, "builder-max-collection-size", flow.DefaultMaxCollectionSize,
   136  			"maximum number of transactions in proposed collections")
   137  		flags.Uint64Var(&maxCollectionByteSize, "builder-max-collection-byte-size", flow.DefaultMaxCollectionByteSize,
   138  			"maximum byte size of the proposed collection")
   139  		flags.Uint64Var(&maxCollectionTotalGas, "builder-max-collection-total-gas", flow.DefaultMaxCollectionTotalGas,
   140  			"maximum total amount of maxgas of transactions in proposed collections")
   141  		// Collection Nodes use a lower min timeout than Consensus Nodes (1.5s vs 2.5s) because:
   142  		//  - they tend to have higher happy-path view rate, allowing a shorter timeout
   143  		//  - since they have smaller committees, 1-2 offline replicas has a larger negative impact, which is mitigating with a smaller timeout
   144  		flags.DurationVar(&hotstuffMinTimeout, "hotstuff-min-timeout", 1500*time.Millisecond,
   145  			"the lower timeout bound for the hotstuff pacemaker, this is also used as initial timeout")
   146  		flags.Float64Var(&hotstuffTimeoutAdjustmentFactor, "hotstuff-timeout-adjustment-factor", timeout.DefaultConfig.TimeoutAdjustmentFactor,
   147  			"adjustment of timeout duration in case of time out event")
   148  		flags.Uint64Var(&hotstuffHappyPathMaxRoundFailures, "hotstuff-happy-path-max-round-failures", timeout.DefaultConfig.HappyPathMaxRoundFailures,
   149  			"number of failed rounds before first timeout increase")
   150  		flags.Uint64Var(&clusterComplianceConfig.SkipNewProposalsThreshold,
   151  			"cluster-compliance-skip-proposals-threshold", modulecompliance.DefaultConfig().SkipNewProposalsThreshold, "threshold at which new proposals are discarded rather than cached, if their height is this much above local finalized height (cluster compliance engine)")
   152  		flags.StringVar(&startupTimeString, "hotstuff-startup-time", cmd.NotSet, "specifies date and time (in ISO 8601 format) after which the consensus participant may enter the first view (e.g (e.g 1996-04-24T15:04:05-07:00))")
   153  		flags.DurationVar(&hotstuffProposalDuration, "hotstuff-proposal-duration", time.Millisecond*250, "the target time between entering a view and broadcasting the proposal for that view (different and smaller than view time)")
   154  		flags.Uint32Var(&maxCollectionRequestCacheSize, "max-collection-provider-cache-size", provider.DefaultEntityRequestCacheSize, "maximum number of collection requests to cache for collection provider")
   155  		flags.UintVar(&collectionProviderWorkers, "collection-provider-workers", provider.DefaultRequestProviderWorkers, "number of workers to use for collection provider")
   156  		// epoch qc contract flags
   157  		flags.BoolVar(&insecureAccessAPI, "insecure-access-api", false, "required if insecure GRPC connection should be used")
   158  		flags.StringSliceVar(&accessNodeIDS, "access-node-ids", []string{}, fmt.Sprintf("array of access node IDs sorted in priority order where the first ID in this array will get the first connection attempt and each subsequent ID after serves as a fallback. Minimum length %d. Use '*' for all IDs in protocol state.", common.DefaultAccessNodeIDSMinimum))
   159  		flags.StringToIntVar(&apiRatelimits, "api-rate-limits", map[string]int{}, "per second rate limits for GRPC API methods e.g. Ping=300,SendTransaction=500 etc. note limits apply globally to all clients.")
   160  		flags.StringToIntVar(&apiBurstlimits, "api-burst-limits", map[string]int{}, "burst limits for gRPC API methods e.g. Ping=100,SendTransaction=100 etc. note limits apply globally to all clients.")
   161  
   162  		// deprecated flags
   163  		flags.DurationVar(&deprecatedFlagBlockRateDelay, "block-rate-delay", 0, "the delay to broadcast block proposal in order to control block production rate")
   164  	}).ValidateFlags(func() error {
   165  		if startupTimeString != cmd.NotSet {
   166  			t, err := time.Parse(time.RFC3339, startupTimeString)
   167  			if err != nil {
   168  				return fmt.Errorf("invalid start-time value: %w", err)
   169  			}
   170  			startupTime = t
   171  		}
   172  		if deprecatedFlagBlockRateDelay > 0 {
   173  			nodeBuilder.Logger.Warn().Msg("A deprecated flag was specified (--block-rate-delay). This flag is deprecated as of v0.30 (Jun 2023), has no effect, and will eventually be removed.")
   174  		}
   175  		return nil
   176  	})
   177  
   178  	if err = nodeBuilder.Initialize(); err != nil {
   179  		nodeBuilder.Logger.Fatal().Err(err).Send()
   180  	}
   181  
   182  	nodeBuilder.
   183  		PreInit(cmd.DynamicStartPreInit).
   184  		AdminCommand("read-range-cluster-blocks", func(conf *cmd.NodeConfig) commands.AdminCommand {
   185  			clusterPayloads := badger.NewClusterPayloads(&metrics.NoopCollector{}, conf.DB)
   186  			headers, ok := conf.Storage.Headers.(*badger.Headers)
   187  			if !ok {
   188  				panic("fail to initialize admin tool, conf.Storage.Headers can not be casted as badger headers")
   189  			}
   190  			return storageCommands.NewReadRangeClusterBlocksCommand(conf.DB, headers, clusterPayloads)
   191  		}).
   192  		Module("follower distributor", func(node *cmd.NodeConfig) error {
   193  			followerDistributor = pubsub.NewFollowerDistributor()
   194  			followerDistributor.AddProposalViolationConsumer(notifications.NewSlashingViolationsConsumer(node.Logger))
   195  			return nil
   196  		}).
   197  		Module("mutable follower state", func(node *cmd.NodeConfig) error {
   198  			// For now, we only support state implementations from package badger.
   199  			// If we ever support different implementations, the following can be replaced by a type-aware factory
   200  			state, ok := node.State.(*badgerState.State)
   201  			if !ok {
   202  				return fmt.Errorf("only implementations of type badger.State are currently supported but read-only state has type %T", node.State)
   203  			}
   204  			followerState, err = badgerState.NewFollowerState(
   205  				node.Logger,
   206  				node.Tracer,
   207  				node.ProtocolEvents,
   208  				state,
   209  				node.Storage.Index,
   210  				node.Storage.Payloads,
   211  				blocktimer.DefaultBlockTimer,
   212  			)
   213  			return err
   214  		}).
   215  		Module("transactions mempool", func(node *cmd.NodeConfig) error {
   216  			create := func(epoch uint64) mempool.Transactions {
   217  				var heroCacheMetricsCollector module.HeroCacheMetrics = metrics.NewNoopCollector()
   218  				if node.BaseConfig.HeroCacheMetricsEnable {
   219  					heroCacheMetricsCollector = metrics.CollectionNodeTransactionsCacheMetrics(node.MetricsRegisterer, epoch)
   220  				}
   221  				return herocache.NewTransactions(
   222  					uint32(txLimit),
   223  					node.Logger,
   224  					heroCacheMetricsCollector)
   225  			}
   226  
   227  			pools = epochpool.NewTransactionPools(create)
   228  			err := node.Metrics.Mempool.Register(metrics.ResourceTransaction, pools.CombinedSize)
   229  			return err
   230  		}).
   231  		Module("metrics", func(node *cmd.NodeConfig) error {
   232  			colMetrics = metrics.NewCollectionCollector(node.Tracer)
   233  			return nil
   234  		}).
   235  		Module("main chain sync core", func(node *cmd.NodeConfig) error {
   236  			log := node.Logger.With().Str("sync_chain_id", node.RootChainID.String()).Logger()
   237  			mainChainSyncCore, err = chainsync.New(log, node.SyncCoreConfig, metrics.NewChainSyncCollector(node.RootChainID), node.RootChainID)
   238  			return err
   239  		}).
   240  		Module("machine account config", func(node *cmd.NodeConfig) error {
   241  			machineAccountInfo, err = cmd.LoadNodeMachineAccountInfoFile(node.BootstrapDir, node.NodeID)
   242  			return err
   243  		}).
   244  		Module("sdk client connection options", func(node *cmd.NodeConfig) error {
   245  			anIDS, err := common.ValidateAccessNodeIDSFlag(accessNodeIDS, node.RootChainID, node.State.Sealed())
   246  			if err != nil {
   247  				return fmt.Errorf("failed to validate flag --access-node-ids %w", err)
   248  			}
   249  
   250  			flowClientConfigs, err = common.FlowClientConfigs(anIDS, insecureAccessAPI, node.State.Sealed())
   251  			if err != nil {
   252  				return fmt.Errorf("failed to prepare flow client connection configs for each access node id %w", err)
   253  			}
   254  
   255  			return nil
   256  		}).
   257  		Component("machine account config validator", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   258  			// @TODO use fallback logic for flowClient similar to DKG/QC contract clients
   259  			flowClient, err := common.FlowClient(flowClientConfigs[0])
   260  			if err != nil {
   261  				return nil, fmt.Errorf("failed to get flow client connection option for access node (0): %s %w", flowClientConfigs[0].AccessAddress, err)
   262  			}
   263  
   264  			// disable balance checks for transient networks, which do not have transaction fees
   265  			var opts []epochs.MachineAccountValidatorConfigOption
   266  			if node.RootChainID.Transient() {
   267  				opts = append(opts, epochs.WithoutBalanceChecks)
   268  			}
   269  			validator, err := epochs.NewMachineAccountConfigValidator(
   270  				node.Logger,
   271  				flowClient,
   272  				flow.RoleCollection,
   273  				*machineAccountInfo,
   274  				opts...,
   275  			)
   276  
   277  			return validator, err
   278  		}).
   279  		Component("consensus committee", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   280  			// initialize consensus committee's membership state
   281  			// This committee state is for the HotStuff follower, which follows the MAIN CONSENSUS Committee
   282  			// Note: node.Me.NodeID() is not part of the consensus committee
   283  			mainConsensusCommittee, err = committees.NewConsensusCommittee(node.State, node.Me.NodeID())
   284  			node.ProtocolEvents.AddConsumer(mainConsensusCommittee)
   285  			return mainConsensusCommittee, err
   286  		}).
   287  		Component("follower core", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   288  			// create a finalizer for updating the protocol
   289  			// state when the follower detects newly finalized blocks
   290  			finalizer := confinalizer.NewFinalizer(node.DB, node.Storage.Headers, followerState, node.Tracer)
   291  			finalized, pending, err := recovery.FindLatest(node.State, node.Storage.Headers)
   292  			if err != nil {
   293  				return nil, fmt.Errorf("could not find latest finalized block and pending blocks to recover consensus follower: %w", err)
   294  			}
   295  			// creates a consensus follower with noop consumer as the notifier
   296  			followerCore, err = consensus.NewFollower(
   297  				node.Logger,
   298  				node.Metrics.Mempool,
   299  				node.Storage.Headers,
   300  				finalizer,
   301  				followerDistributor,
   302  				node.FinalizedRootBlock.Header,
   303  				node.RootQC,
   304  				finalized,
   305  				pending,
   306  			)
   307  			if err != nil {
   308  				return nil, fmt.Errorf("could not create follower core logic: %w", err)
   309  			}
   310  			return followerCore, nil
   311  		}).
   312  		Component("follower engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   313  			packer := hotsignature.NewConsensusSigDataPacker(mainConsensusCommittee)
   314  			// initialize the verifier for the protocol consensus
   315  			verifier := verification.NewCombinedVerifier(mainConsensusCommittee, packer)
   316  
   317  			validator := validator.New(mainConsensusCommittee, verifier)
   318  
   319  			var heroCacheCollector module.HeroCacheMetrics = metrics.NewNoopCollector()
   320  			if node.HeroCacheMetricsEnable {
   321  				heroCacheCollector = metrics.FollowerCacheMetrics(node.MetricsRegisterer)
   322  			}
   323  
   324  			core, err := followereng.NewComplianceCore(
   325  				node.Logger,
   326  				node.Metrics.Mempool,
   327  				heroCacheCollector,
   328  				followerDistributor,
   329  				followerState,
   330  				followerCore,
   331  				validator,
   332  				mainChainSyncCore,
   333  				node.Tracer,
   334  			)
   335  			if err != nil {
   336  				return nil, fmt.Errorf("could not create follower core: %w", err)
   337  			}
   338  
   339  			followerEng, err = followereng.NewComplianceLayer(
   340  				node.Logger,
   341  				node.EngineRegistry,
   342  				node.Me,
   343  				node.Metrics.Engine,
   344  				node.Storage.Headers,
   345  				node.LastFinalizedHeader,
   346  				core,
   347  				node.ComplianceConfig,
   348  			)
   349  			if err != nil {
   350  				return nil, fmt.Errorf("could not create follower engine: %w", err)
   351  			}
   352  			followerDistributor.AddOnBlockFinalizedConsumer(followerEng.OnFinalizedBlock)
   353  
   354  			return followerEng, nil
   355  		}).
   356  		Component("main chain sync engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   357  			spamConfig, err := consync.NewSpamDetectionConfig()
   358  			if err != nil {
   359  				return nil, fmt.Errorf("could not initialize spam detection config: %w", err)
   360  			}
   361  
   362  			// create a block synchronization engine to handle follower getting out of sync
   363  			sync, err := consync.New(
   364  				node.Logger,
   365  				node.Metrics.Engine,
   366  				node.EngineRegistry,
   367  				node.Me,
   368  				node.State,
   369  				node.Storage.Blocks,
   370  				followerEng,
   371  				mainChainSyncCore,
   372  				node.SyncEngineIdentifierProvider,
   373  				spamConfig,
   374  			)
   375  			if err != nil {
   376  				return nil, fmt.Errorf("could not create synchronization engine: %w", err)
   377  			}
   378  			followerDistributor.AddFinalizationConsumer(sync)
   379  
   380  			return sync, nil
   381  		}).
   382  		Component("ingestion engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   383  			ing, err = ingest.New(
   384  				node.Logger,
   385  				node.EngineRegistry,
   386  				node.State,
   387  				node.Metrics.Engine,
   388  				node.Metrics.Mempool,
   389  				colMetrics,
   390  				node.Me,
   391  				node.RootChainID.Chain(),
   392  				pools,
   393  				ingestConf,
   394  			)
   395  			return ing, err
   396  		}).
   397  		Component("transaction ingress rpc server", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   398  			server := rpc.New(
   399  				rpcConf,
   400  				ing,
   401  				node.Logger,
   402  				node.RootChainID,
   403  				apiRatelimits,
   404  				apiBurstlimits,
   405  			)
   406  			return server, nil
   407  		}).
   408  		Component("collection provider engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   409  			retrieve := func(collID flow.Identifier) (flow.Entity, error) {
   410  				coll, err := node.Storage.Collections.ByID(collID)
   411  				return coll, err
   412  			}
   413  
   414  			var collectionRequestMetrics module.HeroCacheMetrics = metrics.NewNoopCollector()
   415  			if node.HeroCacheMetricsEnable {
   416  				collectionRequestMetrics = metrics.CollectionRequestsQueueMetricFactory(node.MetricsRegisterer)
   417  			}
   418  			collectionRequestQueue := queue.NewHeroStore(maxCollectionRequestCacheSize, node.Logger, collectionRequestMetrics)
   419  
   420  			return provider.New(
   421  				node.Logger.With().Str("engine", "collection_provider").Logger(),
   422  				node.Metrics.Engine,
   423  				node.EngineRegistry,
   424  				node.Me,
   425  				node.State,
   426  				collectionRequestQueue,
   427  				collectionProviderWorkers,
   428  				channels.ProvideCollections,
   429  				filter.And(
   430  					filter.HasWeight(true),
   431  					filter.HasRole(flow.RoleAccess, flow.RoleExecution),
   432  				),
   433  				retrieve,
   434  			)
   435  		}).
   436  		Component("pusher engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   437  			push, err = pusher.New(
   438  				node.Logger,
   439  				node.EngineRegistry,
   440  				node.State,
   441  				node.Metrics.Engine,
   442  				colMetrics,
   443  				node.Me,
   444  				node.Storage.Collections,
   445  				node.Storage.Transactions,
   446  			)
   447  			return push, err
   448  		}).
   449  		// Epoch manager encapsulates and manages epoch-dependent engines as we
   450  		// transition between epochs
   451  		Component("epoch manager", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   452  			clusterStateFactory, err := factories.NewClusterStateFactory(node.DB, node.Metrics.Cache, node.Tracer)
   453  			if err != nil {
   454  				return nil, err
   455  			}
   456  
   457  			// convert hex string flag values to addresses
   458  			unlimitedPayers := make([]flow.Address, 0, len(builderUnlimitedPayers))
   459  			for _, payerStr := range builderUnlimitedPayers {
   460  				payerAddr := flow.HexToAddress(payerStr)
   461  				unlimitedPayers = append(unlimitedPayers, payerAddr)
   462  			}
   463  
   464  			builderFactory, err := factories.NewBuilderFactory(
   465  				node.DB,
   466  				node.State,
   467  				node.Storage.Headers,
   468  				node.Tracer,
   469  				colMetrics,
   470  				push,
   471  				node.Logger,
   472  				builder.WithMaxCollectionSize(maxCollectionSize),
   473  				builder.WithMaxCollectionByteSize(maxCollectionByteSize),
   474  				builder.WithMaxCollectionTotalGas(maxCollectionTotalGas),
   475  				builder.WithExpiryBuffer(builderExpiryBuffer),
   476  				builder.WithRateLimitDryRun(builderPayerRateLimitDryRun),
   477  				builder.WithMaxPayerTransactionRate(builderPayerRateLimit),
   478  				builder.WithUnlimitedPayers(unlimitedPayers...),
   479  			)
   480  			if err != nil {
   481  				return nil, err
   482  			}
   483  
   484  			complianceEngineFactory, err := factories.NewComplianceEngineFactory(
   485  				node.Logger,
   486  				node.EngineRegistry,
   487  				node.Me,
   488  				colMetrics,
   489  				node.Metrics.Engine,
   490  				node.Metrics.Mempool,
   491  				node.State,
   492  				node.Storage.Transactions,
   493  				clusterComplianceConfig,
   494  			)
   495  			if err != nil {
   496  				return nil, err
   497  			}
   498  
   499  			syncCoreFactory, err := factories.NewSyncCoreFactory(node.Logger, node.SyncCoreConfig)
   500  			if err != nil {
   501  				return nil, err
   502  			}
   503  
   504  			syncFactory, err := factories.NewSyncEngineFactory(
   505  				node.Logger,
   506  				node.Metrics.Engine,
   507  				node.EngineRegistry,
   508  				node.Me,
   509  			)
   510  			if err != nil {
   511  				return nil, err
   512  			}
   513  
   514  			createMetrics := func(chainID flow.ChainID) module.HotstuffMetrics {
   515  				return metrics.NewHotstuffCollector(chainID)
   516  			}
   517  
   518  			opts := []consensus.Option{
   519  				consensus.WithStaticProposalDuration(hotstuffProposalDuration),
   520  				consensus.WithMinTimeout(hotstuffMinTimeout),
   521  				consensus.WithTimeoutAdjustmentFactor(hotstuffTimeoutAdjustmentFactor),
   522  				consensus.WithHappyPathMaxRoundFailures(hotstuffHappyPathMaxRoundFailures),
   523  			}
   524  
   525  			if !startupTime.IsZero() {
   526  				opts = append(opts, consensus.WithStartupTime(startupTime))
   527  			}
   528  
   529  			hotstuffFactory, err := factories.NewHotStuffFactory(
   530  				node.Logger,
   531  				node.Me,
   532  				node.DB,
   533  				node.State,
   534  				node.Metrics.Engine,
   535  				node.Metrics.Mempool,
   536  				createMetrics,
   537  				opts...,
   538  			)
   539  			if err != nil {
   540  				return nil, err
   541  			}
   542  
   543  			signer := verification.NewStakingSigner(node.Me)
   544  
   545  			// construct QC contract client
   546  			qcContractClients, err := createQCContractClients(node, machineAccountInfo, flowClientConfigs)
   547  			if err != nil {
   548  				return nil, fmt.Errorf("could not create qc contract clients %w", err)
   549  			}
   550  
   551  			rootQCVoter := epochs.NewRootQCVoter(
   552  				node.Logger,
   553  				node.Me,
   554  				signer,
   555  				node.State,
   556  				qcContractClients,
   557  			)
   558  
   559  			messageHubFactory := factories.NewMessageHubFactory(
   560  				node.Logger,
   561  				node.EngineRegistry,
   562  				node.Me,
   563  				node.Metrics.Engine,
   564  				node.State,
   565  			)
   566  
   567  			factory := factories.NewEpochComponentsFactory(
   568  				node.Me,
   569  				pools,
   570  				builderFactory,
   571  				clusterStateFactory,
   572  				hotstuffFactory,
   573  				complianceEngineFactory,
   574  				syncCoreFactory,
   575  				syncFactory,
   576  				messageHubFactory,
   577  			)
   578  
   579  			heightEvents := gadgets.NewHeights()
   580  			node.ProtocolEvents.AddConsumer(heightEvents)
   581  
   582  			clusterEvents := events.NewDistributor()
   583  
   584  			manager, err := epochmgr.New(
   585  				node.Logger,
   586  				node.Me,
   587  				node.State,
   588  				pools,
   589  				rootQCVoter,
   590  				factory,
   591  				heightEvents,
   592  				clusterEvents,
   593  			)
   594  			if err != nil {
   595  				return nil, fmt.Errorf("could not create epoch manager: %w", err)
   596  			}
   597  
   598  			// register the manager for protocol events
   599  			node.ProtocolEvents.AddConsumer(manager)
   600  			clusterEvents.AddConsumer(node.LibP2PNode)
   601  			return manager, err
   602  		})
   603  
   604  	node, err := nodeBuilder.Build()
   605  	if err != nil {
   606  		nodeBuilder.Logger.Fatal().Err(err).Send()
   607  	}
   608  	node.Run()
   609  }
   610  
   611  // createQCContractClient creates QC contract client
   612  func createQCContractClient(node *cmd.NodeConfig, machineAccountInfo *bootstrap.NodeMachineAccountInfo, flowClient *client.Client, anID flow.Identifier) (module.QCContractClient, error) {
   613  
   614  	var qcContractClient module.QCContractClient
   615  
   616  	contracts := systemcontracts.SystemContractsForChain(node.RootChainID)
   617  	qcContractAddress := contracts.ClusterQC.Address.Hex()
   618  
   619  	// construct signer from private key
   620  	sk, err := sdkcrypto.DecodePrivateKey(machineAccountInfo.SigningAlgorithm, machineAccountInfo.EncodedPrivateKey)
   621  	if err != nil {
   622  		return nil, fmt.Errorf("could not decode private key from hex: %w", err)
   623  	}
   624  
   625  	txSigner, err := sdkcrypto.NewInMemorySigner(sk, machineAccountInfo.HashAlgorithm)
   626  	if err != nil {
   627  		return nil, fmt.Errorf("could not create in-memory signer: %w", err)
   628  	}
   629  
   630  	// create actual qc contract client, all flags and machine account info file found
   631  	qcContractClient = epochs.NewQCContractClient(
   632  		node.Logger,
   633  		flowClient,
   634  		anID,
   635  		node.Me.NodeID(),
   636  		machineAccountInfo.Address,
   637  		machineAccountInfo.KeyIndex,
   638  		qcContractAddress,
   639  		txSigner,
   640  	)
   641  
   642  	return qcContractClient, nil
   643  }
   644  
   645  // createQCContractClients creates priority ordered array of QCContractClient
   646  func createQCContractClients(node *cmd.NodeConfig, machineAccountInfo *bootstrap.NodeMachineAccountInfo, flowClientOpts []*common.FlowClientConfig) ([]module.QCContractClient, error) {
   647  	qcClients := make([]module.QCContractClient, 0)
   648  
   649  	for _, opt := range flowClientOpts {
   650  		flowClient, err := common.FlowClient(opt)
   651  		if err != nil {
   652  			return nil, fmt.Errorf("failed to create flow client for qc contract client with options: %s %w", flowClientOpts, err)
   653  		}
   654  
   655  		qcClient, err := createQCContractClient(node, machineAccountInfo, flowClient, opt.AccessNodeID)
   656  		if err != nil {
   657  			return nil, fmt.Errorf("failed to create qc contract client with flow client options: %s %w", flowClientOpts, err)
   658  		}
   659  
   660  		qcClients = append(qcClients, qcClient)
   661  	}
   662  	return qcClients, nil
   663  }