github.com/onflow/flow-go@v0.33.17/network/p2p/scoring/score_option.go (about) 1 package scoring 2 3 import ( 4 "fmt" 5 "time" 6 7 pubsub "github.com/libp2p/go-libp2p-pubsub" 8 "github.com/libp2p/go-libp2p/core/peer" 9 "github.com/rs/zerolog" 10 11 "github.com/onflow/flow-go/module" 12 "github.com/onflow/flow-go/module/component" 13 "github.com/onflow/flow-go/module/irrecoverable" 14 "github.com/onflow/flow-go/module/metrics" 15 "github.com/onflow/flow-go/network" 16 "github.com/onflow/flow-go/network/channels" 17 "github.com/onflow/flow-go/network/p2p" 18 netcache "github.com/onflow/flow-go/network/p2p/cache" 19 p2pconfig "github.com/onflow/flow-go/network/p2p/config" 20 "github.com/onflow/flow-go/network/p2p/scoring/internal" 21 "github.com/onflow/flow-go/network/p2p/utils" 22 "github.com/onflow/flow-go/utils/logging" 23 ) 24 25 // ScoreOption is a functional option for configuring the peer scoring system. 26 // TODO: rename it to ScoreManager. 27 type ScoreOption struct { 28 component.Component 29 logger zerolog.Logger 30 31 peerScoreParams *pubsub.PeerScoreParams 32 peerThresholdParams *pubsub.PeerScoreThresholds 33 defaultTopicScoreParams *pubsub.TopicScoreParams 34 validator p2p.SubscriptionValidator 35 appScoreFunc func(peer.ID) float64 36 } 37 38 type ScoreOptionConfig struct { 39 logger zerolog.Logger 40 params p2pconfig.ScoringParameters 41 provider module.IdentityProvider 42 heroCacheMetricsFactory metrics.HeroCacheMetricsFactory 43 appScoreFunc func(peer.ID) float64 44 topicParams []func(map[string]*pubsub.TopicScoreParams) 45 registerNotificationConsumerFunc func(p2p.GossipSubInvCtrlMsgNotifConsumer) 46 networkingType network.NetworkingType 47 } 48 49 // NewScoreOptionConfig creates a new configuration for the GossipSub peer scoring option. 50 // Args: 51 // - logger: the logger to use. 52 // - hcMetricsFactory: HeroCache metrics factory to create metrics for the scoring-related caches. 53 // - idProvider: the identity provider to use. 54 // - networkingType: the networking type to use, public or private. 55 // Returns: 56 // - a new configuration for the GossipSub peer scoring option. 57 func NewScoreOptionConfig(logger zerolog.Logger, 58 params p2pconfig.ScoringParameters, 59 hcMetricsFactory metrics.HeroCacheMetricsFactory, 60 idProvider module.IdentityProvider, 61 networkingType network.NetworkingType) *ScoreOptionConfig { 62 return &ScoreOptionConfig{ 63 logger: logger.With().Str("module", "pubsub_score_option").Logger(), 64 provider: idProvider, 65 params: params, 66 heroCacheMetricsFactory: hcMetricsFactory, 67 topicParams: make([]func(map[string]*pubsub.TopicScoreParams), 0), 68 networkingType: networkingType, 69 } 70 } 71 72 // OverrideAppSpecificScoreFunction sets the app specific penalty function for the penalty option. 73 // It is used to calculate the app specific penalty of a peer. 74 // If the app specific penalty function is not set, the default one is used. 75 // Note that it is always safer to use the default one, unless you know what you are doing. 76 // It is safe to call this method multiple times, the last call will be used. 77 func (c *ScoreOptionConfig) OverrideAppSpecificScoreFunction(appSpecificScoreFunction func(peer.ID) float64) { 78 c.appScoreFunc = appSpecificScoreFunction 79 } 80 81 // OverrideTopicScoreParams overrides the topic score parameters for the given topic. 82 // It is used to override the default topic score parameters for a specific topic. 83 // If the topic score parameters are not set, the default ones will be used. 84 func (c *ScoreOptionConfig) OverrideTopicScoreParams(topic channels.Topic, topicScoreParams *pubsub.TopicScoreParams) { 85 c.topicParams = append(c.topicParams, func(topics map[string]*pubsub.TopicScoreParams) { 86 topics[topic.String()] = topicScoreParams 87 }) 88 } 89 90 // SetRegisterNotificationConsumerFunc sets the function to register the notification consumer for the penalty option. 91 // ScoreOption uses this function to register the notification consumer for the pubsub system so that it can receive 92 // notifications of invalid control messages. 93 func (c *ScoreOptionConfig) SetRegisterNotificationConsumerFunc(f func(p2p.GossipSubInvCtrlMsgNotifConsumer)) { 94 c.registerNotificationConsumerFunc = f 95 } 96 97 // NewScoreOption creates a new penalty option with the given configuration. 98 func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) (*ScoreOption, error) { 99 throttledSampler := logging.BurstSampler(cfg.params.PeerScoring.Protocol.MaxDebugLogs, time.Second) 100 logger := cfg.logger.With(). 101 Str("module", "pubsub_score_option"). 102 Logger(). 103 Sample(zerolog.LevelSampler{ 104 TraceSampler: throttledSampler, 105 DebugSampler: throttledSampler, 106 }) 107 108 validator := NewSubscriptionValidator(cfg.logger, provider) 109 scoreRegistry, err := NewGossipSubAppSpecificScoreRegistry(&GossipSubAppSpecificScoreRegistryConfig{ 110 Logger: logger, 111 Penalty: cfg.params.ScoringRegistryParameters.MisbehaviourPenalties, 112 Validator: validator, 113 IdProvider: cfg.provider, 114 HeroCacheMetricsFactory: cfg.heroCacheMetricsFactory, 115 AppScoreCacheFactory: func() p2p.GossipSubApplicationSpecificScoreCache { 116 collector := metrics.NewGossipSubApplicationSpecificScoreCacheMetrics(cfg.heroCacheMetricsFactory, cfg.networkingType) 117 return internal.NewAppSpecificScoreCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector) 118 }, 119 SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { 120 collector := metrics.GossipSubSpamRecordCacheMetricsFactory(cfg.heroCacheMetricsFactory, cfg.networkingType) 121 return netcache.NewGossipSubSpamRecordCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector, 122 InitAppScoreRecordStateFunc(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), 123 DefaultDecayFunction(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay)) 124 }, 125 Parameters: cfg.params.ScoringRegistryParameters.AppSpecificScore, 126 NetworkingType: cfg.networkingType, 127 AppSpecificScoreParams: cfg.params.PeerScoring.Protocol.AppSpecificScore, 128 }) 129 if err != nil { 130 return nil, fmt.Errorf("failed to create gossipsub app specific score registry: %w", err) 131 } 132 133 s := &ScoreOption{ 134 logger: logger, 135 validator: validator, 136 peerScoreParams: &pubsub.PeerScoreParams{ 137 Topics: make(map[string]*pubsub.TopicScoreParams), 138 // we don't set all the parameters, so we skip the atomic validation. 139 // atomic validation fails initialization if any parameter is not set. 140 SkipAtomicValidation: cfg.params.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, 141 // DecayInterval is the interval over which we decay the effect of past behavior, so that 142 // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval 143 // that GossipSub uses to refresh the scores of all peers. 144 DecayInterval: cfg.params.PeerScoring.Internal.DecayInterval, 145 // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. 146 // This is to prevent the counter from decaying to a very small value. 147 // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior 148 // for a long time, and we can reset the counter. 149 DecayToZero: cfg.params.PeerScoring.Internal.DecayToZero, 150 // AppSpecificWeight is the weight of the application specific penalty. 151 AppSpecificWeight: cfg.params.PeerScoring.Internal.AppSpecificScoreWeight, 152 // PenaltyThreshold is the threshold above which a peer is penalized for GossipSub-level misbehaviors. 153 BehaviourPenaltyThreshold: cfg.params.PeerScoring.Internal.Behaviour.PenaltyThreshold, 154 // PenaltyWeight is the weight of the GossipSub-level penalty. 155 BehaviourPenaltyWeight: cfg.params.PeerScoring.Internal.Behaviour.PenaltyWeight, 156 // PenaltyDecay is the decay of the GossipSub-level penalty (applied every decay interval). 157 BehaviourPenaltyDecay: cfg.params.PeerScoring.Internal.Behaviour.PenaltyDecay, 158 }, 159 peerThresholdParams: &pubsub.PeerScoreThresholds{ 160 GossipThreshold: cfg.params.PeerScoring.Internal.Thresholds.Gossip, 161 PublishThreshold: cfg.params.PeerScoring.Internal.Thresholds.Publish, 162 GraylistThreshold: cfg.params.PeerScoring.Internal.Thresholds.Graylist, 163 AcceptPXThreshold: cfg.params.PeerScoring.Internal.Thresholds.AcceptPX, 164 OpportunisticGraftThreshold: cfg.params.PeerScoring.Internal.Thresholds.OpportunisticGraft, 165 }, 166 defaultTopicScoreParams: &pubsub.TopicScoreParams{ 167 TopicWeight: cfg.params.PeerScoring.Internal.TopicParameters.TopicWeight, 168 SkipAtomicValidation: cfg.params.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, 169 InvalidMessageDeliveriesWeight: cfg.params.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesWeight, 170 InvalidMessageDeliveriesDecay: cfg.params.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesDecay, 171 TimeInMeshQuantum: cfg.params.PeerScoring.Internal.TopicParameters.TimeInMeshQuantum, 172 MeshMessageDeliveriesWeight: cfg.params.PeerScoring.Internal.TopicParameters.MeshDeliveriesWeight, 173 MeshMessageDeliveriesDecay: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesDecay, 174 MeshMessageDeliveriesCap: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesCap, 175 MeshMessageDeliveriesThreshold: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryThreshold, 176 MeshMessageDeliveriesWindow: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesWindow, 177 MeshMessageDeliveriesActivation: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryActivation, 178 }, 179 appScoreFunc: scoreRegistry.AppSpecificScoreFunc(), 180 } 181 182 // set the app specific penalty function for the penalty option 183 // if the app specific penalty function is not set, use the default one 184 if cfg.appScoreFunc != nil { 185 s.appScoreFunc = cfg.appScoreFunc 186 s.logger. 187 Warn(). 188 Str(logging.KeyNetworkingSecurity, "true"). 189 Msg("app specific score function is overridden, should never happen in production") 190 } 191 192 if cfg.params.PeerScoring.Internal.DecayInterval > 0 && cfg.params.PeerScoring.Internal.DecayInterval != s.peerScoreParams.DecayInterval { 193 // overrides the default decay interval if the decay interval is set. 194 s.peerScoreParams.DecayInterval = cfg.params.PeerScoring.Internal.DecayInterval 195 s.logger. 196 Warn(). 197 Str(logging.KeyNetworkingSecurity, "true"). 198 Dur("decay_interval_ms", cfg.params.PeerScoring.Internal.DecayInterval). 199 Msg("decay interval is overridden, should never happen in production") 200 } 201 202 // registers the score registry as the consumer of the invalid control message notifications 203 if cfg.registerNotificationConsumerFunc != nil { 204 cfg.registerNotificationConsumerFunc(scoreRegistry) 205 } 206 207 s.peerScoreParams.AppSpecificScore = s.appScoreFunc 208 209 // apply the topic penalty parameters if any. 210 for _, topicParams := range cfg.topicParams { 211 topicParams(s.peerScoreParams.Topics) 212 } 213 214 s.Component = component.NewComponentManagerBuilder().AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) { 215 s.logger.Info().Msg("starting score registry") 216 scoreRegistry.Start(ctx) 217 select { 218 case <-ctx.Done(): 219 s.logger.Warn().Msg("stopping score registry; context done") 220 case <-scoreRegistry.Ready(): 221 s.logger.Info().Msg("score registry started") 222 ready() 223 s.logger.Info().Msg("score registry ready") 224 } 225 226 <-ctx.Done() 227 s.logger.Info().Msg("stopping score registry") 228 <-scoreRegistry.Done() 229 s.logger.Info().Msg("score registry stopped") 230 }).Build() 231 232 return s, nil 233 } 234 235 func (s *ScoreOption) BuildFlowPubSubScoreOption() (*pubsub.PeerScoreParams, *pubsub.PeerScoreThresholds) { 236 s.logger.Info(). 237 Float64("gossip_threshold", s.peerThresholdParams.GossipThreshold). 238 Float64("publish_threshold", s.peerThresholdParams.PublishThreshold). 239 Float64("graylist_threshold", s.peerThresholdParams.GraylistThreshold). 240 Float64("accept_px_threshold", s.peerThresholdParams.AcceptPXThreshold). 241 Float64("opportunistic_graft_threshold", s.peerThresholdParams.OpportunisticGraftThreshold). 242 Msg("pubsub score thresholds are set") 243 244 for topic, topicParams := range s.peerScoreParams.Topics { 245 topicScoreParamLogger := utils.TopicScoreParamsLogger(s.logger, topic, topicParams) 246 topicScoreParamLogger.Info(). 247 Msg("pubsub score topic parameters are set for topic") 248 } 249 250 return s.peerScoreParams, s.peerThresholdParams 251 } 252 253 // TopicScoreParams returns the topic score parameters for the given topic. If the topic 254 // score parameters are not set, it returns the default topic score parameters. 255 // The custom topic parameters are set at the initialization of the score option. 256 // Args: 257 // - topic: the topic for which the score parameters are requested. 258 // Returns: 259 // - the topic score parameters for the given topic, or the default topic score parameters if 260 // the topic score parameters are not set. 261 func (s *ScoreOption) TopicScoreParams(topic *pubsub.Topic) *pubsub.TopicScoreParams { 262 params, exists := s.peerScoreParams.Topics[topic.String()] 263 if !exists { 264 return s.defaultTopicScoreParams 265 } 266 return params 267 }