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 }