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 }