github.com/koko1123/flow-go-1@v0.29.6/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  	"github.com/koko1123/flow-go-1/module/mempool/queue"
    11  
    12  	sdkcrypto "github.com/onflow/flow-go-sdk/crypto"
    13  	"github.com/koko1123/flow-go-1/cmd"
    14  	"github.com/koko1123/flow-go-1/cmd/util/cmd/common"
    15  	"github.com/koko1123/flow-go-1/consensus"
    16  	"github.com/koko1123/flow-go-1/consensus/hotstuff/committees"
    17  	"github.com/koko1123/flow-go-1/consensus/hotstuff/notifications/pubsub"
    18  	"github.com/koko1123/flow-go-1/consensus/hotstuff/pacemaker/timeout"
    19  	hotsignature "github.com/koko1123/flow-go-1/consensus/hotstuff/signature"
    20  	"github.com/koko1123/flow-go-1/consensus/hotstuff/verification"
    21  	recovery "github.com/koko1123/flow-go-1/consensus/recovery/protocol"
    22  	"github.com/koko1123/flow-go-1/engine/collection/epochmgr"
    23  	"github.com/koko1123/flow-go-1/engine/collection/epochmgr/factories"
    24  	"github.com/koko1123/flow-go-1/engine/collection/ingest"
    25  	"github.com/koko1123/flow-go-1/engine/collection/pusher"
    26  	"github.com/koko1123/flow-go-1/engine/collection/rpc"
    27  	followereng "github.com/koko1123/flow-go-1/engine/common/follower"
    28  	"github.com/koko1123/flow-go-1/engine/common/provider"
    29  	consync "github.com/koko1123/flow-go-1/engine/common/synchronization"
    30  	"github.com/koko1123/flow-go-1/fvm/systemcontracts"
    31  	"github.com/koko1123/flow-go-1/model/bootstrap"
    32  	"github.com/koko1123/flow-go-1/model/flow"
    33  	"github.com/koko1123/flow-go-1/model/flow/filter"
    34  	"github.com/koko1123/flow-go-1/module"
    35  	"github.com/koko1123/flow-go-1/module/buffer"
    36  	builder "github.com/koko1123/flow-go-1/module/builder/collection"
    37  	"github.com/koko1123/flow-go-1/module/chainsync"
    38  	modulecompliance "github.com/koko1123/flow-go-1/module/compliance"
    39  	"github.com/koko1123/flow-go-1/module/epochs"
    40  	confinalizer "github.com/koko1123/flow-go-1/module/finalizer/consensus"
    41  	"github.com/koko1123/flow-go-1/module/mempool"
    42  	epochpool "github.com/koko1123/flow-go-1/module/mempool/epochs"
    43  	"github.com/koko1123/flow-go-1/module/mempool/herocache"
    44  	"github.com/koko1123/flow-go-1/module/metrics"
    45  	"github.com/koko1123/flow-go-1/network/channels"
    46  	"github.com/koko1123/flow-go-1/state/protocol"
    47  	badgerState "github.com/koko1123/flow-go-1/state/protocol/badger"
    48  	"github.com/koko1123/flow-go-1/state/protocol/blocktimer"
    49  	"github.com/koko1123/flow-go-1/state/protocol/events/gadgets"
    50  	storagekv "github.com/koko1123/flow-go-1/storage/badger"
    51  )
    52  
    53  func main() {
    54  
    55  	var (
    56  		txLimit                                uint
    57  		maxCollectionSize                      uint
    58  		maxCollectionByteSize                  uint64
    59  		maxCollectionTotalGas                  uint64
    60  		maxCollectionRequestCacheSize          uint32 // collection provider engine
    61  		collectionProviderWorkers              uint   // collection provider engine
    62  		builderExpiryBuffer                    uint
    63  		builderPayerRateLimitDryRun            bool
    64  		builderPayerRateLimit                  float64
    65  		builderUnlimitedPayers                 []string
    66  		hotstuffTimeout                        time.Duration
    67  		hotstuffMinTimeout                     time.Duration
    68  		hotstuffTimeoutIncreaseFactor          float64
    69  		hotstuffTimeoutDecreaseFactor          float64
    70  		hotstuffTimeoutVoteAggregationFraction float64
    71  		blockRateDelay                         time.Duration
    72  		startupTimeString                      string
    73  		startupTime                            time.Time
    74  
    75  		followerState           protocol.MutableState
    76  		ingestConf              = ingest.DefaultConfig()
    77  		rpcConf                 rpc.Config
    78  		clusterComplianceConfig modulecompliance.Config
    79  
    80  		pools                   *epochpool.TransactionPools // epoch-scoped transaction pools
    81  		followerBuffer          *buffer.PendingBlocks       // pending block cache for follower
    82  		finalizationDistributor *pubsub.FinalizationDistributor
    83  		finalizedHeader         *consync.FinalizedHeaderCache
    84  
    85  		push              *pusher.Engine
    86  		ing               *ingest.Engine
    87  		mainChainSyncCore *chainsync.Core
    88  		followerEng       *followereng.Engine
    89  		colMetrics        module.CollectionMetrics
    90  		err               error
    91  
    92  		// epoch qc contract client
    93  		machineAccountInfo *bootstrap.NodeMachineAccountInfo
    94  		flowClientConfigs  []*common.FlowClientConfig
    95  		insecureAccessAPI  bool
    96  		accessNodeIDS      []string
    97  		apiRatelimits      map[string]int
    98  		apiBurstlimits     map[string]int
    99  	)
   100  
   101  	nodeBuilder := cmd.FlowNode(flow.RoleCollection.String())
   102  	nodeBuilder.ExtraFlags(func(flags *pflag.FlagSet) {
   103  		flags.UintVar(&txLimit, "tx-limit", 50_000,
   104  			"maximum number of transactions in the memory pool")
   105  		flags.StringVarP(&rpcConf.ListenAddr, "ingress-addr", "i", "localhost:9000",
   106  			"the address the ingress server listens on")
   107  		flags.BoolVar(&rpcConf.RpcMetricsEnabled, "rpc-metrics-enabled", false,
   108  			"whether to enable the rpc metrics")
   109  		flags.Uint64Var(&ingestConf.MaxGasLimit, "ingest-max-gas-limit", flow.DefaultMaxTransactionGasLimit,
   110  			"maximum per-transaction computation limit (gas limit)")
   111  		flags.Uint64Var(&ingestConf.MaxTransactionByteSize, "ingest-max-tx-byte-size", flow.DefaultMaxTransactionByteSize,
   112  			"maximum per-transaction byte size")
   113  		flags.Uint64Var(&ingestConf.MaxCollectionByteSize, "ingest-max-col-byte-size", flow.DefaultMaxCollectionByteSize,
   114  			"maximum per-collection byte size")
   115  		flags.BoolVar(&ingestConf.CheckScriptsParse, "ingest-check-scripts-parse", true,
   116  			"whether we check that inbound transactions are parse-able")
   117  		flags.UintVar(&ingestConf.ExpiryBuffer, "ingest-expiry-buffer", 30,
   118  			"expiry buffer for inbound transactions")
   119  		flags.UintVar(&ingestConf.PropagationRedundancy, "ingest-tx-propagation-redundancy", 10,
   120  			"how many additional cluster members we propagate transactions to")
   121  		flags.UintVar(&builderExpiryBuffer, "builder-expiry-buffer", builder.DefaultExpiryBuffer,
   122  			"expiry buffer for transactions in proposed collections")
   123  		flags.BoolVar(&builderPayerRateLimitDryRun, "builder-rate-limit-dry-run", false,
   124  			"determines whether rate limit configuration should be enforced (false), or only logged (true)")
   125  		flags.Float64Var(&builderPayerRateLimit, "builder-rate-limit", builder.DefaultMaxPayerTransactionRate, // no rate limiting
   126  			"rate limit for each payer (transactions/collection)")
   127  		flags.StringSliceVar(&builderUnlimitedPayers, "builder-unlimited-payers", []string{}, // no unlimited payers
   128  			"set of payer addresses which are omitted from rate limiting")
   129  		flags.UintVar(&maxCollectionSize, "builder-max-collection-size", flow.DefaultMaxCollectionSize,
   130  			"maximum number of transactions in proposed collections")
   131  		flags.Uint64Var(&maxCollectionByteSize, "builder-max-collection-byte-size", flow.DefaultMaxCollectionByteSize,
   132  			"maximum byte size of the proposed collection")
   133  		flags.Uint64Var(&maxCollectionTotalGas, "builder-max-collection-total-gas", flow.DefaultMaxCollectionTotalGas,
   134  			"maximum total amount of maxgas of transactions in proposed collections")
   135  		flags.DurationVar(&hotstuffTimeout, "hotstuff-timeout", 60*time.Second,
   136  			"the initial timeout for the hotstuff pacemaker")
   137  		flags.DurationVar(&hotstuffMinTimeout, "hotstuff-min-timeout", 2500*time.Millisecond,
   138  			"the lower timeout bound for the hotstuff pacemaker")
   139  		flags.Float64Var(&hotstuffTimeoutIncreaseFactor, "hotstuff-timeout-increase-factor",
   140  			timeout.DefaultConfig.TimeoutIncrease,
   141  			"multiplicative increase of timeout value in case of time out event")
   142  		flags.Float64Var(&hotstuffTimeoutDecreaseFactor, "hotstuff-timeout-decrease-factor",
   143  			timeout.DefaultConfig.TimeoutDecrease,
   144  			"multiplicative decrease of timeout value in case of progress")
   145  		flags.Float64Var(&hotstuffTimeoutVoteAggregationFraction, "hotstuff-timeout-vote-aggregation-fraction",
   146  			timeout.DefaultConfig.VoteAggregationTimeoutFraction,
   147  			"additional fraction of replica timeout that the primary will wait for votes")
   148  		flags.DurationVar(&blockRateDelay, "block-rate-delay", 250*time.Millisecond,
   149  			"the delay to broadcast block proposal in order to control block production rate")
   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.Uint32Var(&maxCollectionRequestCacheSize, "max-collection-provider-cache-size", provider.DefaultEntityRequestCacheSize, "maximum number of collection requests to cache for collection provider")
   154  		flags.UintVar(&collectionProviderWorkers, "collection-provider-workers", provider.DefaultRequestProviderWorkers, "number of workers to use for collection provider")
   155  		// epoch qc contract flags
   156  		flags.BoolVar(&insecureAccessAPI, "insecure-access-api", false, "required if insecure GRPC connection should be used")
   157  		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))
   158  		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.")
   159  		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.")
   160  
   161  	}).ValidateFlags(func() error {
   162  		if startupTimeString != cmd.NotSet {
   163  			t, err := time.Parse(time.RFC3339, startupTimeString)
   164  			if err != nil {
   165  				return fmt.Errorf("invalid start-time value: %w", err)
   166  			}
   167  			startupTime = t
   168  		}
   169  		return nil
   170  	})
   171  
   172  	if err = nodeBuilder.Initialize(); err != nil {
   173  		nodeBuilder.Logger.Fatal().Err(err).Send()
   174  	}
   175  
   176  	nodeBuilder.
   177  		PreInit(cmd.DynamicStartPreInit).
   178  		Module("mutable follower state", func(node *cmd.NodeConfig) error {
   179  			// For now, we only support state implementations from package badger.
   180  			// If we ever support different implementations, the following can be replaced by a type-aware factory
   181  			state, ok := node.State.(*badgerState.State)
   182  			if !ok {
   183  				return fmt.Errorf("only implementations of type badger.State are currently supported but read-only state has type %T", node.State)
   184  			}
   185  			followerState, err = badgerState.NewFollowerState(
   186  				state,
   187  				node.Storage.Index,
   188  				node.Storage.Payloads,
   189  				node.Tracer,
   190  				node.ProtocolEvents,
   191  				blocktimer.DefaultBlockTimer,
   192  			)
   193  			return err
   194  		}).
   195  		Module("transactions mempool", func(node *cmd.NodeConfig) error {
   196  			create := func(epoch uint64) mempool.Transactions {
   197  				var heroCacheMetricsCollector module.HeroCacheMetrics = metrics.NewNoopCollector()
   198  				if node.BaseConfig.HeroCacheMetricsEnable {
   199  					heroCacheMetricsCollector = metrics.CollectionNodeTransactionsCacheMetrics(node.MetricsRegisterer, epoch)
   200  				}
   201  				return herocache.NewTransactions(
   202  					uint32(txLimit),
   203  					node.Logger,
   204  					heroCacheMetricsCollector)
   205  			}
   206  
   207  			pools = epochpool.NewTransactionPools(create)
   208  			err := node.Metrics.Mempool.Register(metrics.ResourceTransaction, pools.CombinedSize)
   209  			return err
   210  		}).
   211  		Module("pending block cache", func(node *cmd.NodeConfig) error {
   212  			followerBuffer = buffer.NewPendingBlocks()
   213  			return nil
   214  		}).
   215  		Module("metrics", func(node *cmd.NodeConfig) error {
   216  			colMetrics = metrics.NewCollectionCollector(node.Tracer)
   217  			return nil
   218  		}).
   219  		Module("main chain sync core", func(node *cmd.NodeConfig) error {
   220  			mainChainSyncCore, err = chainsync.New(node.Logger, node.SyncCoreConfig, metrics.NewChainSyncCollector())
   221  			return err
   222  		}).
   223  		Module("machine account config", func(node *cmd.NodeConfig) error {
   224  			machineAccountInfo, err = cmd.LoadNodeMachineAccountInfoFile(node.BootstrapDir, node.NodeID)
   225  			return err
   226  		}).
   227  		Module("sdk client connection options", func(node *cmd.NodeConfig) error {
   228  			anIDS, err := common.ValidateAccessNodeIDSFlag(accessNodeIDS, node.RootChainID, node.State.Sealed())
   229  			if err != nil {
   230  				return fmt.Errorf("failed to validate flag --access-node-ids %w", err)
   231  			}
   232  
   233  			flowClientConfigs, err = common.FlowClientConfigs(anIDS, insecureAccessAPI, node.State.Sealed())
   234  			if err != nil {
   235  				return fmt.Errorf("failed to prepare flow client connection configs for each access node id %w", err)
   236  			}
   237  
   238  			return nil
   239  		}).
   240  		Component("machine account config validator", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   241  			//@TODO use fallback logic for flowClient similar to DKG/QC contract clients
   242  			flowClient, err := common.FlowClient(flowClientConfigs[0])
   243  			if err != nil {
   244  				return nil, fmt.Errorf("failed to get flow client connection option for access node (0): %s %w", flowClientConfigs[0].AccessAddress, err)
   245  			}
   246  
   247  			// disable balance checks for transient networks, which do not have transaction fees
   248  			var opts []epochs.MachineAccountValidatorConfigOption
   249  			if node.RootChainID.Transient() {
   250  				opts = append(opts, epochs.WithoutBalanceChecks)
   251  			}
   252  			validator, err := epochs.NewMachineAccountConfigValidator(
   253  				node.Logger,
   254  				flowClient,
   255  				flow.RoleCollection,
   256  				*machineAccountInfo,
   257  				opts...,
   258  			)
   259  
   260  			return validator, err
   261  		}).
   262  		Component("follower engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   263  
   264  			// initialize cleaner for DB
   265  			cleaner := storagekv.NewCleaner(node.Logger, node.DB, node.Metrics.CleanCollector, flow.DefaultValueLogGCFrequency)
   266  
   267  			// create a finalizer that will handling updating the protocol
   268  			// state when the follower detects newly finalized blocks
   269  			finalizer := confinalizer.NewFinalizer(node.DB, node.Storage.Headers, followerState, node.Tracer)
   270  
   271  			// initialize consensus committee's membership state
   272  			// This committee state is for the HotStuff follower, which follows the MAIN CONSENSUS Committee
   273  			// Note: node.Me.NodeID() is not part of the consensus committee
   274  			mainConsensusCommittee, err := committees.NewConsensusCommittee(node.State, node.Me.NodeID())
   275  			if err != nil {
   276  				return nil, fmt.Errorf("could not create Committee state for main consensus: %w", err)
   277  			}
   278  
   279  			packer := hotsignature.NewConsensusSigDataPacker(mainConsensusCommittee)
   280  			// initialize the verifier for the protocol consensus
   281  			verifier := verification.NewCombinedVerifier(mainConsensusCommittee, packer)
   282  
   283  			finalizationDistributor = pubsub.NewFinalizationDistributor()
   284  
   285  			finalized, pending, err := recovery.FindLatest(node.State, node.Storage.Headers)
   286  			if err != nil {
   287  				return nil, fmt.Errorf("could not find latest finalized block and pending blocks to recover consensus follower: %w", err)
   288  			}
   289  
   290  			// creates a consensus follower with noop consumer as the notifier
   291  			followerCore, err := consensus.NewFollower(
   292  				node.Logger,
   293  				mainConsensusCommittee,
   294  				node.Storage.Headers,
   295  				finalizer,
   296  				verifier,
   297  				finalizationDistributor,
   298  				node.RootBlock.Header,
   299  				node.RootQC,
   300  				finalized,
   301  				pending,
   302  			)
   303  			if err != nil {
   304  				return nil, fmt.Errorf("could not create follower core logic: %w", err)
   305  			}
   306  
   307  			followerEng, err = followereng.New(
   308  				node.Logger,
   309  				node.Network,
   310  				node.Me,
   311  				node.Metrics.Engine,
   312  				node.Metrics.Mempool,
   313  				cleaner,
   314  				node.Storage.Headers,
   315  				node.Storage.Payloads,
   316  				followerState,
   317  				followerBuffer,
   318  				followerCore,
   319  				mainChainSyncCore,
   320  				node.Tracer,
   321  				followereng.WithComplianceOptions(modulecompliance.WithSkipNewProposalsThreshold(node.ComplianceConfig.SkipNewProposalsThreshold)),
   322  			)
   323  			if err != nil {
   324  				return nil, fmt.Errorf("could not create follower engine: %w", err)
   325  			}
   326  
   327  			return followerEng, nil
   328  		}).
   329  		Component("finalized snapshot", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   330  			finalizedHeader, err = consync.NewFinalizedHeaderCache(node.Logger, node.State, finalizationDistributor)
   331  			if err != nil {
   332  				return nil, fmt.Errorf("could not create finalized snapshot cache: %w", err)
   333  			}
   334  
   335  			return finalizedHeader, nil
   336  		}).
   337  		Component("main chain sync engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   338  
   339  			// create a block synchronization engine to handle follower getting out of sync
   340  			sync, err := consync.New(
   341  				node.Logger,
   342  				node.Metrics.Engine,
   343  				node.Network,
   344  				node.Me,
   345  				node.Storage.Blocks,
   346  				followerEng,
   347  				mainChainSyncCore,
   348  				finalizedHeader,
   349  				node.SyncEngineIdentifierProvider,
   350  			)
   351  			if err != nil {
   352  				return nil, fmt.Errorf("could not create synchronization engine: %w", err)
   353  			}
   354  
   355  			return sync, nil
   356  		}).
   357  		Component("ingestion engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   358  			ing, err = ingest.New(
   359  				node.Logger,
   360  				node.Network,
   361  				node.State,
   362  				node.Metrics.Engine,
   363  				node.Metrics.Mempool,
   364  				colMetrics,
   365  				node.Me,
   366  				node.RootChainID.Chain(),
   367  				pools,
   368  				ingestConf,
   369  			)
   370  			return ing, err
   371  		}).
   372  		Component("transaction ingress rpc server", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   373  			server := rpc.New(
   374  				rpcConf,
   375  				ing,
   376  				node.Logger,
   377  				node.RootChainID,
   378  				apiRatelimits,
   379  				apiBurstlimits,
   380  			)
   381  			return server, nil
   382  		}).
   383  		Component("collection provider engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   384  			retrieve := func(collID flow.Identifier) (flow.Entity, error) {
   385  				coll, err := node.Storage.Collections.ByID(collID)
   386  				return coll, err
   387  			}
   388  
   389  			var collectionRequestMetrics module.HeroCacheMetrics = metrics.NewNoopCollector()
   390  			if node.HeroCacheMetricsEnable {
   391  				collectionRequestMetrics = metrics.CollectionRequestsQueueMetricFactory(node.MetricsRegisterer)
   392  			}
   393  			collectionRequestQueue := queue.NewHeroStore(maxCollectionRequestCacheSize, node.Logger, collectionRequestMetrics)
   394  
   395  			return provider.New(
   396  				node.Logger,
   397  				node.Metrics.Engine,
   398  				node.Network,
   399  				node.Me,
   400  				node.State,
   401  				collectionRequestQueue,
   402  				collectionProviderWorkers,
   403  				channels.ProvideCollections,
   404  				filter.HasRole(flow.RoleAccess, flow.RoleExecution),
   405  				retrieve,
   406  			)
   407  		}).
   408  		Component("pusher engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   409  			push, err = pusher.New(
   410  				node.Logger,
   411  				node.Network,
   412  				node.State,
   413  				node.Metrics.Engine,
   414  				colMetrics,
   415  				node.Me,
   416  				node.Storage.Collections,
   417  				node.Storage.Transactions,
   418  			)
   419  			return push, err
   420  		}).
   421  		// Epoch manager encapsulates and manages epoch-dependent engines as we
   422  		// transition between epochs
   423  		Component("epoch manager", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) {
   424  			clusterStateFactory, err := factories.NewClusterStateFactory(node.DB, node.Metrics.Cache, node.Tracer)
   425  			if err != nil {
   426  				return nil, err
   427  			}
   428  
   429  			// convert hex string flag values to addresses
   430  			unlimitedPayers := make([]flow.Address, 0, len(builderUnlimitedPayers))
   431  			for _, payerStr := range builderUnlimitedPayers {
   432  				payerAddr := flow.HexToAddress(payerStr)
   433  				unlimitedPayers = append(unlimitedPayers, payerAddr)
   434  			}
   435  
   436  			builderFactory, err := factories.NewBuilderFactory(
   437  				node.DB,
   438  				node.Storage.Headers,
   439  				node.Tracer,
   440  				colMetrics,
   441  				push,
   442  				node.Logger,
   443  				builder.WithMaxCollectionSize(maxCollectionSize),
   444  				builder.WithMaxCollectionByteSize(maxCollectionByteSize),
   445  				builder.WithMaxCollectionTotalGas(maxCollectionTotalGas),
   446  				builder.WithExpiryBuffer(builderExpiryBuffer),
   447  				builder.WithRateLimitDryRun(builderPayerRateLimitDryRun),
   448  				builder.WithMaxPayerTransactionRate(builderPayerRateLimit),
   449  				builder.WithUnlimitedPayers(unlimitedPayers...),
   450  			)
   451  			if err != nil {
   452  				return nil, err
   453  			}
   454  
   455  			proposalFactory, err := factories.NewProposalEngineFactory(
   456  				node.Logger,
   457  				node.Network,
   458  				node.Me,
   459  				colMetrics,
   460  				node.Metrics.Engine,
   461  				node.Metrics.Mempool,
   462  				node.State,
   463  				node.Storage.Transactions,
   464  				modulecompliance.WithSkipNewProposalsThreshold(clusterComplianceConfig.SkipNewProposalsThreshold),
   465  			)
   466  			if err != nil {
   467  				return nil, err
   468  			}
   469  
   470  			syncFactory, err := factories.NewSyncEngineFactory(
   471  				node.Logger,
   472  				node.Metrics.Engine,
   473  				node.Network,
   474  				node.Me,
   475  				node.SyncCoreConfig,
   476  			)
   477  			if err != nil {
   478  				return nil, err
   479  			}
   480  
   481  			createMetrics := func(chainID flow.ChainID) module.HotstuffMetrics {
   482  				return metrics.NewHotstuffCollector(chainID)
   483  			}
   484  
   485  			opts := []consensus.Option{
   486  				consensus.WithBlockRateDelay(blockRateDelay),
   487  				consensus.WithInitialTimeout(hotstuffTimeout),
   488  				consensus.WithMinTimeout(hotstuffMinTimeout),
   489  				consensus.WithVoteAggregationTimeoutFraction(hotstuffTimeoutVoteAggregationFraction),
   490  				consensus.WithTimeoutIncreaseFactor(hotstuffTimeoutIncreaseFactor),
   491  				consensus.WithTimeoutDecreaseFactor(hotstuffTimeoutDecreaseFactor),
   492  			}
   493  
   494  			if !startupTime.IsZero() {
   495  				opts = append(opts, consensus.WithStartupTime(startupTime))
   496  			}
   497  
   498  			hotstuffFactory, err := factories.NewHotStuffFactory(
   499  				node.Logger,
   500  				node.Me,
   501  				node.DB,
   502  				node.State,
   503  				createMetrics,
   504  				opts...,
   505  			)
   506  			if err != nil {
   507  				return nil, err
   508  			}
   509  
   510  			signer := verification.NewStakingSigner(node.Me)
   511  
   512  			// construct QC contract client
   513  			qcContractClients, err := createQCContractClients(node, machineAccountInfo, flowClientConfigs)
   514  			if err != nil {
   515  				return nil, fmt.Errorf("could not create qc contract clients %w", err)
   516  			}
   517  
   518  			rootQCVoter := epochs.NewRootQCVoter(
   519  				node.Logger,
   520  				node.Me,
   521  				signer,
   522  				node.State,
   523  				qcContractClients,
   524  			)
   525  
   526  			factory := factories.NewEpochComponentsFactory(
   527  				node.Me,
   528  				pools,
   529  				builderFactory,
   530  				clusterStateFactory,
   531  				hotstuffFactory,
   532  				proposalFactory,
   533  				syncFactory,
   534  			)
   535  
   536  			heightEvents := gadgets.NewHeights()
   537  			node.ProtocolEvents.AddConsumer(heightEvents)
   538  
   539  			manager, err := epochmgr.New(
   540  				node.Logger,
   541  				node.Me,
   542  				node.State,
   543  				pools,
   544  				rootQCVoter,
   545  				factory,
   546  				heightEvents,
   547  			)
   548  			if err != nil {
   549  				return nil, fmt.Errorf("could not create epoch manager: %w", err)
   550  			}
   551  
   552  			// register the manager for protocol events
   553  			node.ProtocolEvents.AddConsumer(manager)
   554  
   555  			return manager, err
   556  		})
   557  
   558  	node, err := nodeBuilder.Build()
   559  	if err != nil {
   560  		nodeBuilder.Logger.Fatal().Err(err).Send()
   561  	}
   562  	node.Run()
   563  }
   564  
   565  // createQCContractClient creates QC contract client
   566  func createQCContractClient(node *cmd.NodeConfig, machineAccountInfo *bootstrap.NodeMachineAccountInfo, flowClient *client.Client, anID flow.Identifier) (module.QCContractClient, error) {
   567  
   568  	var qcContractClient module.QCContractClient
   569  
   570  	contracts, err := systemcontracts.SystemContractsForChain(node.RootChainID)
   571  	if err != nil {
   572  		return nil, err
   573  	}
   574  	qcContractAddress := contracts.ClusterQC.Address.Hex()
   575  
   576  	// construct signer from private key
   577  	sk, err := sdkcrypto.DecodePrivateKey(machineAccountInfo.SigningAlgorithm, machineAccountInfo.EncodedPrivateKey)
   578  	if err != nil {
   579  		return nil, fmt.Errorf("could not decode private key from hex: %w", err)
   580  	}
   581  
   582  	txSigner, err := sdkcrypto.NewInMemorySigner(sk, machineAccountInfo.HashAlgorithm)
   583  	if err != nil {
   584  		return nil, fmt.Errorf("could not create in-memory signer: %w", err)
   585  	}
   586  
   587  	// create actual qc contract client, all flags and machine account info file found
   588  	qcContractClient = epochs.NewQCContractClient(node.Logger, flowClient, anID, node.Me.NodeID(), machineAccountInfo.Address, machineAccountInfo.KeyIndex, qcContractAddress, txSigner)
   589  
   590  	return qcContractClient, nil
   591  }
   592  
   593  // createQCContractClients creates priority ordered array of QCContractClient
   594  func createQCContractClients(node *cmd.NodeConfig, machineAccountInfo *bootstrap.NodeMachineAccountInfo, flowClientOpts []*common.FlowClientConfig) ([]module.QCContractClient, error) {
   595  	qcClients := make([]module.QCContractClient, 0)
   596  
   597  	for _, opt := range flowClientOpts {
   598  		flowClient, err := common.FlowClient(opt)
   599  		if err != nil {
   600  			return nil, fmt.Errorf("failed to create flow client for qc contract client with options: %s %w", flowClientOpts, err)
   601  		}
   602  
   603  		qcClient, err := createQCContractClient(node, machineAccountInfo, flowClient, opt.AccessNodeID)
   604  		if err != nil {
   605  			return nil, fmt.Errorf("failed to create qc contract client with flow client options: %s %w", flowClientOpts, err)
   606  		}
   607  
   608  		qcClients = append(qcClients, qcClient)
   609  	}
   610  	return qcClients, nil
   611  }