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  }