github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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 appScoreRegistry *GossipSubAppSpecificScoreRegistry 37 } 38 39 type ScoreOptionConfig struct { 40 logger zerolog.Logger 41 params p2pconfig.ScoringParameters 42 provider module.IdentityProvider 43 heroCacheMetricsFactory metrics.HeroCacheMetricsFactory 44 appScoreFunc func(peer.ID) float64 45 topicParams []func(map[string]*pubsub.TopicScoreParams) 46 getDuplicateMessageCount func(id peer.ID) float64 47 scoringRegistryMetricsCollector module.GossipSubScoringRegistryMetrics 48 networkingType network.NetworkingType 49 } 50 51 // NewScoreOptionConfig creates a new configuration for the GossipSub peer scoring option. 52 // Args: 53 // - logger: the logger to use. 54 // - hcMetricsFactory: HeroCache metrics factory to create metrics for the scoring-related caches. 55 // - idProvider: the identity provider to use. 56 // - networkingType: the networking type to use, public or private. 57 // Returns: 58 // - a new configuration for the GossipSub peer scoring option. 59 func NewScoreOptionConfig(logger zerolog.Logger, 60 params p2pconfig.ScoringParameters, 61 hcMetricsFactory metrics.HeroCacheMetricsFactory, 62 scoringRegistryMetricsCollector module.GossipSubScoringRegistryMetrics, 63 idProvider module.IdentityProvider, 64 getDuplicateMessageCount func(id peer.ID) float64, 65 networkingType network.NetworkingType) *ScoreOptionConfig { 66 return &ScoreOptionConfig{ 67 logger: logger.With().Str("module", "pubsub_score_option").Logger(), 68 provider: idProvider, 69 params: params, 70 heroCacheMetricsFactory: hcMetricsFactory, 71 topicParams: make([]func(map[string]*pubsub.TopicScoreParams), 0), 72 networkingType: networkingType, 73 getDuplicateMessageCount: getDuplicateMessageCount, 74 scoringRegistryMetricsCollector: scoringRegistryMetricsCollector, 75 } 76 } 77 78 // OverrideAppSpecificScoreFunction sets the app specific penalty function for the penalty option. 79 // It is used to calculate the app specific penalty of a peer. 80 // If the app specific penalty function is not set, the default one is used. 81 // Note that it is always safer to use the default one, unless you know what you are doing. 82 // It is safe to call this method multiple times, the last call will be used. 83 func (c *ScoreOptionConfig) OverrideAppSpecificScoreFunction(appSpecificScoreFunction func(peer.ID) float64) { 84 c.appScoreFunc = appSpecificScoreFunction 85 } 86 87 // OverrideTopicScoreParams overrides the topic score parameters for the given topic. 88 // It is used to override the default topic score parameters for a specific topic. 89 // If the topic score parameters are not set, the default ones will be used. 90 func (c *ScoreOptionConfig) OverrideTopicScoreParams(topic channels.Topic, topicScoreParams *pubsub.TopicScoreParams) { 91 c.topicParams = append(c.topicParams, func(topics map[string]*pubsub.TopicScoreParams) { 92 topics[topic.String()] = topicScoreParams 93 }) 94 } 95 96 // NewScoreOption creates a new penalty option with the given configuration. 97 func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) (*ScoreOption, error) { 98 throttledSampler := logging.BurstSampler(cfg.params.PeerScoring.Protocol.MaxDebugLogs, time.Second) 99 logger := cfg.logger.With(). 100 Str("module", "pubsub_score_option"). 101 Logger(). 102 Sample(zerolog.LevelSampler{ 103 TraceSampler: throttledSampler, 104 DebugSampler: throttledSampler, 105 }) 106 107 validator := NewSubscriptionValidator(cfg.logger, provider) 108 scoreRegistry, err := NewGossipSubAppSpecificScoreRegistry(&GossipSubAppSpecificScoreRegistryConfig{ 109 Logger: logger, 110 Penalty: cfg.params.ScoringRegistryParameters.MisbehaviourPenalties, 111 Validator: validator, 112 IdProvider: cfg.provider, 113 HeroCacheMetricsFactory: cfg.heroCacheMetricsFactory, 114 AppScoreCacheFactory: func() p2p.GossipSubApplicationSpecificScoreCache { 115 collector := metrics.NewGossipSubApplicationSpecificScoreCacheMetrics(cfg.heroCacheMetricsFactory, cfg.networkingType) 116 return internal.NewAppSpecificScoreCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector) 117 }, 118 SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { 119 collector := metrics.GossipSubSpamRecordCacheMetricsFactory(cfg.heroCacheMetricsFactory, cfg.networkingType) 120 return netcache.NewGossipSubSpamRecordCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector, 121 InitAppScoreRecordStateFunc(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), 122 DefaultDecayFunction(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay)) 123 }, 124 GetDuplicateMessageCount: func(id peer.ID) float64 { 125 return cfg.getDuplicateMessageCount(id) 126 }, 127 Parameters: cfg.params.ScoringRegistryParameters.AppSpecificScore, 128 NetworkingType: cfg.networkingType, 129 AppSpecificScoreParams: cfg.params.PeerScoring.Protocol.AppSpecificScore, 130 DuplicateMessageThreshold: cfg.params.PeerScoring.Protocol.AppSpecificScore.DuplicateMessageThreshold, 131 Collector: cfg.scoringRegistryMetricsCollector, 132 }) 133 if err != nil { 134 return nil, fmt.Errorf("failed to create gossipsub app specific score registry: %w", err) 135 } 136 137 s := &ScoreOption{ 138 logger: logger, 139 validator: validator, 140 peerScoreParams: &pubsub.PeerScoreParams{ 141 Topics: make(map[string]*pubsub.TopicScoreParams), 142 // we don't set all the parameters, so we skip the atomic validation. 143 // atomic validation fails initialization if any parameter is not set. 144 SkipAtomicValidation: cfg.params.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, 145 // DecayInterval is the interval over which we decay the effect of past behavior, so that 146 // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval 147 // that GossipSub uses to refresh the scores of all peers. 148 DecayInterval: cfg.params.PeerScoring.Internal.DecayInterval, 149 // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. 150 // This is to prevent the counter from decaying to a very small value. 151 // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior 152 // for a long time, and we can reset the counter. 153 DecayToZero: cfg.params.PeerScoring.Internal.DecayToZero, 154 // AppSpecificWeight is the weight of the application specific penalty. 155 AppSpecificWeight: cfg.params.PeerScoring.Internal.AppSpecificScoreWeight, 156 // PenaltyThreshold is the threshold above which a peer is penalized for GossipSub-level misbehaviors. 157 BehaviourPenaltyThreshold: cfg.params.PeerScoring.Internal.Behaviour.PenaltyThreshold, 158 // PenaltyWeight is the weight of the GossipSub-level penalty. 159 BehaviourPenaltyWeight: cfg.params.PeerScoring.Internal.Behaviour.PenaltyWeight, 160 // PenaltyDecay is the decay of the GossipSub-level penalty (applied every decay interval). 161 BehaviourPenaltyDecay: cfg.params.PeerScoring.Internal.Behaviour.PenaltyDecay, 162 }, 163 peerThresholdParams: &pubsub.PeerScoreThresholds{ 164 GossipThreshold: cfg.params.PeerScoring.Internal.Thresholds.Gossip, 165 PublishThreshold: cfg.params.PeerScoring.Internal.Thresholds.Publish, 166 GraylistThreshold: cfg.params.PeerScoring.Internal.Thresholds.Graylist, 167 AcceptPXThreshold: cfg.params.PeerScoring.Internal.Thresholds.AcceptPX, 168 OpportunisticGraftThreshold: cfg.params.PeerScoring.Internal.Thresholds.OpportunisticGraft, 169 }, 170 defaultTopicScoreParams: &pubsub.TopicScoreParams{ 171 TopicWeight: cfg.params.PeerScoring.Internal.TopicParameters.TopicWeight, 172 SkipAtomicValidation: cfg.params.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, 173 InvalidMessageDeliveriesWeight: cfg.params.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesWeight, 174 InvalidMessageDeliveriesDecay: cfg.params.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesDecay, 175 TimeInMeshQuantum: cfg.params.PeerScoring.Internal.TopicParameters.TimeInMeshQuantum, 176 MeshMessageDeliveriesWeight: cfg.params.PeerScoring.Internal.TopicParameters.MeshDeliveriesWeight, 177 MeshMessageDeliveriesDecay: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesDecay, 178 MeshMessageDeliveriesCap: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesCap, 179 MeshMessageDeliveriesThreshold: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryThreshold, 180 MeshMessageDeliveriesWindow: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesWindow, 181 MeshMessageDeliveriesActivation: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryActivation, 182 }, 183 appScoreFunc: scoreRegistry.AppSpecificScoreFunc(), 184 appScoreRegistry: scoreRegistry, 185 } 186 187 // set the app specific penalty function for the penalty option 188 // if the app specific penalty function is not set, use the default one 189 if cfg.appScoreFunc != nil { 190 s.appScoreFunc = cfg.appScoreFunc 191 s.logger. 192 Warn(). 193 Str(logging.KeyNetworkingSecurity, "true"). 194 Msg("app specific score function is overridden, should never happen in production") 195 } 196 197 if cfg.params.PeerScoring.Internal.DecayInterval > 0 && cfg.params.PeerScoring.Internal.DecayInterval != s.peerScoreParams.DecayInterval { 198 // overrides the default decay interval if the decay interval is set. 199 s.peerScoreParams.DecayInterval = cfg.params.PeerScoring.Internal.DecayInterval 200 s.logger. 201 Warn(). 202 Str(logging.KeyNetworkingSecurity, "true"). 203 Dur("decay_interval_ms", cfg.params.PeerScoring.Internal.DecayInterval). 204 Msg("decay interval is overridden, should never happen in production") 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 } 268 269 // OnInvalidControlMessageNotification is called when a new invalid control message notification is distributed. 270 // Any error on consuming event must handle internally. 271 // The implementation must be concurrency safe and non-blocking. 272 // Note: there is no real-time guarantee on processing the notification. 273 func (s *ScoreOption) OnInvalidControlMessageNotification(notif *p2p.InvCtrlMsgNotif) { 274 s.appScoreRegistry.OnInvalidControlMessageNotification(notif) 275 }