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 }