github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/node/gossipSubAdapter.go (about)

     1  package p2pnode
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	pubsub "github.com/libp2p/go-libp2p-pubsub"
     8  	"github.com/libp2p/go-libp2p/core/host"
     9  	"github.com/libp2p/go-libp2p/core/peer"
    10  	"github.com/rs/zerolog"
    11  
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/module/component"
    14  	"github.com/onflow/flow-go/module/irrecoverable"
    15  	"github.com/onflow/flow-go/network/channels"
    16  	"github.com/onflow/flow-go/network/p2p"
    17  	"github.com/onflow/flow-go/network/p2p/utils"
    18  	"github.com/onflow/flow-go/utils/logging"
    19  )
    20  
    21  // GossipSubAdapter is a wrapper around the libp2p GossipSub implementation
    22  // that implements the PubSubAdapter interface for the Flow network.
    23  type GossipSubAdapter struct {
    24  	component.Component
    25  	gossipSub *pubsub.PubSub
    26  	// topicScoreParamFunc is a function that returns the topic score params for a given topic.
    27  	// If no function is provided the node will join the topic with no scoring params. As the
    28  	// node will not be able to score other peers in the topic, it may be vulnerable to routing
    29  	// attacks on the topic that may also affect the overall function of the node.
    30  	// It is not recommended to use this adapter without a topicScoreParamFunc. Also in mature
    31  	// implementations of the Flow network, the topicScoreParamFunc must be a required parameter.
    32  	topicScoreParamFunc func(topic *pubsub.Topic) *pubsub.TopicScoreParams
    33  	logger              zerolog.Logger
    34  	peerScoreExposer    p2p.PeerScoreExposer
    35  	localMeshTracer     p2p.PubSubTracer
    36  	// clusterChangeConsumer is a callback that is invoked when the set of active clusters of collection nodes changes.
    37  	// This callback is implemented by the rpc inspector suite of the GossipSubAdapter, and consumes the cluster changes
    38  	// to update the rpc inspector state of the recent topics (i.e., channels).
    39  	clusterChangeConsumer p2p.CollectionClusterChangesConsumer
    40  }
    41  
    42  var _ p2p.PubSubAdapter = (*GossipSubAdapter)(nil)
    43  
    44  func NewGossipSubAdapter(ctx context.Context,
    45  	logger zerolog.Logger,
    46  	h host.Host,
    47  	cfg p2p.PubSubAdapterConfig,
    48  	clusterChangeConsumer p2p.CollectionClusterChangesConsumer) (p2p.PubSubAdapter, error) {
    49  	gossipSubConfig, ok := cfg.(*GossipSubAdapterConfig)
    50  	if !ok {
    51  		return nil, fmt.Errorf("invalid gossipsub config type: %T", cfg)
    52  	}
    53  
    54  	gossipSub, err := pubsub.NewGossipSub(ctx, h, gossipSubConfig.Build()...)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	builder := component.NewComponentManagerBuilder()
    60  
    61  	a := &GossipSubAdapter{
    62  		gossipSub:             gossipSub,
    63  		logger:                logger.With().Str("component", "gossipsub-adapter").Logger(),
    64  		clusterChangeConsumer: clusterChangeConsumer,
    65  	}
    66  
    67  	topicScoreParamFunc, ok := gossipSubConfig.TopicScoreParamFunc()
    68  	if ok {
    69  		a.topicScoreParamFunc = topicScoreParamFunc
    70  	} else {
    71  		a.logger.Warn().Msg("no topic score param func provided")
    72  	}
    73  
    74  	if scoreTracer := gossipSubConfig.ScoreTracer(); scoreTracer != nil {
    75  		builder.AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
    76  			ready()
    77  			a.logger.Info().Msg("starting score tracer")
    78  			scoreTracer.Start(ctx)
    79  			select {
    80  			case <-ctx.Done():
    81  				a.logger.Warn().Msg("aborting score tracer startup due to context done")
    82  			case <-scoreTracer.Ready():
    83  				a.logger.Info().Msg("score tracer is ready")
    84  			}
    85  			ready()
    86  
    87  			<-ctx.Done()
    88  			a.logger.Info().Msg("stopping score tracer")
    89  			<-scoreTracer.Done()
    90  			a.logger.Info().Msg("score tracer stopped")
    91  		})
    92  		a.peerScoreExposer = scoreTracer
    93  	}
    94  
    95  	if tracer := gossipSubConfig.PubSubTracer(); tracer != nil {
    96  		builder.AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
    97  			a.logger.Info().Msg("starting pubsub tracer")
    98  			tracer.Start(ctx)
    99  			select {
   100  			case <-ctx.Done():
   101  				a.logger.Warn().Msg("aborting pubsub tracer startup due to context done")
   102  			case <-tracer.Ready():
   103  				a.logger.Info().Msg("pubsub tracer is ready")
   104  			}
   105  			ready()
   106  
   107  			<-ctx.Done()
   108  			a.logger.Info().Msg("stopping pubsub tracer")
   109  			<-tracer.Done()
   110  			a.logger.Info().Msg("pubsub tracer stopped")
   111  		})
   112  		a.localMeshTracer = tracer
   113  	}
   114  
   115  	if inspectorSuite := gossipSubConfig.RpcInspectorComponent(); inspectorSuite != nil {
   116  		builder.AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
   117  			a.logger.Info().Msg("starting inspector suite")
   118  			inspectorSuite.Start(ctx)
   119  			select {
   120  			case <-ctx.Done():
   121  				a.logger.Warn().Msg("aborting inspector suite startup due to context done")
   122  			case <-inspectorSuite.Ready():
   123  				a.logger.Info().Msg("inspector suite is ready")
   124  			}
   125  			ready()
   126  
   127  			<-ctx.Done()
   128  			a.logger.Info().Msg("stopping inspector suite")
   129  			<-inspectorSuite.Done()
   130  			a.logger.Info().Msg("inspector suite stopped")
   131  		})
   132  	}
   133  
   134  	if scoringComponent := gossipSubConfig.ScoringComponent(); scoringComponent != nil {
   135  		builder.AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
   136  			a.logger.Info().Msg("starting gossipsub scoring component")
   137  			scoringComponent.Start(ctx)
   138  			select {
   139  			case <-ctx.Done():
   140  				a.logger.Warn().Msg("aborting gossipsub scoring component startup due to context done")
   141  			case <-scoringComponent.Ready():
   142  				a.logger.Info().Msg("gossipsub scoring component is ready")
   143  			}
   144  			ready()
   145  
   146  			<-ctx.Done()
   147  			a.logger.Info().Msg("stopping gossipsub scoring component")
   148  			<-scoringComponent.Done()
   149  			a.logger.Info().Msg("gossipsub scoring component stopped")
   150  		})
   151  	}
   152  
   153  	a.Component = builder.Build()
   154  
   155  	return a, nil
   156  }
   157  
   158  func (g *GossipSubAdapter) RegisterTopicValidator(topic string, topicValidator p2p.TopicValidatorFunc) error {
   159  	// wrap the topic validator function into a libp2p topic validator function.
   160  	var v pubsub.ValidatorEx = func(ctx context.Context, from peer.ID, message *pubsub.Message) pubsub.ValidationResult {
   161  		switch result := topicValidator(ctx, from, message); result {
   162  		case p2p.ValidationAccept:
   163  			return pubsub.ValidationAccept
   164  		case p2p.ValidationIgnore:
   165  			return pubsub.ValidationIgnore
   166  		case p2p.ValidationReject:
   167  			return pubsub.ValidationReject
   168  		default:
   169  			// should never happen, indicates a bug in the topic validator
   170  			g.logger.Fatal().Msgf("invalid validation result: %v", result)
   171  		}
   172  		// should never happen, indicates a bug in the topic validator, but we need to return something
   173  		g.logger.Warn().
   174  			Bool(logging.KeySuspicious, true).
   175  			Msg("invalid validation result, returning reject")
   176  		return pubsub.ValidationReject
   177  	}
   178  
   179  	return g.gossipSub.RegisterTopicValidator(topic, v, pubsub.WithValidatorInline(true))
   180  }
   181  
   182  func (g *GossipSubAdapter) UnregisterTopicValidator(topic string) error {
   183  	return g.gossipSub.UnregisterTopicValidator(topic)
   184  }
   185  
   186  func (g *GossipSubAdapter) Join(topic string) (p2p.Topic, error) {
   187  	t, err := g.gossipSub.Join(topic)
   188  	if err != nil {
   189  		return nil, fmt.Errorf("could not join topic %s: %w", topic, err)
   190  	}
   191  
   192  	if g.topicScoreParamFunc != nil {
   193  		topicParams := g.topicScoreParamFunc(t)
   194  		err = t.SetScoreParams(topicParams)
   195  		if err != nil {
   196  			return nil, fmt.Errorf("could not set score params for topic %s: %w", topic, err)
   197  		}
   198  		topicParamsLogger := utils.TopicScoreParamsLogger(g.logger, topic, topicParams)
   199  		topicParamsLogger.Info().Msg("joined topic with score params set")
   200  	} else {
   201  		g.logger.Warn().
   202  			Bool(logging.KeyNetworkingSecurity, true).
   203  			Str("topic", topic).
   204  			Msg("joining topic without score params, this is not recommended from a security perspective")
   205  	}
   206  	return NewGossipSubTopic(t), nil
   207  }
   208  
   209  func (g *GossipSubAdapter) GetTopics() []string {
   210  	return g.gossipSub.GetTopics()
   211  }
   212  
   213  func (g *GossipSubAdapter) ListPeers(topic string) []peer.ID {
   214  	return g.gossipSub.ListPeers(topic)
   215  }
   216  
   217  // GetLocalMeshPeers returns the list of peers in the local mesh for the given topic.
   218  // Args:
   219  // - topic: the topic.
   220  // Returns:
   221  // - []peer.ID: the list of peers in the local mesh for the given topic.
   222  func (g *GossipSubAdapter) GetLocalMeshPeers(topic channels.Topic) []peer.ID {
   223  	return g.localMeshTracer.GetLocalMeshPeers(topic)
   224  }
   225  
   226  // PeerScoreExposer returns the peer score exposer for the gossipsub adapter. The exposer is a read-only interface
   227  // for querying peer scores and returns the local scoring table of the underlying gossipsub node.
   228  // The exposer is only available if the gossipsub adapter was configured with a score tracer.
   229  // If the gossipsub adapter was not configured with a score tracer, the exposer will be nil.
   230  // Args:
   231  //
   232  //	None.
   233  //
   234  // Returns:
   235  //
   236  //	The peer score exposer for the gossipsub adapter.
   237  func (g *GossipSubAdapter) PeerScoreExposer() p2p.PeerScoreExposer {
   238  	return g.peerScoreExposer
   239  }
   240  
   241  // ActiveClustersChanged is called when the active clusters of collection nodes changes.
   242  // GossipSubAdapter implements this method to forward the call to the clusterChangeConsumer (rpc inspector),
   243  // which will then update the cluster state of the rpc inspector.
   244  // Args:
   245  // - lst: the list of active clusters
   246  // Returns:
   247  // - void
   248  func (g *GossipSubAdapter) ActiveClustersChanged(lst flow.ChainIDList) {
   249  	g.clusterChangeConsumer.ActiveClustersChanged(lst)
   250  }