github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/follower/follower_builder.go (about) 1 package follower 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "strings" 9 10 dht "github.com/libp2p/go-libp2p-kad-dht" 11 "github.com/libp2p/go-libp2p/core/host" 12 "github.com/libp2p/go-libp2p/core/peer" 13 "github.com/libp2p/go-libp2p/core/routing" 14 "github.com/onflow/crypto" 15 "github.com/rs/zerolog" 16 17 "github.com/onflow/flow-go/cmd" 18 "github.com/onflow/flow-go/config" 19 "github.com/onflow/flow-go/consensus" 20 "github.com/onflow/flow-go/consensus/hotstuff" 21 "github.com/onflow/flow-go/consensus/hotstuff/committees" 22 "github.com/onflow/flow-go/consensus/hotstuff/notifications" 23 "github.com/onflow/flow-go/consensus/hotstuff/notifications/pubsub" 24 hotsignature "github.com/onflow/flow-go/consensus/hotstuff/signature" 25 hotstuffvalidator "github.com/onflow/flow-go/consensus/hotstuff/validator" 26 "github.com/onflow/flow-go/consensus/hotstuff/verification" 27 recovery "github.com/onflow/flow-go/consensus/recovery/protocol" 28 "github.com/onflow/flow-go/engine/common/follower" 29 synceng "github.com/onflow/flow-go/engine/common/synchronization" 30 "github.com/onflow/flow-go/model/encodable" 31 "github.com/onflow/flow-go/model/flow" 32 "github.com/onflow/flow-go/model/flow/filter" 33 "github.com/onflow/flow-go/module" 34 synchronization "github.com/onflow/flow-go/module/chainsync" 35 finalizer "github.com/onflow/flow-go/module/finalizer/consensus" 36 "github.com/onflow/flow-go/module/id" 37 "github.com/onflow/flow-go/module/local" 38 "github.com/onflow/flow-go/module/metrics" 39 "github.com/onflow/flow-go/module/upstream" 40 "github.com/onflow/flow-go/network" 41 alspmgr "github.com/onflow/flow-go/network/alsp/manager" 42 netcache "github.com/onflow/flow-go/network/cache" 43 "github.com/onflow/flow-go/network/channels" 44 cborcodec "github.com/onflow/flow-go/network/codec/cbor" 45 "github.com/onflow/flow-go/network/converter" 46 "github.com/onflow/flow-go/network/p2p" 47 p2pbuilder "github.com/onflow/flow-go/network/p2p/builder" 48 p2pbuilderconfig "github.com/onflow/flow-go/network/p2p/builder/config" 49 "github.com/onflow/flow-go/network/p2p/cache" 50 "github.com/onflow/flow-go/network/p2p/conduit" 51 p2pdht "github.com/onflow/flow-go/network/p2p/dht" 52 "github.com/onflow/flow-go/network/p2p/keyutils" 53 p2plogging "github.com/onflow/flow-go/network/p2p/logging" 54 "github.com/onflow/flow-go/network/p2p/subscription" 55 "github.com/onflow/flow-go/network/p2p/translator" 56 "github.com/onflow/flow-go/network/p2p/unicast/protocols" 57 "github.com/onflow/flow-go/network/p2p/utils" 58 "github.com/onflow/flow-go/network/slashing" 59 "github.com/onflow/flow-go/network/underlay" 60 "github.com/onflow/flow-go/network/validator" 61 "github.com/onflow/flow-go/state/protocol" 62 badgerState "github.com/onflow/flow-go/state/protocol/badger" 63 "github.com/onflow/flow-go/state/protocol/blocktimer" 64 "github.com/onflow/flow-go/state/protocol/events/gadgets" 65 ) 66 67 // FlowBuilder extends cmd.NodeBuilder and declares additional functions needed to bootstrap an Access node 68 // These functions are shared by staked and observer builders. 69 // The Staked network allows the staked nodes to communicate among themselves, while the public network allows the 70 // observers and an Access node to communicate. 71 // 72 // public network staked network 73 // +------------------------+ 74 // | observer 1 |<--------------------------| 75 // +------------------------+ v 76 // +------------------------+ +----------------------+ +------------------------+ 77 // | observer 2 |<----------------------->| Access Node (staked) |<------------>| All other staked Nodes | 78 // +------------------------+ +----------------------+ +------------------------+ 79 // +------------------------+ ^ 80 // | observer 3 |<--------------------------| 81 // +------------------------+ 82 83 // FollowerServiceConfig defines all the user defined parameters required to bootstrap an access node 84 // For a node running as a standalone process, the config fields will be populated from the command line params, 85 // while for a node running as a library, the config fields are expected to be initialized by the caller. 86 type FollowerServiceConfig struct { 87 bootstrapNodeAddresses []string 88 bootstrapNodePublicKeys []string 89 bootstrapIdentities flow.IdentitySkeletonList // the identity list of bootstrap peers the node uses to discover other nodes 90 NetworkKey crypto.PrivateKey // the networking key passed in by the caller when being used as a library 91 baseOptions []cmd.Option 92 } 93 94 // DefaultFollowerServiceConfig defines all the default values for the FollowerServiceConfig 95 func DefaultFollowerServiceConfig() *FollowerServiceConfig { 96 return &FollowerServiceConfig{ 97 bootstrapNodeAddresses: []string{}, 98 bootstrapNodePublicKeys: []string{}, 99 } 100 } 101 102 // FollowerServiceBuilder provides the common functionality needed to bootstrap a Flow staked and observer 103 // It is composed of the FlowNodeBuilder, the FollowerServiceConfig and contains all the components and modules needed for the 104 // staked and observers 105 type FollowerServiceBuilder struct { 106 *cmd.FlowNodeBuilder 107 *FollowerServiceConfig 108 109 // components 110 LibP2PNode p2p.LibP2PNode 111 FollowerState protocol.FollowerState 112 SyncCore *synchronization.Core 113 FollowerDistributor *pubsub.FollowerDistributor 114 Committee hotstuff.DynamicCommittee 115 Finalized *flow.Header 116 Pending []*flow.Header 117 FollowerCore module.HotStuffFollower 118 // for the observer, the sync engine participants provider is the libp2p peer store which is not 119 // available until after the network has started. Hence, a factory function that needs to be called just before 120 // creating the sync engine 121 SyncEngineParticipantsProviderFactory func() module.IdentifierProvider 122 123 // engines 124 FollowerEng *follower.ComplianceEngine 125 SyncEng *synceng.Engine 126 127 peerID peer.ID 128 } 129 130 // deriveBootstrapPeerIdentities derives the Flow Identity of the bootstrap peers from the parameters. 131 // These are the identities of the staked and observers also acting as the DHT bootstrap server 132 func (builder *FollowerServiceBuilder) deriveBootstrapPeerIdentities() error { 133 // if bootstrap identities already provided (as part of alternate initialization as a library the skip reading command 134 // line params) 135 if builder.bootstrapIdentities != nil { 136 return nil 137 } 138 139 ids, err := BootstrapIdentities(builder.bootstrapNodeAddresses, builder.bootstrapNodePublicKeys) 140 if err != nil { 141 return fmt.Errorf("failed to derive bootstrap peer identities: %w", err) 142 } 143 144 builder.bootstrapIdentities = ids 145 146 return nil 147 } 148 149 func (builder *FollowerServiceBuilder) buildFollowerState() *FollowerServiceBuilder { 150 builder.Module("mutable follower state", func(node *cmd.NodeConfig) error { 151 // For now, we only support state implementations from package badger. 152 // If we ever support different implementations, the following can be replaced by a type-aware factory 153 state, ok := node.State.(*badgerState.State) 154 if !ok { 155 return fmt.Errorf("only implementations of type badger.State are currently supported but read-only state has type %T", node.State) 156 } 157 158 followerState, err := badgerState.NewFollowerState( 159 node.Logger, 160 node.Tracer, 161 node.ProtocolEvents, 162 state, 163 node.Storage.Index, 164 node.Storage.Payloads, 165 blocktimer.DefaultBlockTimer, 166 ) 167 builder.FollowerState = followerState 168 169 return err 170 }) 171 172 return builder 173 } 174 175 func (builder *FollowerServiceBuilder) buildSyncCore() *FollowerServiceBuilder { 176 builder.Module("sync core", func(node *cmd.NodeConfig) error { 177 syncCore, err := synchronization.New(node.Logger, node.SyncCoreConfig, metrics.NewChainSyncCollector(node.RootChainID), node.RootChainID) 178 builder.SyncCore = syncCore 179 180 return err 181 }) 182 183 return builder 184 } 185 186 func (builder *FollowerServiceBuilder) buildCommittee() *FollowerServiceBuilder { 187 builder.Component("committee", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 188 // initialize consensus committee's membership state 189 // This committee state is for the HotStuff follower, which follows the MAIN CONSENSUS committee 190 // Note: node.Me.NodeID() is not part of the consensus committee 191 committee, err := committees.NewConsensusCommittee(node.State, node.Me.NodeID()) 192 node.ProtocolEvents.AddConsumer(committee) 193 builder.Committee = committee 194 195 return committee, err 196 }) 197 198 return builder 199 } 200 201 func (builder *FollowerServiceBuilder) buildLatestHeader() *FollowerServiceBuilder { 202 builder.Module("latest header", func(node *cmd.NodeConfig) error { 203 finalized, pending, err := recovery.FindLatest(node.State, node.Storage.Headers) 204 builder.Finalized, builder.Pending = finalized, pending 205 206 return err 207 }) 208 209 return builder 210 } 211 212 func (builder *FollowerServiceBuilder) buildFollowerCore() *FollowerServiceBuilder { 213 builder.Component("follower core", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 214 // create a finalizer that will handle updating the protocol 215 // state when the follower detects newly finalized blocks 216 final := finalizer.NewFinalizer(node.DB, node.Storage.Headers, builder.FollowerState, node.Tracer) 217 218 followerCore, err := consensus.NewFollower( 219 node.Logger, 220 node.Metrics.Mempool, 221 node.Storage.Headers, 222 final, 223 builder.FollowerDistributor, 224 node.FinalizedRootBlock.Header, 225 node.RootQC, 226 builder.Finalized, 227 builder.Pending, 228 ) 229 if err != nil { 230 return nil, fmt.Errorf("could not initialize follower core: %w", err) 231 } 232 builder.FollowerCore = followerCore 233 234 return builder.FollowerCore, nil 235 }) 236 237 return builder 238 } 239 240 func (builder *FollowerServiceBuilder) buildFollowerEngine() *FollowerServiceBuilder { 241 builder.Component("follower engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 242 var heroCacheCollector module.HeroCacheMetrics = metrics.NewNoopCollector() 243 if node.HeroCacheMetricsEnable { 244 heroCacheCollector = metrics.FollowerCacheMetrics(node.MetricsRegisterer) 245 } 246 247 packer := hotsignature.NewConsensusSigDataPacker(builder.Committee) 248 verifier := verification.NewCombinedVerifier(builder.Committee, packer) 249 val := hotstuffvalidator.New(builder.Committee, verifier) // verifier for HotStuff signature constructs (QCs, TCs, votes) 250 251 core, err := follower.NewComplianceCore( 252 node.Logger, 253 node.Metrics.Mempool, 254 heroCacheCollector, 255 builder.FollowerDistributor, 256 builder.FollowerState, 257 builder.FollowerCore, 258 val, 259 builder.SyncCore, 260 node.Tracer, 261 ) 262 if err != nil { 263 return nil, fmt.Errorf("could not create follower core: %w", err) 264 } 265 266 builder.FollowerEng, err = follower.NewComplianceLayer( 267 node.Logger, 268 node.EngineRegistry, 269 node.Me, 270 node.Metrics.Engine, 271 node.Storage.Headers, 272 builder.Finalized, 273 core, 274 node.ComplianceConfig, 275 follower.WithChannel(channels.PublicReceiveBlocks), 276 ) 277 if err != nil { 278 return nil, fmt.Errorf("could not create follower engine: %w", err) 279 } 280 builder.FollowerDistributor.AddOnBlockFinalizedConsumer(builder.FollowerEng.OnFinalizedBlock) 281 282 return builder.FollowerEng, nil 283 }) 284 285 return builder 286 } 287 288 func (builder *FollowerServiceBuilder) buildSyncEngine() *FollowerServiceBuilder { 289 builder.Component("sync engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 290 spamConfig, err := synceng.NewSpamDetectionConfig() 291 if err != nil { 292 return nil, fmt.Errorf("could not initialize spam detection config: %w", err) 293 } 294 295 sync, err := synceng.New( 296 node.Logger, 297 node.Metrics.Engine, 298 node.EngineRegistry, 299 node.Me, 300 node.State, 301 node.Storage.Blocks, 302 builder.FollowerEng, 303 builder.SyncCore, 304 builder.SyncEngineParticipantsProviderFactory(), 305 spamConfig, 306 ) 307 if err != nil { 308 return nil, fmt.Errorf("could not create synchronization engine: %w", err) 309 } 310 builder.SyncEng = sync 311 builder.FollowerDistributor.AddFinalizationConsumer(sync) 312 313 return builder.SyncEng, nil 314 }) 315 316 return builder 317 } 318 319 func (builder *FollowerServiceBuilder) BuildConsensusFollower() cmd.NodeBuilder { 320 builder. 321 buildFollowerState(). 322 buildSyncCore(). 323 buildCommittee(). 324 buildLatestHeader(). 325 buildFollowerCore(). 326 buildFollowerEngine(). 327 buildSyncEngine() 328 329 return builder 330 } 331 332 type FollowerOption func(*FollowerServiceConfig) 333 334 func WithBootStrapPeers(bootstrapNodes ...*flow.IdentitySkeleton) FollowerOption { 335 return func(config *FollowerServiceConfig) { 336 config.bootstrapIdentities = bootstrapNodes 337 } 338 } 339 340 func WithNetworkKey(key crypto.PrivateKey) FollowerOption { 341 return func(config *FollowerServiceConfig) { 342 config.NetworkKey = key 343 } 344 } 345 346 func WithBaseOptions(baseOptions []cmd.Option) FollowerOption { 347 return func(config *FollowerServiceConfig) { 348 config.baseOptions = baseOptions 349 } 350 } 351 352 func FlowConsensusFollowerService(opts ...FollowerOption) *FollowerServiceBuilder { 353 config := DefaultFollowerServiceConfig() 354 for _, opt := range opts { 355 opt(config) 356 } 357 ret := &FollowerServiceBuilder{ 358 FollowerServiceConfig: config, 359 // TODO: using RoleAccess here for now. This should be refactored eventually to have its own role type 360 FlowNodeBuilder: cmd.FlowNode(flow.RoleAccess.String(), config.baseOptions...), 361 FollowerDistributor: pubsub.NewFollowerDistributor(), 362 } 363 ret.FollowerDistributor.AddProposalViolationConsumer(notifications.NewSlashingViolationsConsumer(ret.Logger)) 364 // the observer gets a version of the root snapshot file that does not contain any node addresses 365 // hence skip all the root snapshot validations that involved an identity address 366 ret.FlowNodeBuilder.SkipNwAddressBasedValidations = true 367 return ret 368 } 369 370 func publicNetworkMsgValidators(log zerolog.Logger, idProvider module.IdentityProvider, selfID flow.Identifier) []network.MessageValidator { 371 return []network.MessageValidator{ 372 // filter out messages sent by this node itself 373 validator.ValidateNotSender(selfID), 374 validator.NewAnyValidator( 375 // message should be either from a valid staked node 376 validator.NewOriginValidator( 377 id.NewIdentityFilterIdentifierProvider(filter.IsValidCurrentEpochParticipant, idProvider), 378 ), 379 // or the message should be specifically targeted for this node 380 validator.ValidateTarget(log, selfID), 381 ), 382 } 383 } 384 385 // BootstrapIdentities converts the bootstrap node addresses and keys to a Flow Identity list where 386 // each Flow Identity is initialized with the passed address, the networking key 387 // and the Node ID set to ZeroID, role set to Access, 0 stake and no staking key. 388 func BootstrapIdentities(addresses []string, keys []string) (flow.IdentitySkeletonList, error) { 389 390 if len(addresses) != len(keys) { 391 return nil, fmt.Errorf("number of addresses and keys provided for the boostrap nodes don't match") 392 } 393 394 ids := make(flow.IdentitySkeletonList, len(addresses)) 395 for i, address := range addresses { 396 key := keys[i] 397 398 // json unmarshaller needs a quotes before and after the string 399 // the pflags.StringSliceVar does not retain quotes for the command line arg even if escaped with \" 400 // hence this additional check to ensure the key is indeed quoted 401 if !strings.HasPrefix(key, "\"") { 402 key = fmt.Sprintf("\"%s\"", key) 403 } 404 // networking public key 405 var networkKey encodable.NetworkPubKey 406 err := json.Unmarshal([]byte(key), &networkKey) 407 if err != nil { 408 return nil, err 409 } 410 411 // create the identity of the peer by setting only the relevant fields 412 ids[i] = &flow.IdentitySkeleton{ 413 NodeID: flow.ZeroID, // the NodeID is the hash of the staking key and for the public network it does not apply 414 Address: address, 415 Role: flow.RoleAccess, // the upstream node has to be an access node 416 NetworkPubKey: networkKey, 417 } 418 } 419 return ids, nil 420 } 421 422 func (builder *FollowerServiceBuilder) initNodeInfo() error { 423 // use the networking key that has been passed in the config, or load from the configured file 424 networkingKey := builder.FollowerServiceConfig.NetworkKey 425 426 pubKey, err := keyutils.LibP2PPublicKeyFromFlow(networkingKey.PublicKey()) 427 if err != nil { 428 return fmt.Errorf("could not load networking public key: %w", err) 429 } 430 431 builder.peerID, err = peer.IDFromPublicKey(pubKey) 432 if err != nil { 433 return fmt.Errorf("could not get peer ID from public key: %w", err) 434 } 435 436 builder.NodeID, err = translator.NewPublicNetworkIDTranslator().GetFlowID(builder.peerID) 437 if err != nil { 438 return fmt.Errorf("could not get flow node ID: %w", err) 439 } 440 441 builder.NodeConfig.NetworkKey = networkingKey // copy the key to NodeConfig 442 builder.NodeConfig.StakingKey = nil // no staking key for the observer 443 444 return nil 445 } 446 447 func (builder *FollowerServiceBuilder) InitIDProviders() { 448 builder.Module("id providers", func(node *cmd.NodeConfig) error { 449 idCache, err := cache.NewProtocolStateIDCache(node.Logger, node.State, builder.ProtocolEvents) 450 if err != nil { 451 return fmt.Errorf("could not initialize ProtocolStateIDCache: %w", err) 452 } 453 builder.IDTranslator = translator.NewHierarchicalIDTranslator(idCache, translator.NewPublicNetworkIDTranslator()) 454 455 // The following wrapper allows to disallow-list byzantine nodes via an admin command: 456 // the wrapper overrides the 'Ejected' flag of the disallow-listed nodes to true 457 builder.IdentityProvider, err = cache.NewNodeDisallowListWrapper(idCache, node.DB, func() network.DisallowListNotificationConsumer { 458 return builder.NetworkUnderlay 459 }) 460 if err != nil { 461 return fmt.Errorf("could not initialize NodeBlockListWrapper: %w", err) 462 } 463 464 // use the default identifier provider 465 builder.SyncEngineParticipantsProviderFactory = func() module.IdentifierProvider { 466 return id.NewCustomIdentifierProvider(func() flow.IdentifierList { 467 pids := builder.LibP2PNode.GetPeersForProtocol(protocols.FlowProtocolID(builder.SporkID)) 468 result := make(flow.IdentifierList, 0, len(pids)) 469 470 for _, pid := range pids { 471 // exclude own Identifier 472 if pid == builder.peerID { 473 continue 474 } 475 476 if flowID, err := builder.IDTranslator.GetFlowID(pid); err != nil { 477 // TODO: this is an instance of "log error and continue with best effort" anti-pattern 478 builder.Logger.Err(err).Str("peer", p2plogging.PeerId(pid)).Msg("failed to translate to Flow ID") 479 } else { 480 result = append(result, flowID) 481 } 482 } 483 484 return result 485 }) 486 } 487 488 return nil 489 }) 490 } 491 492 func (builder *FollowerServiceBuilder) Initialize() error { 493 // initialize default flow configuration 494 if err := config.Unmarshall(&builder.FlowConfig); err != nil { 495 return fmt.Errorf("failed to initialize flow config for follower builder: %w", err) 496 } 497 498 if err := builder.deriveBootstrapPeerIdentities(); err != nil { 499 return err 500 } 501 502 if err := builder.validateParams(); err != nil { 503 return err 504 } 505 506 if err := builder.initNodeInfo(); err != nil { 507 return err 508 } 509 510 builder.InitIDProviders() 511 512 builder.enqueuePublicNetworkInit() 513 514 builder.enqueueConnectWithStakedAN() 515 516 if builder.BaseConfig.MetricsEnabled { 517 builder.EnqueueMetricsServerInit() 518 if err := builder.RegisterBadgerMetrics(); err != nil { 519 return err 520 } 521 } 522 523 builder.PreInit(builder.initObserverLocal()) 524 525 return nil 526 } 527 528 func (builder *FollowerServiceBuilder) validateParams() error { 529 if builder.BaseConfig.BindAddr == cmd.NotSet || builder.BaseConfig.BindAddr == "" { 530 return errors.New("bind address not specified") 531 } 532 if builder.FollowerServiceConfig.NetworkKey == nil { 533 return errors.New("networking key not provided") 534 } 535 if len(builder.bootstrapIdentities) > 0 { 536 return nil 537 } 538 if len(builder.bootstrapNodeAddresses) == 0 { 539 return errors.New("no bootstrap node address provided") 540 } 541 if len(builder.bootstrapNodeAddresses) != len(builder.bootstrapNodePublicKeys) { 542 return errors.New("number of bootstrap node addresses and public keys should match") 543 } 544 return nil 545 } 546 547 // initPublicLibp2pNode creates a libp2p node for the follower service in public (unstaked) network. 548 // The LibP2P host is created with the following options: 549 // - DHT as client and seeded with the given bootstrap peers 550 // - The specified bind address as the listen address 551 // - The passed in private key as the libp2p key 552 // - No connection gater 553 // - No connection manager 554 // - No peer manager 555 // - Default libp2p pubsub options 556 // 557 // Args: 558 // - networkKey: the private key to use for the libp2p node 559 // 560 // Returns: 561 // - p2p.LibP2PNode: the libp2p node 562 // - error: if any error occurs. Any error returned from this function is irrecoverable. 563 func (builder *FollowerServiceBuilder) initPublicLibp2pNode(networkKey crypto.PrivateKey) (p2p.LibP2PNode, error) { 564 var pis []peer.AddrInfo 565 566 for _, b := range builder.bootstrapIdentities { 567 pi, err := utils.PeerAddressInfo(*b) 568 if err != nil { 569 return nil, fmt.Errorf("could not extract peer address info from bootstrap identity %v: %w", b, err) 570 } 571 572 pis = append(pis, pi) 573 } 574 575 node, err := p2pbuilder.NewNodeBuilder( 576 builder.Logger, 577 &builder.FlowConfig.NetworkConfig.GossipSub, 578 &p2pbuilderconfig.MetricsConfig{ 579 HeroCacheFactory: builder.HeroCacheMetricsFactory(), 580 Metrics: builder.Metrics.Network, 581 }, 582 network.PublicNetwork, 583 builder.BaseConfig.BindAddr, 584 networkKey, 585 builder.SporkID, 586 builder.IdentityProvider, 587 &builder.FlowConfig.NetworkConfig.ResourceManager, 588 p2pbuilderconfig.PeerManagerDisableConfig(), // disable peer manager for follower 589 &p2p.DisallowListCacheConfig{ 590 MaxSize: builder.FlowConfig.NetworkConfig.DisallowListNotificationCacheSize, 591 Metrics: metrics.DisallowListCacheMetricsFactory(builder.HeroCacheMetricsFactory(), network.PublicNetwork), 592 }, 593 &p2pbuilderconfig.UnicastConfig{ 594 Unicast: builder.FlowConfig.NetworkConfig.Unicast, 595 }). 596 SetSubscriptionFilter( 597 subscription.NewRoleBasedFilter( 598 subscription.UnstakedRole, builder.IdentityProvider, 599 ), 600 ). 601 SetRoutingSystem(func(ctx context.Context, h host.Host) (routing.Routing, error) { 602 return p2pdht.NewDHT(ctx, h, protocols.FlowPublicDHTProtocolID(builder.SporkID), 603 builder.Logger, 604 builder.Metrics.Network, 605 p2pdht.AsClient(), 606 dht.BootstrapPeers(pis...), 607 ) 608 }).Build() 609 if err != nil { 610 return nil, fmt.Errorf("could not build public libp2p node: %w", err) 611 } 612 613 builder.LibP2PNode = node 614 615 return builder.LibP2PNode, nil 616 } 617 618 // initObserverLocal initializes the observer's ID, network key and network address 619 // Currently, it reads a node-info.priv.json like any other node. 620 // TODO: read the node ID from the special bootstrap files 621 func (builder *FollowerServiceBuilder) initObserverLocal() func(node *cmd.NodeConfig) error { 622 return func(node *cmd.NodeConfig) error { 623 // for an observer, set the identity here explicitly since it will not be found in the protocol state 624 self := flow.IdentitySkeleton{ 625 NodeID: node.NodeID, 626 NetworkPubKey: node.NetworkKey.PublicKey(), 627 StakingPubKey: nil, // no staking key needed for the observer 628 Role: flow.RoleAccess, // observer can only run as an access node 629 Address: builder.BindAddr, 630 } 631 632 var err error 633 node.Me, err = local.NewNoKey(self) 634 if err != nil { 635 return fmt.Errorf("could not initialize local: %w", err) 636 } 637 return nil 638 } 639 } 640 641 // Build enqueues the sync engine and the follower engine for the observer. 642 // Currently, the observer only runs the follower engine. 643 func (builder *FollowerServiceBuilder) Build() (cmd.Node, error) { 644 builder.BuildConsensusFollower() 645 return builder.FlowNodeBuilder.Build() 646 } 647 648 // enqueuePublicNetworkInit enqueues the observer network component initialized for the observer 649 func (builder *FollowerServiceBuilder) enqueuePublicNetworkInit() { 650 var publicLibp2pNode p2p.LibP2PNode 651 builder. 652 Component("public libp2p node", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 653 var err error 654 publicLibp2pNode, err = builder.initPublicLibp2pNode(node.NetworkKey) 655 if err != nil { 656 return nil, fmt.Errorf("could not create public libp2p node: %w", err) 657 } 658 659 return publicLibp2pNode, nil 660 }). 661 Component("public network", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { 662 receiveCache := netcache.NewHeroReceiveCache(builder.FlowConfig.NetworkConfig.NetworkReceivedMessageCacheSize, 663 builder.Logger, 664 metrics.NetworkReceiveCacheMetricsFactory(builder.HeroCacheMetricsFactory(), network.PublicNetwork)) 665 666 err := node.Metrics.Mempool.Register(metrics.PrependPublicPrefix(metrics.ResourceNetworkingReceiveCache), receiveCache.Size) 667 if err != nil { 668 return nil, fmt.Errorf("could not register networking receive cache metric: %w", err) 669 } 670 671 net, err := underlay.NewNetwork(&underlay.NetworkConfig{ 672 Logger: builder.Logger.With().Str("component", "public-network").Logger(), 673 Codec: cborcodec.NewCodec(), 674 Me: builder.Me, 675 Libp2pNode: publicLibp2pNode, 676 Topology: nil, // topology is nil since it is automatically managed by libp2p // TODO: can we use empty topology? 677 Metrics: builder.Metrics.Network, 678 BitSwapMetrics: builder.Metrics.Bitswap, 679 IdentityProvider: builder.IdentityProvider, 680 ReceiveCache: receiveCache, 681 ConduitFactory: conduit.NewDefaultConduitFactory(), 682 SporkId: builder.SporkID, 683 UnicastMessageTimeout: underlay.DefaultUnicastTimeout, 684 IdentityTranslator: builder.IDTranslator, 685 AlspCfg: &alspmgr.MisbehaviorReportManagerConfig{ 686 Logger: builder.Logger, 687 SpamRecordCacheSize: builder.FlowConfig.NetworkConfig.AlspConfig.SpamRecordCacheSize, 688 SpamReportQueueSize: builder.FlowConfig.NetworkConfig.AlspConfig.SpamReportQueueSize, 689 DisablePenalty: builder.FlowConfig.NetworkConfig.AlspConfig.DisablePenalty, 690 HeartBeatInterval: builder.FlowConfig.NetworkConfig.AlspConfig.HearBeatInterval, 691 AlspMetrics: builder.Metrics.Network, 692 HeroCacheMetricsFactory: builder.HeroCacheMetricsFactory(), 693 NetworkType: network.PublicNetwork, 694 }, 695 SlashingViolationConsumerFactory: func(adapter network.ConduitAdapter) network.ViolationsConsumer { 696 return slashing.NewSlashingViolationsConsumer(builder.Logger, builder.Metrics.Network, adapter) 697 }, 698 }, underlay.WithMessageValidators(publicNetworkMsgValidators(node.Logger, node.IdentityProvider, node.NodeID)...)) 699 if err != nil { 700 return nil, fmt.Errorf("could not initialize network: %w", err) 701 } 702 703 builder.NetworkUnderlay = net 704 builder.EngineRegistry = converter.NewNetwork(net, channels.SyncCommittee, channels.PublicSyncCommittee) 705 706 builder.Logger.Info().Msgf("network will run on address: %s", builder.BindAddr) 707 708 idEvents := gadgets.NewIdentityDeltas(builder.NetworkUnderlay.UpdateNodeAddresses) 709 builder.ProtocolEvents.AddConsumer(idEvents) 710 711 return builder.EngineRegistry, nil 712 }) 713 } 714 715 // enqueueConnectWithStakedAN enqueues the upstream connector component which connects the libp2p host of the observer 716 // AN with the staked AN. 717 // Currently, there is an issue with LibP2P stopping advertisements of subscribed topics if no peers are connected 718 // (https://github.com/libp2p/go-libp2p-pubsub/issues/442). This means that an observer could end up not being 719 // discovered by other observers if it subscribes to a topic before connecting to the staked AN. Hence, the need 720 // of an explicit connect to the staked AN before the node attempts to subscribe to topics. 721 func (builder *FollowerServiceBuilder) enqueueConnectWithStakedAN() { 722 builder.Component("upstream connector", func(_ *cmd.NodeConfig) (module.ReadyDoneAware, error) { 723 return upstream.NewUpstreamConnector(builder.bootstrapIdentities, builder.LibP2PNode, builder.Logger), nil 724 }) 725 }