github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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  	WithRpcInspector(GossipSubRPCInspector)
    87  }
    88  
    89  // GossipSubRPCInspector abstracts the general behavior of an app specific RPC inspector specifically
    90  // used to inspect and validate incoming. It is used to implement custom message validation logic. It is injected into
    91  // the GossipSubRouter and run on every incoming RPC message before the message is processed by libp2p. If the message
    92  // is invalid the RPC message will be dropped.
    93  // Implementations must:
    94  //   - be concurrency safe
    95  //   - be non-blocking
    96  type GossipSubRPCInspector interface {
    97  	collection.ClusterEvents
    98  	component.Component
    99  
   100  	// Name returns the name of the rpc inspector.
   101  	Name() string
   102  
   103  	// Inspect inspects an incoming RPC message. This callback func is invoked
   104  	// on ever RPC message received before the message is processed by libp2p.
   105  	// If this func returns any error the RPC message will be dropped.
   106  	Inspect(peer.ID, *pubsub.RPC) error
   107  }
   108  
   109  // Topic is the abstraction of the underlying pubsub topic that is used by the Flow network.
   110  type Topic interface {
   111  	// String returns the topic name as a string.
   112  	String() string
   113  
   114  	// Close closes the topic.
   115  	Close() error
   116  
   117  	// Publish publishes a message to the topic.
   118  	Publish(context.Context, []byte) error
   119  
   120  	// Subscribe returns a subscription to the topic so that the caller can receive messages from the topic.
   121  	Subscribe() (Subscription, error)
   122  }
   123  
   124  // ScoreOptionBuilder abstracts the configuration for the underlying pubsub score implementation.
   125  type ScoreOptionBuilder interface {
   126  	component.Component
   127  	// BuildFlowPubSubScoreOption builds the pubsub score options as pubsub.Option for the Flow network.
   128  	BuildFlowPubSubScoreOption() (*pubsub.PeerScoreParams, *pubsub.PeerScoreThresholds)
   129  	// TopicScoreParams returns the topic score params for the given topic.
   130  	// If the topic score params for the given topic does not exist, it will return the default topic score params.
   131  	TopicScoreParams(*pubsub.Topic) *pubsub.TopicScoreParams
   132  }
   133  
   134  // Subscription is the abstraction of the underlying pubsub subscription that is used by the Flow network.
   135  type Subscription interface {
   136  	// Cancel cancels the subscription so that the caller will no longer receive messages from the topic.
   137  	Cancel()
   138  
   139  	// Topic returns the topic that the subscription is subscribed to.
   140  	Topic() string
   141  
   142  	// Next returns the next message from the subscription.
   143  	Next(context.Context) (*pubsub.Message, error)
   144  }
   145  
   146  // BasePubSubAdapterConfig is the base configuration for the underlying pubsub implementation.
   147  // These configurations are common to all pubsub implementations and must be observed by all implementations.
   148  type BasePubSubAdapterConfig struct {
   149  	// MaxMessageSize is the maximum size of a message that can be sent on the pubsub network.
   150  	MaxMessageSize int
   151  }
   152  
   153  // SubscriptionFilter is the abstraction of the underlying pubsub subscription filter that is used by the Flow network.
   154  type SubscriptionFilter interface {
   155  	// CanSubscribe returns true if the current peer can subscribe to the topic.
   156  	CanSubscribe(string) bool
   157  
   158  	// FilterIncomingSubscriptions is invoked for all RPCs containing subscription notifications.
   159  	// It filters and returns the subscriptions of interest to the current node.
   160  	FilterIncomingSubscriptions(peer.ID, []*pb.RPC_SubOpts) ([]*pb.RPC_SubOpts, error)
   161  }
   162  
   163  // PubSubTracer is the abstraction of the underlying pubsub tracer that is used by the Flow network. It wraps the
   164  // pubsub.RawTracer interface with the component.Component interface so that it can be started and stopped.
   165  // The RawTracer interface is used to trace the internal events of the pubsub system.
   166  type PubSubTracer interface {
   167  	component.Component
   168  	pubsub.RawTracer
   169  	RpcControlTracking
   170  	// DuplicateMessageCount returns the current duplicate message count for the peer.
   171  	// Args:
   172  	// - peer.ID: the peer ID.
   173  	// Returns:
   174  	// - float64: duplicate message count.
   175  	DuplicateMessageCount(peer.ID) float64
   176  	// GetLocalMeshPeers returns the list of peers in the mesh for the given topic.
   177  	// Args:
   178  	// - topic: the topic.
   179  	// Returns:
   180  	// - []peer.ID: the list of peers in the mesh for the given topic.
   181  	GetLocalMeshPeers(topic channels.Topic) []peer.ID
   182  }
   183  
   184  // RpcControlTracking is the abstraction of the underlying libp2p control message tracker used to track message ids advertised by the iHave control messages.
   185  // This collection of methods can ensure an iWant control message for a message-id corresponds to a broadcast iHave message id. Implementations
   186  // must be non-blocking and concurrency safe.
   187  type RpcControlTracking interface {
   188  	// LastHighestIHaveRPCSize returns the last highest size of iHaves sent in a rpc.
   189  	LastHighestIHaveRPCSize() int64
   190  	// WasIHaveRPCSent checks if an iHave control message with the provided message ID was sent.
   191  	WasIHaveRPCSent(messageID string) bool
   192  }
   193  
   194  // PeerScoreSnapshot is a snapshot of the overall peer score at a given time.
   195  type PeerScoreSnapshot struct {
   196  	// Score the overall score of the peer.
   197  	Score float64
   198  	// Topics map that stores the score of the peer per topic.
   199  	Topics map[string]*TopicScoreSnapshot
   200  	// AppSpecificScore application specific score (set by Flow protocol).
   201  	AppSpecificScore float64
   202  
   203  	// A positive value indicates that the peer is colocated with other nodes on the same network id,
   204  	// and can be used to warn of sybil attacks.
   205  	IPColocationFactor float64
   206  	// A positive value indicates that GossipSub has caught the peer misbehaving, and can be used to warn of an attack.
   207  	BehaviourPenalty float64
   208  }
   209  
   210  // TopicScoreSnapshot is a snapshot of the peer score within a topic at a given time.
   211  // Note that float64 is used for the counters as they are decayed over the time.
   212  type TopicScoreSnapshot struct {
   213  	// TimeInMesh total time in mesh.
   214  	TimeInMesh time.Duration
   215  	// FirstMessageDeliveries counter of first message deliveries.
   216  	FirstMessageDeliveries float64
   217  	// MeshMessageDeliveries total mesh message deliveries (in the mesh).
   218  	MeshMessageDeliveries float64
   219  	// InvalidMessageDeliveries counter of invalid message deliveries.
   220  	InvalidMessageDeliveries float64
   221  }
   222  
   223  // IsWarning returns true if the peer score is in warning state. When the peer score is in warning state, the peer is
   224  // considered to be misbehaving.
   225  func (p PeerScoreSnapshot) IsWarning() bool {
   226  	// Check if any topic is in warning state.
   227  	for _, topic := range p.Topics {
   228  		if topic.IsWarning() {
   229  			return true
   230  		}
   231  	}
   232  
   233  	// Check overall score.
   234  	switch {
   235  	case p.Score < -1:
   236  		// If the overall score is negative, the peer is in warning state, it means that the peer is suspected to be
   237  		// misbehaving at the GossipSub level.
   238  		return true
   239  	// Check app-specific score.
   240  	case p.AppSpecificScore < -1:
   241  		// If the app specific score is negative, the peer is in warning state, it means that the peer behaves in a way
   242  		// that is not allowed by the Flow protocol.
   243  		return true
   244  	// Check IP colocation factor.
   245  	case p.IPColocationFactor > 5:
   246  		// If the IP colocation factor is positive, the peer is in warning state, it means that the peer is running on the
   247  		// 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
   248  		// that peers from the same operator are not marked as sybil nodes.
   249  		// TODO: this should be revisited once the collocation penalty is enabled.
   250  		return true
   251  	// Check behaviour penalty.
   252  	case p.BehaviourPenalty > 20:
   253  		// If the behaviour penalty is positive, the peer is in warning state, it means that the peer is suspected to be
   254  		// 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).
   255  		return true
   256  	// If none of the conditions are met, return false.
   257  	default:
   258  		return false
   259  	}
   260  }
   261  
   262  // String returns the string representation of the peer score snapshot.
   263  func (s TopicScoreSnapshot) String() string {
   264  	return fmt.Sprintf("time_in_mesh: %s, first_message_deliveries: %f, mesh message deliveries: %f, invalid message deliveries: %f",
   265  		s.TimeInMesh, s.FirstMessageDeliveries, s.MeshMessageDeliveries, s.InvalidMessageDeliveries)
   266  }
   267  
   268  // IsWarning returns true if the topic score is in warning state.
   269  func (s TopicScoreSnapshot) IsWarning() bool {
   270  	// TODO: also check for first message deliveries and time in mesh when we have a better understanding of the score.
   271  	// If invalid message deliveries is positive, the topic is in warning state. It means that the peer is suspected to
   272  	// be misbehaving at the GossipSub level, e.g. sending too many invalid messages to the topic.
   273  	return s.InvalidMessageDeliveries > 0
   274  }
   275  
   276  // PeerScoreTracer is the interface for the tracer that is used to trace the peer score.
   277  type PeerScoreTracer interface {
   278  	component.Component
   279  	PeerScoreExposer
   280  	// UpdatePeerScoreSnapshots updates the peer score snapshot/
   281  	UpdatePeerScoreSnapshots(map[peer.ID]*PeerScoreSnapshot)
   282  
   283  	// UpdateInterval returns the update interval for the tracer. The tracer will be receiving updates
   284  	// at this interval.
   285  	UpdateInterval() time.Duration
   286  }
   287  
   288  // PeerScoreExposer is the interface for the tracer that is used to expose the peers score.
   289  type PeerScoreExposer interface {
   290  	// GetScore returns the overall score for the given peer.
   291  	GetScore(peerID peer.ID) (float64, bool)
   292  	// GetAppScore returns the application score for the given peer.
   293  	GetAppScore(peerID peer.ID) (float64, bool)
   294  	// GetIPColocationFactor returns the IP colocation factor for the given peer.
   295  	GetIPColocationFactor(peerID peer.ID) (float64, bool)
   296  	// GetBehaviourPenalty returns the behaviour penalty for the given peer.
   297  	GetBehaviourPenalty(peerID peer.ID) (float64, bool)
   298  	// GetTopicScores returns the topic scores for the given peer for all topics.
   299  	// The returned map is keyed by topic name.
   300  	GetTopicScores(peerID peer.ID) (map[string]TopicScoreSnapshot, bool)
   301  }