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