github.com/onflow/flow-go@v0.33.17/network/p2p/pubsub.go (about)

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	pubsub "github.com/libp2p/go-libp2p-pubsub"
     9  	pb "github.com/libp2p/go-libp2p-pubsub/pb"
    10  	"github.com/libp2p/go-libp2p/core/peer"
    11  	"github.com/libp2p/go-libp2p/core/routing"
    12  
    13  	"github.com/onflow/flow-go/engine/collection"
    14  	"github.com/onflow/flow-go/module/component"
    15  	"github.com/onflow/flow-go/network/channels"
    16  )
    17  
    18  type ValidationResult int
    19  
    20  const (
    21  	ValidationAccept ValidationResult = iota
    22  	ValidationIgnore
    23  	ValidationReject
    24  )
    25  
    26  type TopicValidatorFunc func(context.Context, peer.ID, *pubsub.Message) ValidationResult
    27  
    28  // PubSubAdapter is the abstraction of the underlying pubsub logic that is used by the Flow network.
    29  type PubSubAdapter interface {
    30  	component.Component
    31  	// CollectionClusterChangesConsumer  is the interface for consuming the events of changes in the collection cluster.
    32  	// This is used to notify the node of changes in the collection cluster.
    33  	// PubSubAdapter implements this interface and consumes the events to be notified of changes in the clustering channels.
    34  	// The clustering channels are used by the collection nodes of a cluster to communicate with each other.
    35  	// As the cluster (and hence their cluster channels) of collection nodes changes over time (per epoch) the node needs to be notified of these changes.
    36  	CollectionClusterChangesConsumer
    37  	// RegisterTopicValidator registers a validator for topic.
    38  	RegisterTopicValidator(topic string, topicValidator TopicValidatorFunc) error
    39  
    40  	// UnregisterTopicValidator removes a validator from a topic.
    41  	// Returns an error if there was no validator registered with the topic.
    42  	UnregisterTopicValidator(topic string) error
    43  
    44  	// Join joins the topic and returns a Topic handle.
    45  	// Only one Topic handle should exist per topic, and Join will error if the Topic handle already exists.
    46  	Join(topic string) (Topic, error)
    47  
    48  	// GetTopics returns all the topics within the pubsub network that the current peer has subscribed to.
    49  	GetTopics() []string
    50  
    51  	// ListPeers returns all the peers subscribed to a topic.
    52  	// Note that the current peer must be subscribed to the topic for it to query for other peers.
    53  	// If the current peer is not subscribed to the topic, an empty list is returned.
    54  	// For example, if current peer has subscribed to topics A and B, then ListPeers only return
    55  	// subscribed peers for topics A and B, and querying for topic C will return an empty list.
    56  	ListPeers(topic string) []peer.ID
    57  
    58  	// GetLocalMeshPeers returns the list of peers in the local mesh for the given topic.
    59  	// Args:
    60  	// - topic: the topic.
    61  	// Returns:
    62  	// - []peer.ID: the list of peers in the local mesh for the given topic.
    63  	GetLocalMeshPeers(topic channels.Topic) []peer.ID
    64  
    65  	// PeerScoreExposer returns the peer score exposer for the gossipsub adapter. The exposer is a read-only interface
    66  	// for querying peer scores and returns the local scoring table of the underlying gossipsub node.
    67  	// The exposer is only available if the gossipsub adapter was configured with a score tracer.
    68  	// If the gossipsub adapter was not configured with a score tracer, the exposer will be nil.
    69  	// Args:
    70  	//     None.
    71  	// Returns:
    72  	//    The peer score exposer for the gossipsub adapter.
    73  	PeerScoreExposer() PeerScoreExposer
    74  }
    75  
    76  // PubSubAdapterConfig abstracts the configuration for the underlying pubsub implementation.
    77  type PubSubAdapterConfig interface {
    78  	WithRoutingDiscovery(routing.ContentRouting)
    79  	WithSubscriptionFilter(SubscriptionFilter)
    80  	WithScoreOption(ScoreOptionBuilder)
    81  	WithMessageIdFunction(f func([]byte) string)
    82  	WithTracer(t PubSubTracer)
    83  	// WithScoreTracer sets the tracer for the underlying pubsub score implementation.
    84  	// This is used to expose the local scoring table of the GossipSub node to its higher level components.
    85  	WithScoreTracer(tracer PeerScoreTracer)
    86  	WithInspectorSuite(GossipSubInspectorSuite)
    87  }
    88  
    89  // GossipSubRPCInspector app specific RPC inspector used to inspect and validate incoming RPC messages before they are processed by libp2p.
    90  // Implementations must:
    91  //   - be concurrency safe
    92  //   - be non-blocking
    93  type GossipSubRPCInspector interface {
    94  	component.Component
    95  
    96  	// Name returns the name of the rpc inspector.
    97  	Name() string
    98  
    99  	// Inspect inspects an incoming RPC message. This callback func is invoked
   100  	// on ever RPC message received before the message is processed by libp2p.
   101  	// If this func returns any error the RPC message will be dropped.
   102  	Inspect(peer.ID, *pubsub.RPC) error
   103  }
   104  
   105  // GossipSubMsgValidationRpcInspector abstracts the general behavior of an app specific RPC inspector specifically
   106  // used to inspect and validate incoming. It is used to implement custom message validation logic. It is injected into
   107  // the GossipSubRouter and run on every incoming RPC message before the message is processed by libp2p. If the message
   108  // is invalid the RPC message will be dropped.
   109  // Implementations must:
   110  //   - be concurrency safe
   111  //   - be non-blocking
   112  type GossipSubMsgValidationRpcInspector interface {
   113  	collection.ClusterEvents
   114  	GossipSubRPCInspector
   115  }
   116  
   117  // Topic is the abstraction of the underlying pubsub topic that is used by the Flow network.
   118  type Topic interface {
   119  	// String returns the topic name as a string.
   120  	String() string
   121  
   122  	// Close closes the topic.
   123  	Close() error
   124  
   125  	// Publish publishes a message to the topic.
   126  	Publish(context.Context, []byte) error
   127  
   128  	// Subscribe returns a subscription to the topic so that the caller can receive messages from the topic.
   129  	Subscribe() (Subscription, error)
   130  }
   131  
   132  // ScoreOptionBuilder abstracts the configuration for the underlying pubsub score implementation.
   133  type ScoreOptionBuilder interface {
   134  	component.Component
   135  	// BuildFlowPubSubScoreOption builds the pubsub score options as pubsub.Option for the Flow network.
   136  	BuildFlowPubSubScoreOption() (*pubsub.PeerScoreParams, *pubsub.PeerScoreThresholds)
   137  	// TopicScoreParams returns the topic score params for the given topic.
   138  	// If the topic score params for the given topic does not exist, it will return the default topic score params.
   139  	TopicScoreParams(*pubsub.Topic) *pubsub.TopicScoreParams
   140  }
   141  
   142  // Subscription is the abstraction of the underlying pubsub subscription that is used by the Flow network.
   143  type Subscription interface {
   144  	// Cancel cancels the subscription so that the caller will no longer receive messages from the topic.
   145  	Cancel()
   146  
   147  	// Topic returns the topic that the subscription is subscribed to.
   148  	Topic() string
   149  
   150  	// Next returns the next message from the subscription.
   151  	Next(context.Context) (*pubsub.Message, error)
   152  }
   153  
   154  // BasePubSubAdapterConfig is the base configuration for the underlying pubsub implementation.
   155  // These configurations are common to all pubsub implementations and must be observed by all implementations.
   156  type BasePubSubAdapterConfig struct {
   157  	// MaxMessageSize is the maximum size of a message that can be sent on the pubsub network.
   158  	MaxMessageSize int
   159  }
   160  
   161  // SubscriptionFilter is the abstraction of the underlying pubsub subscription filter that is used by the Flow network.
   162  type SubscriptionFilter interface {
   163  	// CanSubscribe returns true if the current peer can subscribe to the topic.
   164  	CanSubscribe(string) bool
   165  
   166  	// FilterIncomingSubscriptions is invoked for all RPCs containing subscription notifications.
   167  	// It filters and returns the subscriptions of interest to the current node.
   168  	FilterIncomingSubscriptions(peer.ID, []*pb.RPC_SubOpts) ([]*pb.RPC_SubOpts, error)
   169  }
   170  
   171  // PubSubTracer is the abstraction of the underlying pubsub tracer that is used by the Flow network. It wraps the
   172  // pubsub.RawTracer interface with the component.Component interface so that it can be started and stopped.
   173  // The RawTracer interface is used to trace the internal events of the pubsub system.
   174  type PubSubTracer interface {
   175  	component.Component
   176  	pubsub.RawTracer
   177  	RpcControlTracking
   178  	// GetLocalMeshPeers returns the list of peers in the mesh for the given topic.
   179  	// Args:
   180  	// - topic: the topic.
   181  	// Returns:
   182  	// - []peer.ID: the list of peers in the mesh for the given topic.
   183  	GetLocalMeshPeers(topic channels.Topic) []peer.ID
   184  }
   185  
   186  // RpcControlTracking is the abstraction of the underlying libp2p control message tracker used to track message ids advertised by the iHave control messages.
   187  // This collection of methods can ensure an iWant control message for a message-id corresponds to a broadcast iHave message id. Implementations
   188  // must be non-blocking and concurrency safe.
   189  type RpcControlTracking interface {
   190  	// LastHighestIHaveRPCSize returns the last highest size of iHaves sent in a rpc.
   191  	LastHighestIHaveRPCSize() int64
   192  	// WasIHaveRPCSent checks if an iHave control message with the provided message ID was sent.
   193  	WasIHaveRPCSent(messageID string) bool
   194  }
   195  
   196  // PeerScoreSnapshot is a snapshot of the overall peer score at a given time.
   197  type PeerScoreSnapshot struct {
   198  	// Score the overall score of the peer.
   199  	Score float64
   200  	// Topics map that stores the score of the peer per topic.
   201  	Topics map[string]*TopicScoreSnapshot
   202  	// AppSpecificScore application specific score (set by Flow protocol).
   203  	AppSpecificScore float64
   204  
   205  	// A positive value indicates that the peer is colocated with other nodes on the same network id,
   206  	// and can be used to warn of sybil attacks.
   207  	IPColocationFactor float64
   208  	// A positive value indicates that GossipSub has caught the peer misbehaving, and can be used to warn of an attack.
   209  	BehaviourPenalty float64
   210  }
   211  
   212  // TopicScoreSnapshot is a snapshot of the peer score within a topic at a given time.
   213  // Note that float64 is used for the counters as they are decayed over the time.
   214  type TopicScoreSnapshot struct {
   215  	// TimeInMesh total time in mesh.
   216  	TimeInMesh time.Duration
   217  	// FirstMessageDeliveries counter of first message deliveries.
   218  	FirstMessageDeliveries float64
   219  	// MeshMessageDeliveries total mesh message deliveries (in the mesh).
   220  	MeshMessageDeliveries float64
   221  	// InvalidMessageDeliveries counter of invalid message deliveries.
   222  	InvalidMessageDeliveries float64
   223  }
   224  
   225  // IsWarning returns true if the peer score is in warning state. When the peer score is in warning state, the peer is
   226  // considered to be misbehaving.
   227  func (p PeerScoreSnapshot) IsWarning() bool {
   228  	// Check if any topic is in warning state.
   229  	for _, topic := range p.Topics {
   230  		if topic.IsWarning() {
   231  			return true
   232  		}
   233  	}
   234  
   235  	// Check overall score.
   236  	switch {
   237  	case p.Score < -1:
   238  		// If the overall score is negative, the peer is in warning state, it means that the peer is suspected to be
   239  		// misbehaving at the GossipSub level.
   240  		return true
   241  	// Check app-specific score.
   242  	case p.AppSpecificScore < -1:
   243  		// If the app specific score is negative, the peer is in warning state, it means that the peer behaves in a way
   244  		// that is not allowed by the Flow protocol.
   245  		return true
   246  	// Check IP colocation factor.
   247  	case p.IPColocationFactor > 5:
   248  		// If the IP colocation factor is positive, the peer is in warning state, it means that the peer is running on the
   249  		// same IP as another peer and is suspected to be a sybil node. For now, we set it to a high value to make sure
   250  		// that peers from the same operator are not marked as sybil nodes.
   251  		// TODO: this should be revisited once the collocation penalty is enabled.
   252  		return true
   253  	// Check behaviour penalty.
   254  	case p.BehaviourPenalty > 20:
   255  		// If the behaviour penalty is positive, the peer is in warning state, it means that the peer is suspected to be
   256  		// misbehaving at the GossipSub level, e.g. sending too many duplicate messages. Setting it to 20 to reduce the noise; 20 is twice the threshold (defaultBehaviourPenaltyThreshold).
   257  		return true
   258  	// If none of the conditions are met, return false.
   259  	default:
   260  		return false
   261  	}
   262  }
   263  
   264  // String returns the string representation of the peer score snapshot.
   265  func (s TopicScoreSnapshot) String() string {
   266  	return fmt.Sprintf("time_in_mesh: %s, first_message_deliveries: %f, mesh message deliveries: %f, invalid message deliveries: %f",
   267  		s.TimeInMesh, s.FirstMessageDeliveries, s.MeshMessageDeliveries, s.InvalidMessageDeliveries)
   268  }
   269  
   270  // IsWarning returns true if the topic score is in warning state.
   271  func (s TopicScoreSnapshot) IsWarning() bool {
   272  	// TODO: also check for first message deliveries and time in mesh when we have a better understanding of the score.
   273  	// If invalid message deliveries is positive, the topic is in warning state. It means that the peer is suspected to
   274  	// be misbehaving at the GossipSub level, e.g. sending too many invalid messages to the topic.
   275  	return s.InvalidMessageDeliveries > 0
   276  }
   277  
   278  // PeerScoreTracer is the interface for the tracer that is used to trace the peer score.
   279  type PeerScoreTracer interface {
   280  	component.Component
   281  	PeerScoreExposer
   282  	// UpdatePeerScoreSnapshots updates the peer score snapshot/
   283  	UpdatePeerScoreSnapshots(map[peer.ID]*PeerScoreSnapshot)
   284  
   285  	// UpdateInterval returns the update interval for the tracer. The tracer will be receiving updates
   286  	// at this interval.
   287  	UpdateInterval() time.Duration
   288  }
   289  
   290  // PeerScoreExposer is the interface for the tracer that is used to expose the peers score.
   291  type PeerScoreExposer interface {
   292  	// GetScore returns the overall score for the given peer.
   293  	GetScore(peerID peer.ID) (float64, bool)
   294  	// GetAppScore returns the application score for the given peer.
   295  	GetAppScore(peerID peer.ID) (float64, bool)
   296  	// GetIPColocationFactor returns the IP colocation factor for the given peer.
   297  	GetIPColocationFactor(peerID peer.ID) (float64, bool)
   298  	// GetBehaviourPenalty returns the behaviour penalty for the given peer.
   299  	GetBehaviourPenalty(peerID peer.ID) (float64, bool)
   300  	// GetTopicScores returns the topic scores for the given peer for all topics.
   301  	// The returned map is keyed by topic name.
   302  	GetTopicScores(peerID peer.ID) (map[string]TopicScoreSnapshot, bool)
   303  }