github.com/onflow/flow-go@v0.33.17/cmd/consensus/main.go (about) 1 // (c) 2019 Dapper Labs - ALL RIGHTS RESERVED 2 3 package main 4 5 import ( 6 "encoding/json" 7 "errors" 8 "fmt" 9 "os" 10 "path/filepath" 11 "time" 12 13 "github.com/spf13/pflag" 14 15 client "github.com/onflow/flow-go-sdk/access/grpc" 16 "github.com/onflow/flow-go-sdk/crypto" 17 "github.com/onflow/flow-go/cmd" 18 "github.com/onflow/flow-go/cmd/util/cmd/common" 19 "github.com/onflow/flow-go/consensus" 20 "github.com/onflow/flow-go/consensus/hotstuff" 21 "github.com/onflow/flow-go/consensus/hotstuff/blockproducer" 22 "github.com/onflow/flow-go/consensus/hotstuff/committees" 23 "github.com/onflow/flow-go/consensus/hotstuff/cruisectl" 24 "github.com/onflow/flow-go/consensus/hotstuff/notifications" 25 "github.com/onflow/flow-go/consensus/hotstuff/notifications/pubsub" 26 "github.com/onflow/flow-go/consensus/hotstuff/pacemaker/timeout" 27 "github.com/onflow/flow-go/consensus/hotstuff/persister" 28 hotsignature "github.com/onflow/flow-go/consensus/hotstuff/signature" 29 "github.com/onflow/flow-go/consensus/hotstuff/timeoutcollector" 30 "github.com/onflow/flow-go/consensus/hotstuff/verification" 31 "github.com/onflow/flow-go/consensus/hotstuff/votecollector" 32 recovery "github.com/onflow/flow-go/consensus/recovery/protocol" 33 "github.com/onflow/flow-go/engine/common/requester" 34 synceng "github.com/onflow/flow-go/engine/common/synchronization" 35 "github.com/onflow/flow-go/engine/consensus/approvals/tracker" 36 "github.com/onflow/flow-go/engine/consensus/compliance" 37 dkgeng "github.com/onflow/flow-go/engine/consensus/dkg" 38 "github.com/onflow/flow-go/engine/consensus/ingestion" 39 "github.com/onflow/flow-go/engine/consensus/matching" 40 "github.com/onflow/flow-go/engine/consensus/message_hub" 41 "github.com/onflow/flow-go/engine/consensus/sealing" 42 "github.com/onflow/flow-go/fvm/systemcontracts" 43 "github.com/onflow/flow-go/model/bootstrap" 44 "github.com/onflow/flow-go/model/encodable" 45 "github.com/onflow/flow-go/model/flow" 46 "github.com/onflow/flow-go/model/flow/filter" 47 "github.com/onflow/flow-go/module" 48 "github.com/onflow/flow-go/module/buffer" 49 builder "github.com/onflow/flow-go/module/builder/consensus" 50 "github.com/onflow/flow-go/module/chainsync" 51 chmodule "github.com/onflow/flow-go/module/chunks" 52 dkgmodule "github.com/onflow/flow-go/module/dkg" 53 "github.com/onflow/flow-go/module/epochs" 54 finalizer "github.com/onflow/flow-go/module/finalizer/consensus" 55 "github.com/onflow/flow-go/module/mempool" 56 consensusMempools "github.com/onflow/flow-go/module/mempool/consensus" 57 "github.com/onflow/flow-go/module/mempool/stdmap" 58 "github.com/onflow/flow-go/module/metrics" 59 msig "github.com/onflow/flow-go/module/signature" 60 "github.com/onflow/flow-go/module/updatable_configs" 61 "github.com/onflow/flow-go/module/util" 62 "github.com/onflow/flow-go/module/validation" 63 "github.com/onflow/flow-go/network/channels" 64 "github.com/onflow/flow-go/state/protocol" 65 badgerState "github.com/onflow/flow-go/state/protocol/badger" 66 "github.com/onflow/flow-go/state/protocol/blocktimer" 67 "github.com/onflow/flow-go/state/protocol/events/gadgets" 68 "github.com/onflow/flow-go/storage" 69 bstorage "github.com/onflow/flow-go/storage/badger" 70 "github.com/onflow/flow-go/utils/io" 71 ) 72 73 func main() { 74 75 var ( 76 guaranteeLimit uint 77 resultLimit uint 78 approvalLimit uint 79 sealLimit uint 80 pendingReceiptsLimit uint 81 minInterval time.Duration 82 maxInterval time.Duration 83 maxSealPerBlock uint 84 maxGuaranteePerBlock uint 85 hotstuffMinTimeout time.Duration 86 hotstuffTimeoutAdjustmentFactor float64 87 hotstuffHappyPathMaxRoundFailures uint64 88 chunkAlpha uint 89 requiredApprovalsForSealVerification uint 90 requiredApprovalsForSealConstruction uint 91 emergencySealing bool 92 dkgMessagingEngineConfig = dkgeng.DefaultMessagingEngineConfig() 93 cruiseCtlConfig = cruisectl.DefaultConfig() 94 cruiseCtlTargetTransitionTimeFlag = cruiseCtlConfig.TargetTransition.String() 95 cruiseCtlFallbackProposalDurationFlag time.Duration 96 cruiseCtlMinViewDurationFlag time.Duration 97 cruiseCtlMaxViewDurationFlag time.Duration 98 cruiseCtlEnabledFlag bool 99 startupTimeString string 100 startupTime time.Time 101 102 // DKG contract client 103 machineAccountInfo *bootstrap.NodeMachineAccountInfo 104 flowClientConfigs []*common.FlowClientConfig 105 insecureAccessAPI bool 106 accessNodeIDS []string 107 108 err error 109 mutableState protocol.ParticipantState 110 beaconPrivateKey *encodable.RandomBeaconPrivKey 111 guarantees mempool.Guarantees 112 receipts mempool.ExecutionTree 113 seals mempool.IncorporatedResultSeals 114 pendingReceipts mempool.PendingReceipts 115 receiptRequester *requester.Engine 116 syncCore *chainsync.Core 117 comp *compliance.Engine 118 hot module.HotStuff 119 conMetrics module.ConsensusMetrics 120 mainMetrics module.HotstuffMetrics 121 receiptValidator module.ReceiptValidator 122 chunkAssigner *chmodule.ChunkAssigner 123 followerDistributor *pubsub.FollowerDistributor 124 dkgBrokerTunnel *dkgmodule.BrokerTunnel 125 blockTimer protocol.BlockTimer 126 proposalDurProvider hotstuff.ProposalDurationProvider 127 committee *committees.Consensus 128 epochLookup *epochs.EpochLookup 129 hotstuffModules *consensus.HotstuffModules 130 dkgState *bstorage.DKGState 131 safeBeaconKeys *bstorage.SafeBeaconPrivateKeys 132 getSealingConfigs module.SealingConfigsGetter 133 ) 134 var deprecatedFlagBlockRateDelay time.Duration 135 136 nodeBuilder := cmd.FlowNode(flow.RoleConsensus.String()) 137 nodeBuilder.ExtraFlags(func(flags *pflag.FlagSet) { 138 flags.UintVar(&guaranteeLimit, "guarantee-limit", 1000, "maximum number of guarantees in the memory pool") 139 flags.UintVar(&resultLimit, "result-limit", 10000, "maximum number of execution results in the memory pool") 140 flags.UintVar(&approvalLimit, "approval-limit", 1000, "maximum number of result approvals in the memory pool") 141 // the default value is able to buffer as many seals as would be generated over ~12 hours. In case it 142 // ever gets full, the node will simply crash instead of employing complex ejection logic. 143 flags.UintVar(&sealLimit, "seal-limit", 44200, "maximum number of block seals in the memory pool") 144 flags.UintVar(&pendingReceiptsLimit, "pending-receipts-limit", 10000, "maximum number of pending receipts in the mempool") 145 flags.DurationVar(&minInterval, "min-interval", time.Millisecond, "the minimum amount of time between two blocks") 146 flags.DurationVar(&maxInterval, "max-interval", 90*time.Second, "the maximum amount of time between two blocks") 147 flags.UintVar(&maxSealPerBlock, "max-seal-per-block", 100, "the maximum number of seals to be included in a block") 148 flags.UintVar(&maxGuaranteePerBlock, "max-guarantee-per-block", 100, "the maximum number of collection guarantees to be included in a block") 149 flags.DurationVar(&hotstuffMinTimeout, "hotstuff-min-timeout", 2500*time.Millisecond, "the lower timeout bound for the hotstuff pacemaker, this is also used as initial timeout") 150 flags.Float64Var(&hotstuffTimeoutAdjustmentFactor, "hotstuff-timeout-adjustment-factor", timeout.DefaultConfig.TimeoutAdjustmentFactor, "adjustment of timeout duration in case of time out event") 151 flags.Uint64Var(&hotstuffHappyPathMaxRoundFailures, "hotstuff-happy-path-max-round-failures", timeout.DefaultConfig.HappyPathMaxRoundFailures, "number of failed rounds before first timeout increase") 152 flags.StringVar(&cruiseCtlTargetTransitionTimeFlag, "cruise-ctl-target-epoch-transition-time", cruiseCtlTargetTransitionTimeFlag, "the target epoch switchover schedule") 153 flags.DurationVar(&cruiseCtlFallbackProposalDurationFlag, "cruise-ctl-fallback-proposal-duration", cruiseCtlConfig.FallbackProposalDelay.Load(), "the proposal duration value to use when the controller is disabled, or in epoch fallback mode. In those modes, this value has the same as the old `--block-rate-delay`") 154 flags.DurationVar(&cruiseCtlMinViewDurationFlag, "cruise-ctl-min-view-duration", cruiseCtlConfig.MinViewDuration.Load(), "the lower bound of authority for the controller, when active. This is the smallest amount of time a view is allowed to take.") 155 flags.DurationVar(&cruiseCtlMaxViewDurationFlag, "cruise-ctl-max-view-duration", cruiseCtlConfig.MaxViewDuration.Load(), "the upper bound of authority for the controller when active. This is the largest amount of time a view is allowed to take.") 156 flags.BoolVar(&cruiseCtlEnabledFlag, "cruise-ctl-enabled", cruiseCtlConfig.Enabled.Load(), "whether the block time controller is enabled; when disabled, the FallbackProposalDelay is used") 157 flags.UintVar(&chunkAlpha, "chunk-alpha", flow.DefaultChunkAssignmentAlpha, "number of verifiers that should be assigned to each chunk") 158 flags.UintVar(&requiredApprovalsForSealVerification, "required-verification-seal-approvals", flow.DefaultRequiredApprovalsForSealValidation, "minimum number of approvals that are required to verify a seal") 159 flags.UintVar(&requiredApprovalsForSealConstruction, "required-construction-seal-approvals", flow.DefaultRequiredApprovalsForSealConstruction, "minimum number of approvals that are required to construct a seal") 160 flags.BoolVar(&emergencySealing, "emergency-sealing-active", flow.DefaultEmergencySealingActive, "(de)activation of emergency sealing") 161 flags.BoolVar(&insecureAccessAPI, "insecure-access-api", false, "required if insecure GRPC connection should be used") 162 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)) 163 flags.DurationVar(&dkgMessagingEngineConfig.RetryBaseWait, "dkg-messaging-engine-retry-base-wait", dkgMessagingEngineConfig.RetryBaseWait, "the inter-attempt wait time for the first attempt (base of exponential retry)") 164 flags.Uint64Var(&dkgMessagingEngineConfig.RetryMax, "dkg-messaging-engine-retry-max", dkgMessagingEngineConfig.RetryMax, "the maximum number of retry attempts for an outbound DKG message") 165 flags.Uint64Var(&dkgMessagingEngineConfig.RetryJitterPercent, "dkg-messaging-engine-retry-jitter-percent", dkgMessagingEngineConfig.RetryJitterPercent, "the percentage of jitter to apply to each inter-attempt wait time") 166 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 1996-04-24T15:04:05-07:00)") 167 flags.DurationVar(&deprecatedFlagBlockRateDelay, "block-rate-delay", 0, "[deprecated in v0.30; Jun 2023] Use `cruise-ctl-*` flags instead, this flag has no effect and will eventually be removed") 168 }).ValidateFlags(func() error { 169 nodeBuilder.Logger.Info().Str("startup_time_str", startupTimeString).Msg("got startup_time_str") 170 if startupTimeString != cmd.NotSet { 171 t, err := time.Parse(time.RFC3339, startupTimeString) 172 if err != nil { 173 return fmt.Errorf("invalid start-time value: %w", err) 174 } 175 startupTime = t 176 nodeBuilder.Logger.Info().Time("startup_time", startupTime).Msg("got startup_time") 177 } 178 // parse target transition time string, if set 179 if cruiseCtlTargetTransitionTimeFlag != cruiseCtlConfig.TargetTransition.String() { 180 transitionTime, err := cruisectl.ParseTransition(cruiseCtlTargetTransitionTimeFlag) 181 if err != nil { 182 return fmt.Errorf("invalid epoch transition time string: %w", err) 183 } 184 cruiseCtlConfig.TargetTransition = *transitionTime 185 } 186 // convert local flag variables to atomic config variables, for dynamically updatable fields 187 if cruiseCtlEnabledFlag != cruiseCtlConfig.Enabled.Load() { 188 cruiseCtlConfig.Enabled.Store(cruiseCtlEnabledFlag) 189 } 190 if cruiseCtlFallbackProposalDurationFlag != cruiseCtlConfig.FallbackProposalDelay.Load() { 191 cruiseCtlConfig.FallbackProposalDelay.Store(cruiseCtlFallbackProposalDurationFlag) 192 } 193 if cruiseCtlMinViewDurationFlag != cruiseCtlConfig.MinViewDuration.Load() { 194 cruiseCtlConfig.MinViewDuration.Store(cruiseCtlMinViewDurationFlag) 195 } 196 if cruiseCtlMaxViewDurationFlag != cruiseCtlConfig.MaxViewDuration.Load() { 197 cruiseCtlConfig.MaxViewDuration.Store(cruiseCtlMaxViewDurationFlag) 198 } 199 // log a warning about deprecated flags 200 if deprecatedFlagBlockRateDelay > 0 { 201 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.") 202 } 203 return nil 204 }) 205 206 if err = nodeBuilder.Initialize(); err != nil { 207 nodeBuilder.Logger.Fatal().Err(err).Send() 208 } 209 210 nodeBuilder. 211 PreInit(cmd.DynamicStartPreInit). 212 ValidateRootSnapshot(badgerState.ValidRootSnapshotContainsEntityExpiryRange). 213 Module("consensus node metrics", func(node *cmd.NodeConfig) error { 214 conMetrics = metrics.NewConsensusCollector(node.Tracer, node.MetricsRegisterer) 215 return nil 216 }). 217 Module("dkg state", func(node *cmd.NodeConfig) error { 218 dkgState, err = bstorage.NewDKGState(node.Metrics.Cache, node.SecretsDB) 219 return err 220 }). 221 Module("beacon keys", func(node *cmd.NodeConfig) error { 222 safeBeaconKeys = bstorage.NewSafeBeaconPrivateKeys(dkgState) 223 return nil 224 }). 225 Module("updatable sealing config", func(node *cmd.NodeConfig) error { 226 setter, err := updatable_configs.NewSealingConfigs( 227 requiredApprovalsForSealConstruction, 228 requiredApprovalsForSealVerification, 229 chunkAlpha, 230 emergencySealing, 231 ) 232 if err != nil { 233 return err 234 } 235 236 // update the getter with the setter, so other modules can only get, but not set 237 getSealingConfigs = setter 238 239 // admin tool is the only instance that have access to the setter interface, therefore, is 240 // the only module can change this config 241 err = node.ConfigManager.RegisterUintConfig("consensus-required-approvals-for-sealing", 242 setter.RequireApprovalsForSealConstructionDynamicValue, 243 setter.SetRequiredApprovalsForSealingConstruction) 244 return err 245 }). 246 Module("mutable follower state", func(node *cmd.NodeConfig) error { 247 // For now, we only support state implementations from package badger. 248 // If we ever support different implementations, the following can be replaced by a type-aware factory 249 state, ok := node.State.(*badgerState.State) 250 if !ok { 251 return fmt.Errorf("only implementations of type badger.State are currently supported but read-only state has type %T", node.State) 252 } 253 254 chunkAssigner, err = chmodule.NewChunkAssigner(chunkAlpha, node.State) 255 if err != nil { 256 return fmt.Errorf("could not instantiate assignment algorithm for chunk verification: %w", err) 257 } 258 259 receiptValidator = validation.NewReceiptValidator( 260 node.State, 261 node.Storage.Headers, 262 node.Storage.Index, 263 node.Storage.Results, 264 node.Storage.Seals) 265 266 sealValidator := validation.NewSealValidator( 267 node.State, 268 node.Storage.Headers, 269 node.Storage.Index, 270 node.Storage.Results, 271 node.Storage.Seals, 272 chunkAssigner, 273 getSealingConfigs, 274 conMetrics) 275 276 blockTimer, err = blocktimer.NewBlockTimer(minInterval, maxInterval) 277 if err != nil { 278 return err 279 } 280 281 mutableState, err = badgerState.NewFullConsensusState( 282 node.Logger, 283 node.Tracer, 284 node.ProtocolEvents, 285 state, 286 node.Storage.Index, 287 node.Storage.Payloads, 288 blockTimer, 289 receiptValidator, 290 sealValidator, 291 ) 292 return err 293 }). 294 Module("random beacon key", func(node *cmd.NodeConfig) error { 295 // If this node was a participant in a spork, their beacon key for the 296 // first epoch was generated during the bootstrapping process and is 297 // specified in a private bootstrapping file. We load their key and 298 // store it in the db for the initial post-spork epoch for use going 299 // forward. 300 // 301 // If this node was not a participant in a spork, they joined at an 302 // epoch boundary, so they have no beacon key file (they will generate 303 // their first beacon private key through the DKG in the EpochSetup phase 304 // prior to their first epoch as network participant). 305 306 rootSnapshot := node.State.AtBlockID(node.FinalizedRootBlock.ID()) 307 isSporkRoot, err := protocol.IsSporkRootSnapshot(rootSnapshot) 308 if err != nil { 309 return fmt.Errorf("could not check whether root snapshot is spork root: %w", err) 310 } 311 if !isSporkRoot { 312 node.Logger.Info().Msg("node starting from mid-spork snapshot, will not read spork random beacon key file") 313 return nil 314 } 315 316 // If the node has a beacon key file, then save it to the secrets database 317 // as the beacon key for the epoch of the root snapshot. 318 beaconPrivateKey, err = loadBeaconPrivateKey(node.BaseConfig.BootstrapDir, node.NodeID) 319 if errors.Is(err, os.ErrNotExist) { 320 return fmt.Errorf("node is starting from spork root snapshot, but does not have spork random beacon key file: %w", err) 321 } 322 if err != nil { 323 return fmt.Errorf("could not load beacon key file: %w", err) 324 } 325 326 rootEpoch := node.State.AtBlockID(node.FinalizedRootBlock.ID()).Epochs().Current() 327 epochCounter, err := rootEpoch.Counter() 328 if err != nil { 329 return fmt.Errorf("could not get root epoch counter: %w", err) 330 } 331 332 // confirm the beacon key file matches the canonical public keys 333 rootDKG, err := rootEpoch.DKG() 334 if err != nil { 335 return fmt.Errorf("could not get dkg for root epoch: %w", err) 336 } 337 myBeaconPublicKeyShare, err := rootDKG.KeyShare(node.NodeID) 338 if err != nil { 339 return fmt.Errorf("could not get my beacon public key share for root epoch: %w", err) 340 } 341 342 if !myBeaconPublicKeyShare.Equals(beaconPrivateKey.PrivateKey.PublicKey()) { 343 return fmt.Errorf("configured beacon key is inconsistent with this node's canonical public beacon key (%s!=%s)", 344 beaconPrivateKey.PrivateKey.PublicKey(), 345 myBeaconPublicKeyShare) 346 } 347 348 // store my beacon key for the first epoch post-spork 349 err = dkgState.InsertMyBeaconPrivateKey(epochCounter, beaconPrivateKey.PrivateKey) 350 if err != nil && !errors.Is(err, storage.ErrAlreadyExists) { 351 return err 352 } 353 // mark the root DKG as successful, so it is considered safe to use the key 354 err = dkgState.SetDKGEndState(epochCounter, flow.DKGEndStateSuccess) 355 if err != nil && !errors.Is(err, storage.ErrAlreadyExists) { 356 return err 357 } 358 359 return nil 360 }). 361 Module("collection guarantees mempool", func(node *cmd.NodeConfig) error { 362 guarantees, err = stdmap.NewGuarantees(guaranteeLimit) 363 return err 364 }). 365 Module("execution receipts mempool", func(node *cmd.NodeConfig) error { 366 receipts = consensusMempools.NewExecutionTree() 367 // registers size method of backend for metrics 368 err = node.Metrics.Mempool.Register(metrics.ResourceReceipt, receipts.Size) 369 if err != nil { 370 return fmt.Errorf("could not register backend metric: %w", err) 371 } 372 return nil 373 }). 374 Module("block seals mempool", func(node *cmd.NodeConfig) error { 375 // use a custom ejector, so we don't eject seals that would break 376 // the chain of seals 377 rawMempool := stdmap.NewIncorporatedResultSeals(sealLimit) 378 multipleReceiptsFilterMempool := consensusMempools.NewIncorporatedResultSeals(rawMempool, node.Storage.Receipts) 379 seals, err = consensusMempools.NewExecStateForkSuppressor( 380 multipleReceiptsFilterMempool, 381 consensusMempools.LogForkAndCrash(node.Logger), 382 node.DB, 383 node.Logger, 384 ) 385 if err != nil { 386 return fmt.Errorf("failed to wrap seals mempool into ExecStateForkSuppressor: %w", err) 387 } 388 err = node.Metrics.Mempool.Register(metrics.ResourcePendingIncorporatedSeal, seals.Size) 389 return nil 390 }). 391 Module("pending receipts mempool", func(node *cmd.NodeConfig) error { 392 pendingReceipts = stdmap.NewPendingReceipts(node.Storage.Headers, pendingReceiptsLimit) 393 return nil 394 }). 395 Module("hotstuff main metrics", func(node *cmd.NodeConfig) error { 396 mainMetrics = metrics.NewHotstuffCollector(node.RootChainID) 397 return nil 398 }). 399 Module("sync core", func(node *cmd.NodeConfig) error { 400 syncCore, err = chainsync.New(node.Logger, node.SyncCoreConfig, metrics.NewChainSyncCollector(node.RootChainID), node.RootChainID) 401 return err 402 }). 403 Module("follower distributor", func(node *cmd.NodeConfig) error { 404 followerDistributor = pubsub.NewFollowerDistributor() 405 return nil 406 }). 407 Module("machine account config", func(node *cmd.NodeConfig) error { 408 machineAccountInfo, err = cmd.LoadNodeMachineAccountInfoFile(node.BootstrapDir, node.NodeID) 409 return err 410 }). 411 Module("sdk client connection options", func(node *cmd.NodeConfig) error { 412 anIDS, err := common.ValidateAccessNodeIDSFlag(accessNodeIDS, node.RootChainID, node.State.Sealed()) 413 if err != nil { 414 return fmt.Errorf("failed to validate flag --access-node-ids %w", err) 415 } 416 417 flowClientConfigs, err = common.FlowClientConfigs(anIDS, insecureAccessAPI, node.State.Sealed()) 418 if err != nil { 419 return fmt.Errorf("failed to prepare flow client connection configs for each access node id %w", err) 420 } 421 422 return nil 423 }). 424 Component("machine account config validator", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 425 // @TODO use fallback logic for flowClient similar to DKG/QC contract clients 426 flowClient, err := common.FlowClient(flowClientConfigs[0]) 427 if err != nil { 428 return nil, fmt.Errorf("failed to get flow client connection option for access node (0): %s %w", flowClientConfigs[0].AccessAddress, err) 429 } 430 431 // disable balance checks for transient networks, which do not have transaction fees 432 var opts []epochs.MachineAccountValidatorConfigOption 433 if node.RootChainID.Transient() { 434 opts = append(opts, epochs.WithoutBalanceChecks) 435 } 436 validator, err := epochs.NewMachineAccountConfigValidator( 437 node.Logger, 438 flowClient, 439 flow.RoleCollection, 440 *machineAccountInfo, 441 opts..., 442 ) 443 return validator, err 444 }). 445 Component("sealing engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 446 447 sealingTracker := tracker.NewSealingTracker(node.Logger, node.Storage.Headers, node.Storage.Receipts, seals) 448 449 e, err := sealing.NewEngine( 450 node.Logger, 451 node.Tracer, 452 conMetrics, 453 node.Metrics.Engine, 454 node.Metrics.Mempool, 455 sealingTracker, 456 node.EngineRegistry, 457 node.Me, 458 node.Storage.Headers, 459 node.Storage.Payloads, 460 node.Storage.Results, 461 node.Storage.Index, 462 node.State, 463 node.Storage.Seals, 464 chunkAssigner, 465 seals, 466 getSealingConfigs, 467 ) 468 469 // subscribe for finalization events from hotstuff 470 followerDistributor.AddOnBlockFinalizedConsumer(e.OnFinalizedBlock) 471 followerDistributor.AddOnBlockIncorporatedConsumer(e.OnBlockIncorporated) 472 473 return e, err 474 }). 475 Component("matching engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 476 receiptRequester, err = requester.New( 477 node.Logger, 478 node.Metrics.Engine, 479 node.EngineRegistry, 480 node.Me, 481 node.State, 482 channels.RequestReceiptsByBlockID, 483 filter.HasRole(flow.RoleExecution), 484 func() flow.Entity { return &flow.ExecutionReceipt{} }, 485 requester.WithRetryInitial(2*time.Second), 486 requester.WithRetryMaximum(30*time.Second), 487 ) 488 if err != nil { 489 return nil, err 490 } 491 492 core := matching.NewCore( 493 node.Logger, 494 node.Tracer, 495 conMetrics, 496 node.Metrics.Mempool, 497 node.State, 498 node.Storage.Headers, 499 node.Storage.Receipts, 500 receipts, 501 pendingReceipts, 502 seals, 503 receiptValidator, 504 receiptRequester, 505 matching.DefaultConfig(), 506 ) 507 508 e, err := matching.NewEngine( 509 node.Logger, 510 node.EngineRegistry, 511 node.Me, 512 node.Metrics.Engine, 513 node.Metrics.Mempool, 514 node.State, 515 node.Storage.Receipts, 516 node.Storage.Index, 517 core, 518 ) 519 if err != nil { 520 return nil, err 521 } 522 523 // subscribe engine to inputs from other node-internal components 524 receiptRequester.WithHandle(e.HandleReceipt) 525 followerDistributor.AddOnBlockFinalizedConsumer(e.OnFinalizedBlock) 526 followerDistributor.AddOnBlockIncorporatedConsumer(e.OnBlockIncorporated) 527 528 return e, err 529 }). 530 Component("ingestion engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 531 core := ingestion.NewCore( 532 node.Logger, 533 node.Tracer, 534 node.Metrics.Mempool, 535 node.State, 536 node.Storage.Headers, 537 guarantees, 538 ) 539 540 ing, err := ingestion.New( 541 node.Logger, 542 node.Metrics.Engine, 543 node.EngineRegistry, 544 node.Me, 545 core, 546 ) 547 548 return ing, err 549 }). 550 Component("hotstuff committee", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 551 committee, err = committees.NewConsensusCommittee(node.State, node.Me.NodeID()) 552 node.ProtocolEvents.AddConsumer(committee) 553 return committee, err 554 }). 555 Component("epoch lookup", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 556 epochLookup, err = epochs.NewEpochLookup(node.State) 557 node.ProtocolEvents.AddConsumer(epochLookup) 558 return epochLookup, err 559 }). 560 Component("hotstuff modules", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 561 // initialize the block finalizer 562 finalize := finalizer.NewFinalizer( 563 node.DB, 564 node.Storage.Headers, 565 mutableState, 566 node.Tracer, 567 finalizer.WithCleanup(finalizer.CleanupMempools( 568 node.Metrics.Mempool, 569 conMetrics, 570 node.Storage.Payloads, 571 guarantees, 572 seals, 573 )), 574 ) 575 576 // wrap Main consensus committee with metrics 577 wrappedCommittee := committees.NewMetricsWrapper(committee, mainMetrics) // wrapper for measuring time spent determining consensus committee relations 578 579 beaconKeyStore := hotsignature.NewEpochAwareRandomBeaconKeyStore(epochLookup, safeBeaconKeys) 580 581 // initialize the combined signer for hotstuff 582 var signer hotstuff.Signer 583 signer = verification.NewCombinedSigner( 584 node.Me, 585 beaconKeyStore, 586 ) 587 signer = verification.NewMetricsWrapper(signer, mainMetrics) // wrapper for measuring time spent with crypto-related operations 588 589 // create consensus logger 590 logger := createLogger(node.Logger, node.RootChainID) 591 592 telemetryConsumer := notifications.NewTelemetryConsumer(logger) 593 slashingViolationConsumer := notifications.NewSlashingViolationsConsumer(nodeBuilder.Logger) 594 followerDistributor.AddProposalViolationConsumer(slashingViolationConsumer) 595 596 // initialize a logging notifier for hotstuff 597 notifier := createNotifier( 598 logger, 599 mainMetrics, 600 ) 601 602 notifier.AddParticipantConsumer(telemetryConsumer) 603 notifier.AddCommunicatorConsumer(telemetryConsumer) 604 notifier.AddFinalizationConsumer(telemetryConsumer) 605 notifier.AddFollowerConsumer(followerDistributor) 606 607 // initialize the persister 608 persist := persister.New(node.DB, node.RootChainID) 609 610 finalizedBlock, err := node.State.Final().Head() 611 if err != nil { 612 return nil, err 613 } 614 615 forks, err := consensus.NewForks( 616 finalizedBlock, 617 node.Storage.Headers, 618 finalize, 619 notifier, 620 node.FinalizedRootBlock.Header, 621 node.RootQC, 622 ) 623 if err != nil { 624 return nil, err 625 } 626 627 // create producer and connect it to consumers 628 voteAggregationDistributor := pubsub.NewVoteAggregationDistributor() 629 voteAggregationDistributor.AddVoteCollectorConsumer(telemetryConsumer) 630 voteAggregationDistributor.AddVoteAggregationViolationConsumer(slashingViolationConsumer) 631 632 validator := consensus.NewValidator(mainMetrics, wrappedCommittee) 633 voteProcessorFactory := votecollector.NewCombinedVoteProcessorFactory(wrappedCommittee, voteAggregationDistributor.OnQcConstructedFromVotes) 634 lowestViewForVoteProcessing := finalizedBlock.View + 1 635 voteAggregator, err := consensus.NewVoteAggregator( 636 logger, 637 mainMetrics, 638 node.Metrics.Engine, 639 node.Metrics.Mempool, 640 lowestViewForVoteProcessing, 641 voteAggregationDistributor, 642 voteProcessorFactory, 643 followerDistributor) 644 if err != nil { 645 return nil, fmt.Errorf("could not initialize vote aggregator: %w", err) 646 } 647 648 // create producer and connect it to consumers 649 timeoutAggregationDistributor := pubsub.NewTimeoutAggregationDistributor() 650 timeoutAggregationDistributor.AddTimeoutCollectorConsumer(telemetryConsumer) 651 timeoutAggregationDistributor.AddTimeoutAggregationViolationConsumer(slashingViolationConsumer) 652 653 timeoutProcessorFactory := timeoutcollector.NewTimeoutProcessorFactory( 654 logger, 655 timeoutAggregationDistributor, 656 committee, 657 validator, 658 msig.ConsensusTimeoutTag, 659 ) 660 timeoutAggregator, err := consensus.NewTimeoutAggregator( 661 logger, 662 mainMetrics, 663 node.Metrics.Engine, 664 node.Metrics.Mempool, 665 notifier, 666 timeoutProcessorFactory, 667 timeoutAggregationDistributor, 668 lowestViewForVoteProcessing, 669 ) 670 if err != nil { 671 return nil, fmt.Errorf("could not initialize timeout aggregator: %w", err) 672 } 673 674 hotstuffModules = &consensus.HotstuffModules{ 675 Notifier: notifier, 676 Committee: wrappedCommittee, 677 Signer: signer, 678 Persist: persist, 679 VoteCollectorDistributor: voteAggregationDistributor.VoteCollectorDistributor, 680 TimeoutCollectorDistributor: timeoutAggregationDistributor.TimeoutCollectorDistributor, 681 Forks: forks, 682 Validator: validator, 683 VoteAggregator: voteAggregator, 684 TimeoutAggregator: timeoutAggregator, 685 } 686 687 return util.MergeReadyDone(voteAggregator, timeoutAggregator), nil 688 }). 689 Component("block rate cruise control", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 690 livenessData, err := hotstuffModules.Persist.GetLivenessData() 691 if err != nil { 692 return nil, err 693 } 694 ctl, err := cruisectl.NewBlockTimeController(node.Logger, metrics.NewCruiseCtlMetrics(), cruiseCtlConfig, node.State, livenessData.CurrentView) 695 if err != nil { 696 return nil, err 697 } 698 proposalDurProvider = ctl 699 hotstuffModules.Notifier.AddOnBlockIncorporatedConsumer(ctl.OnBlockIncorporated) 700 node.ProtocolEvents.AddConsumer(ctl) 701 702 // set up admin commands for dynamically updating configs 703 err = node.ConfigManager.RegisterBoolConfig("cruise-ctl-enabled", cruiseCtlConfig.GetEnabled, cruiseCtlConfig.SetEnabled) 704 if err != nil { 705 return nil, err 706 } 707 err = node.ConfigManager.RegisterDurationConfig("cruise-ctl-fallback-proposal-duration", cruiseCtlConfig.GetFallbackProposalDuration, cruiseCtlConfig.SetFallbackProposalDuration) 708 if err != nil { 709 return nil, err 710 } 711 err = node.ConfigManager.RegisterDurationConfig("cruise-ctl-min-view-duration", cruiseCtlConfig.GetMinViewDuration, cruiseCtlConfig.SetMinViewDuration) 712 if err != nil { 713 return nil, err 714 } 715 err = node.ConfigManager.RegisterDurationConfig("cruise-ctl-max-view-duration", cruiseCtlConfig.GetMaxViewDuration, cruiseCtlConfig.SetMaxViewDuration) 716 if err != nil { 717 return nil, err 718 } 719 720 return ctl, nil 721 }). 722 Component("consensus participant", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 723 // initialize the block builder 724 var build module.Builder 725 build, err = builder.NewBuilder( 726 node.Metrics.Mempool, 727 node.DB, 728 mutableState, 729 node.Storage.Headers, 730 node.Storage.Seals, 731 node.Storage.Index, 732 node.Storage.Blocks, 733 node.Storage.Results, 734 node.Storage.Receipts, 735 guarantees, 736 seals, 737 receipts, 738 node.Tracer, 739 builder.WithBlockTimer(blockTimer), 740 builder.WithMaxSealCount(maxSealPerBlock), 741 builder.WithMaxGuaranteeCount(maxGuaranteePerBlock), 742 ) 743 if err != nil { 744 return nil, fmt.Errorf("could not initialized block builder: %w", err) 745 } 746 build = blockproducer.NewMetricsWrapper(build, mainMetrics) // wrapper for measuring time spent building block payload component 747 748 opts := []consensus.Option{ 749 consensus.WithMinTimeout(hotstuffMinTimeout), 750 consensus.WithTimeoutAdjustmentFactor(hotstuffTimeoutAdjustmentFactor), 751 consensus.WithHappyPathMaxRoundFailures(hotstuffHappyPathMaxRoundFailures), 752 consensus.WithProposalDurationProvider(proposalDurProvider), 753 } 754 755 if !startupTime.IsZero() { 756 opts = append(opts, consensus.WithStartupTime(startupTime)) 757 } 758 finalizedBlock, pending, err := recovery.FindLatest(node.State, node.Storage.Headers) 759 if err != nil { 760 return nil, err 761 } 762 763 // initialize hotstuff consensus algorithm 764 hot, err = consensus.NewParticipant( 765 createLogger(node.Logger, node.RootChainID), 766 mainMetrics, 767 node.Metrics.Mempool, 768 build, 769 finalizedBlock, 770 pending, 771 hotstuffModules, 772 opts..., 773 ) 774 if err != nil { 775 return nil, fmt.Errorf("could not initialize hotstuff engine: %w", err) 776 } 777 return hot, nil 778 }). 779 Component("consensus compliance engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 780 // initialize the pending blocks cache 781 proposals := buffer.NewPendingBlocks() 782 783 logger := createLogger(node.Logger, node.RootChainID) 784 complianceCore, err := compliance.NewCore( 785 logger, 786 node.Metrics.Engine, 787 node.Metrics.Mempool, 788 mainMetrics, 789 node.Metrics.Compliance, 790 followerDistributor, 791 node.Tracer, 792 node.Storage.Headers, 793 node.Storage.Payloads, 794 mutableState, 795 proposals, 796 syncCore, 797 hotstuffModules.Validator, 798 hot, 799 hotstuffModules.VoteAggregator, 800 hotstuffModules.TimeoutAggregator, 801 node.ComplianceConfig, 802 ) 803 if err != nil { 804 return nil, fmt.Errorf("could not initialize compliance core: %w", err) 805 } 806 807 // initialize the compliance engine 808 comp, err = compliance.NewEngine( 809 logger, 810 node.Me, 811 complianceCore, 812 ) 813 if err != nil { 814 return nil, fmt.Errorf("could not initialize compliance engine: %w", err) 815 } 816 followerDistributor.AddOnBlockFinalizedConsumer(comp.OnFinalizedBlock) 817 818 return comp, nil 819 }). 820 Component("consensus message hub", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 821 messageHub, err := message_hub.NewMessageHub( 822 createLogger(node.Logger, node.RootChainID), 823 node.Metrics.Engine, 824 node.EngineRegistry, 825 node.Me, 826 comp, 827 hot, 828 hotstuffModules.VoteAggregator, 829 hotstuffModules.TimeoutAggregator, 830 node.State, 831 node.Storage.Payloads, 832 ) 833 if err != nil { 834 return nil, fmt.Errorf("could not create consensus message hub: %w", err) 835 } 836 hotstuffModules.Notifier.AddConsumer(messageHub) 837 return messageHub, nil 838 }). 839 Component("sync engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 840 spamConfig, err := synceng.NewSpamDetectionConfig() 841 if err != nil { 842 return nil, fmt.Errorf("could not initialize spam detection config: %w", err) 843 } 844 845 sync, err := synceng.New( 846 node.Logger, 847 node.Metrics.Engine, 848 node.EngineRegistry, 849 node.Me, 850 node.State, 851 node.Storage.Blocks, 852 comp, 853 syncCore, 854 node.SyncEngineIdentifierProvider, 855 spamConfig, 856 ) 857 if err != nil { 858 return nil, fmt.Errorf("could not initialize synchronization engine: %w", err) 859 } 860 followerDistributor.AddFinalizationConsumer(sync) 861 862 return sync, nil 863 }). 864 Component("receipt requester engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 865 // created with sealing engine 866 return receiptRequester, nil 867 }). 868 Component("DKG messaging engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 869 870 // brokerTunnel is used to forward messages between the DKG 871 // messaging engine and the DKG broker/controller 872 dkgBrokerTunnel = dkgmodule.NewBrokerTunnel() 873 874 // messagingEngine is a network engine that is used by nodes to 875 // exchange private DKG messages 876 messagingEngine, err := dkgeng.NewMessagingEngine( 877 node.Logger, 878 node.EngineRegistry, 879 node.Me, 880 dkgBrokerTunnel, 881 node.Metrics.Mempool, 882 dkgMessagingEngineConfig, 883 ) 884 if err != nil { 885 return nil, fmt.Errorf("could not initialize DKG messaging engine: %w", err) 886 } 887 888 return messagingEngine, nil 889 }). 890 Component("DKG reactor engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 891 // the viewsObserver is used by the reactor engine to subscribe to 892 // new views being finalized 893 viewsObserver := gadgets.NewViews() 894 node.ProtocolEvents.AddConsumer(viewsObserver) 895 896 // construct DKG contract client 897 dkgContractClients, err := createDKGContractClients(node, machineAccountInfo, flowClientConfigs) 898 if err != nil { 899 return nil, fmt.Errorf("could not create dkg contract client %w", err) 900 } 901 902 // the reactor engine reacts to new views being finalized and drives the 903 // DKG protocol 904 reactorEngine := dkgeng.NewReactorEngine( 905 node.Logger, 906 node.Me, 907 node.State, 908 dkgState, 909 dkgmodule.NewControllerFactory( 910 node.Logger, 911 node.Me, 912 dkgContractClients, 913 dkgBrokerTunnel, 914 ), 915 viewsObserver, 916 ) 917 918 // reactorEngine consumes the EpochSetupPhaseStarted event 919 node.ProtocolEvents.AddConsumer(reactorEngine) 920 921 return reactorEngine, nil 922 }) 923 924 node, err := nodeBuilder.Build() 925 if err != nil { 926 nodeBuilder.Logger.Fatal().Err(err).Send() 927 } 928 node.Run() 929 } 930 931 func loadBeaconPrivateKey(dir string, myID flow.Identifier) (*encodable.RandomBeaconPrivKey, error) { 932 path := fmt.Sprintf(bootstrap.PathRandomBeaconPriv, myID) 933 data, err := io.ReadFile(filepath.Join(dir, path)) 934 if err != nil { 935 return nil, err 936 } 937 938 var priv encodable.RandomBeaconPrivKey 939 err = json.Unmarshal(data, &priv) 940 if err != nil { 941 return nil, err 942 } 943 return &priv, nil 944 } 945 946 // createDKGContractClient creates an dkgContractClient 947 func createDKGContractClient(node *cmd.NodeConfig, machineAccountInfo *bootstrap.NodeMachineAccountInfo, flowClient *client.Client, anID flow.Identifier) (module.DKGContractClient, error) { 948 var dkgClient module.DKGContractClient 949 950 contracts := systemcontracts.SystemContractsForChain(node.RootChainID) 951 dkgContractAddress := contracts.DKG.Address.Hex() 952 953 // construct signer from private key 954 sk, err := crypto.DecodePrivateKey(machineAccountInfo.SigningAlgorithm, machineAccountInfo.EncodedPrivateKey) 955 if err != nil { 956 return nil, fmt.Errorf("could not decode private key from hex: %w", err) 957 } 958 959 txSigner, err := crypto.NewInMemorySigner(sk, machineAccountInfo.HashAlgorithm) 960 if err != nil { 961 return nil, fmt.Errorf("could not create in-memory signer: %w", err) 962 } 963 964 // create actual dkg contract client, all flags and machine account info file found 965 dkgClient = dkgmodule.NewClient( 966 node.Logger, 967 flowClient, 968 anID, 969 txSigner, 970 dkgContractAddress, 971 machineAccountInfo.Address, 972 machineAccountInfo.KeyIndex, 973 ) 974 975 return dkgClient, nil 976 } 977 978 // createDKGContractClients creates an array dkgContractClient that is sorted by retry fallback priority 979 func createDKGContractClients(node *cmd.NodeConfig, machineAccountInfo *bootstrap.NodeMachineAccountInfo, flowClientOpts []*common.FlowClientConfig) ([]module.DKGContractClient, error) { 980 dkgClients := make([]module.DKGContractClient, 0) 981 982 for _, opt := range flowClientOpts { 983 flowClient, err := common.FlowClient(opt) 984 if err != nil { 985 return nil, fmt.Errorf("failed to create flow client for dkg contract client with options: %s %w", flowClientOpts, err) 986 } 987 988 node.Logger.Info().Msgf("created dkg contract client with opts: %s", opt.String()) 989 dkgClient, err := createDKGContractClient(node, machineAccountInfo, flowClient, opt.AccessNodeID) 990 if err != nil { 991 return nil, fmt.Errorf("failed to create dkg contract client with flow client options: %s %w", flowClientOpts, err) 992 } 993 994 dkgClients = append(dkgClients, dkgClient) 995 } 996 997 return dkgClients, nil 998 }