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 }