github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/p2p/gossip_scoring_params.go (about) 1 package p2p 2 3 import ( 4 "math" 5 "reflect" 6 "strings" 7 "time" 8 9 "github.com/libp2p/go-libp2p-core/peer" 10 pubsub "github.com/libp2p/go-libp2p-pubsub" 11 "github.com/pkg/errors" 12 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 13 "github.com/prysmaticlabs/prysm/shared/params" 14 "github.com/sirupsen/logrus" 15 ) 16 17 const ( 18 // beaconBlockWeight specifies the scoring weight that we apply to 19 // our beacon block topic. 20 beaconBlockWeight = 0.8 21 // aggregateWeight specifies the scoring weight that we apply to 22 // our aggregate topic. 23 aggregateWeight = 0.5 24 // attestationTotalWeight specifies the scoring weight that we apply to 25 // our attestation subnet topic. 26 attestationTotalWeight = 1 27 // attesterSlashingWeight specifies the scoring weight that we apply to 28 // our attester slashing topic. 29 attesterSlashingWeight = 0.05 30 // proposerSlashingWeight specifies the scoring weight that we apply to 31 // our proposer slashing topic. 32 proposerSlashingWeight = 0.05 33 // voluntaryExitWeight specifies the scoring weight that we apply to 34 // our voluntary exit topic. 35 voluntaryExitWeight = 0.05 36 37 // maxInMeshScore describes the max score a peer can attain from being in the mesh. 38 maxInMeshScore = 10 39 // maxFirstDeliveryScore describes the max score a peer can obtain from first deliveries. 40 maxFirstDeliveryScore = 40 41 42 // decayToZero specifies the terminal value that we will use when decaying 43 // a value. 44 decayToZero = 0.01 45 46 // dampeningFactor reduces the amount by which the various thresholds and caps are created. 47 dampeningFactor = 90 48 ) 49 50 var ( 51 // a bool to check if we enable scoring for messages in the mesh sent for near first deliveries. 52 meshDeliveryIsScored = false 53 ) 54 55 func peerScoringParams() (*pubsub.PeerScoreParams, *pubsub.PeerScoreThresholds) { 56 thresholds := &pubsub.PeerScoreThresholds{ 57 GossipThreshold: -4000, 58 PublishThreshold: -8000, 59 GraylistThreshold: -16000, 60 AcceptPXThreshold: 100, 61 OpportunisticGraftThreshold: 5, 62 } 63 scoreParams := &pubsub.PeerScoreParams{ 64 Topics: make(map[string]*pubsub.TopicScoreParams), 65 TopicScoreCap: 32.72, 66 AppSpecificScore: func(p peer.ID) float64 { 67 return 0 68 }, 69 AppSpecificWeight: 1, 70 IPColocationFactorWeight: -35.11, 71 IPColocationFactorThreshold: 10, 72 IPColocationFactorWhitelist: nil, 73 BehaviourPenaltyWeight: -15.92, 74 BehaviourPenaltyThreshold: 6, 75 BehaviourPenaltyDecay: scoreDecay(10 * oneEpochDuration()), 76 DecayInterval: 1 * oneSlotDuration(), 77 DecayToZero: decayToZero, 78 RetainScore: 100 * oneEpochDuration(), 79 } 80 return scoreParams, thresholds 81 } 82 83 func (s *Service) topicScoreParams(topic string) (*pubsub.TopicScoreParams, error) { 84 activeValidators, err := s.retrieveActiveValidators() 85 if err != nil { 86 return nil, err 87 } 88 switch { 89 case strings.Contains(topic, "beacon_block"): 90 return defaultBlockTopicParams(), nil 91 case strings.Contains(topic, "beacon_aggregate_and_proof"): 92 return defaultAggregateTopicParams(activeValidators) 93 case strings.Contains(topic, "beacon_attestation"): 94 return defaultAggregateSubnetTopicParams(activeValidators) 95 case strings.Contains(topic, "voluntary_exit"): 96 return defaultVoluntaryExitTopicParams(), nil 97 case strings.Contains(topic, "proposer_slashing"): 98 return defaultProposerSlashingTopicParams(), nil 99 case strings.Contains(topic, "attester_slashing"): 100 return defaultAttesterSlashingTopicParams(), nil 101 default: 102 return nil, errors.Errorf("unrecognized topic provided for parameter registration: %s", topic) 103 } 104 } 105 106 func (s *Service) retrieveActiveValidators() (uint64, error) { 107 if s.activeValidatorCount != 0 { 108 return s.activeValidatorCount, nil 109 } 110 rt := s.cfg.DB.LastArchivedRoot(s.ctx) 111 if rt == params.BeaconConfig().ZeroHash { 112 genState, err := s.cfg.DB.GenesisState(s.ctx) 113 if err != nil { 114 return 0, err 115 } 116 if genState == nil || genState.IsNil() { 117 return 0, errors.New("no genesis state exists") 118 } 119 activeVals, err := helpers.ActiveValidatorCount(genState, helpers.CurrentEpoch(genState)) 120 if err != nil { 121 return 0, err 122 } 123 // Cache active validator count 124 s.activeValidatorCount = activeVals 125 return activeVals, nil 126 } 127 bState, err := s.cfg.DB.State(s.ctx, rt) 128 if err != nil { 129 return 0, err 130 } 131 if bState == nil || bState.IsNil() { 132 return 0, errors.Errorf("no state with root %#x exists", rt) 133 } 134 activeVals, err := helpers.ActiveValidatorCount(bState, helpers.CurrentEpoch(bState)) 135 if err != nil { 136 return 0, err 137 } 138 // Cache active validator count 139 s.activeValidatorCount = activeVals 140 return activeVals, nil 141 } 142 143 // Based on the lighthouse parameters. 144 // https://gist.github.com/blacktemplar/5c1862cb3f0e32a1a7fb0b25e79e6e2c 145 146 func defaultBlockTopicParams() *pubsub.TopicScoreParams { 147 decayEpoch := time.Duration(5) 148 blocksPerEpoch := uint64(params.BeaconConfig().SlotsPerEpoch) 149 meshWeight := -0.717 150 if !meshDeliveryIsScored { 151 // Set the mesh weight as zero as a temporary measure, so as to prevent 152 // the average nodes from being penalised. 153 meshWeight = 0 154 } 155 return &pubsub.TopicScoreParams{ 156 TopicWeight: beaconBlockWeight, 157 TimeInMeshWeight: maxInMeshScore / inMeshCap(), 158 TimeInMeshQuantum: inMeshTime(), 159 TimeInMeshCap: inMeshCap(), 160 FirstMessageDeliveriesWeight: 1, 161 FirstMessageDeliveriesDecay: scoreDecay(20 * oneEpochDuration()), 162 FirstMessageDeliveriesCap: 23, 163 MeshMessageDeliveriesWeight: meshWeight, 164 MeshMessageDeliveriesDecay: scoreDecay(decayEpoch * oneEpochDuration()), 165 MeshMessageDeliveriesCap: float64(blocksPerEpoch * uint64(decayEpoch)), 166 MeshMessageDeliveriesThreshold: float64(blocksPerEpoch*uint64(decayEpoch)) / 10, 167 MeshMessageDeliveriesWindow: 2 * time.Second, 168 MeshMessageDeliveriesActivation: 4 * oneEpochDuration(), 169 MeshFailurePenaltyWeight: meshWeight, 170 MeshFailurePenaltyDecay: scoreDecay(decayEpoch * oneEpochDuration()), 171 InvalidMessageDeliveriesWeight: -140.4475, 172 InvalidMessageDeliveriesDecay: scoreDecay(50 * oneEpochDuration()), 173 } 174 } 175 176 func defaultAggregateTopicParams(activeValidators uint64) (*pubsub.TopicScoreParams, error) { 177 // Determine the expected message rate for the particular gossip topic. 178 aggPerSlot := aggregatorsPerSlot(activeValidators) 179 firstMessageCap, err := decayLimit(scoreDecay(1*oneEpochDuration()), float64(aggPerSlot*2/gossipSubD)) 180 if err != nil { 181 log.Warnf("skipping initializing topic scoring: %v", err) 182 return nil, nil 183 } 184 firstMessageWeight := maxFirstDeliveryScore / firstMessageCap 185 meshThreshold, err := decayThreshold(scoreDecay(1*oneEpochDuration()), float64(aggPerSlot)/dampeningFactor) 186 if err != nil { 187 log.Warnf("skipping initializing topic scoring: %v", err) 188 return nil, nil 189 } 190 meshWeight := -scoreByWeight(aggregateWeight, meshThreshold) 191 meshCap := 4 * meshThreshold 192 if !meshDeliveryIsScored { 193 // Set the mesh weight as zero as a temporary measure, so as to prevent 194 // the average nodes from being penalised. 195 meshWeight = 0 196 } 197 return &pubsub.TopicScoreParams{ 198 TopicWeight: aggregateWeight, 199 TimeInMeshWeight: maxInMeshScore / inMeshCap(), 200 TimeInMeshQuantum: inMeshTime(), 201 TimeInMeshCap: inMeshCap(), 202 FirstMessageDeliveriesWeight: firstMessageWeight, 203 FirstMessageDeliveriesDecay: scoreDecay(1 * oneEpochDuration()), 204 FirstMessageDeliveriesCap: firstMessageCap, 205 MeshMessageDeliveriesWeight: meshWeight, 206 MeshMessageDeliveriesDecay: scoreDecay(1 * oneEpochDuration()), 207 MeshMessageDeliveriesCap: meshCap, 208 MeshMessageDeliveriesThreshold: meshThreshold, 209 MeshMessageDeliveriesWindow: 2 * time.Second, 210 MeshMessageDeliveriesActivation: 1 * oneEpochDuration(), 211 MeshFailurePenaltyWeight: meshWeight, 212 MeshFailurePenaltyDecay: scoreDecay(1 * oneEpochDuration()), 213 InvalidMessageDeliveriesWeight: -maxScore() / aggregateWeight, 214 InvalidMessageDeliveriesDecay: scoreDecay(50 * oneEpochDuration()), 215 }, nil 216 } 217 218 func defaultAggregateSubnetTopicParams(activeValidators uint64) (*pubsub.TopicScoreParams, error) { 219 subnetCount := params.BeaconNetworkConfig().AttestationSubnetCount 220 // Get weight for each specific subnet. 221 topicWeight := attestationTotalWeight / float64(subnetCount) 222 subnetWeight := activeValidators / subnetCount 223 if subnetWeight == 0 { 224 log.Warn("Subnet weight is 0, skipping initializing topic scoring") 225 return nil, nil 226 } 227 // Determine the amount of validators expected in a subnet in a single slot. 228 numPerSlot := time.Duration(subnetWeight / uint64(params.BeaconConfig().SlotsPerEpoch)) 229 if numPerSlot == 0 { 230 log.Warn("numPerSlot is 0, skipping initializing topic scoring") 231 return nil, nil 232 } 233 comsPerSlot := committeeCountPerSlot(activeValidators) 234 exceedsThreshold := comsPerSlot >= 2*subnetCount/uint64(params.BeaconConfig().SlotsPerEpoch) 235 firstDecay := time.Duration(1) 236 meshDecay := time.Duration(4) 237 if exceedsThreshold { 238 firstDecay = 4 239 meshDecay = 16 240 } 241 // Determine expected first deliveries based on the message rate. 242 firstMessageCap, err := decayLimit(scoreDecay(firstDecay*oneEpochDuration()), float64(numPerSlot*2/gossipSubD)) 243 if err != nil { 244 log.Warnf("skipping initializing topic scoring: %v", err) 245 return nil, nil 246 } 247 firstMessageWeight := maxFirstDeliveryScore / firstMessageCap 248 // Determine expected mesh deliveries based on message rate applied with a dampening factor. 249 meshThreshold, err := decayThreshold(scoreDecay(meshDecay*oneEpochDuration()), float64(numPerSlot)/dampeningFactor) 250 if err != nil { 251 log.Warnf("skipping initializing topic scoring: %v", err) 252 return nil, nil 253 } 254 meshWeight := -scoreByWeight(topicWeight, meshThreshold) 255 meshCap := 4 * meshThreshold 256 if !meshDeliveryIsScored { 257 // Set the mesh weight as zero as a temporary measure, so as to prevent 258 // the average nodes from being penalised. 259 meshWeight = 0 260 } 261 return &pubsub.TopicScoreParams{ 262 TopicWeight: topicWeight, 263 TimeInMeshWeight: maxInMeshScore / inMeshCap(), 264 TimeInMeshQuantum: inMeshTime(), 265 TimeInMeshCap: inMeshCap(), 266 FirstMessageDeliveriesWeight: firstMessageWeight, 267 FirstMessageDeliveriesDecay: scoreDecay(firstDecay * oneEpochDuration()), 268 FirstMessageDeliveriesCap: firstMessageCap, 269 MeshMessageDeliveriesWeight: meshWeight, 270 MeshMessageDeliveriesDecay: scoreDecay(meshDecay * oneEpochDuration()), 271 MeshMessageDeliveriesCap: meshCap, 272 MeshMessageDeliveriesThreshold: meshThreshold, 273 MeshMessageDeliveriesWindow: 2 * time.Second, 274 MeshMessageDeliveriesActivation: 1 * oneEpochDuration(), 275 MeshFailurePenaltyWeight: meshWeight, 276 MeshFailurePenaltyDecay: scoreDecay(meshDecay * oneEpochDuration()), 277 InvalidMessageDeliveriesWeight: -maxScore() / topicWeight, 278 InvalidMessageDeliveriesDecay: scoreDecay(50 * oneEpochDuration()), 279 }, nil 280 } 281 282 func defaultAttesterSlashingTopicParams() *pubsub.TopicScoreParams { 283 return &pubsub.TopicScoreParams{ 284 TopicWeight: attesterSlashingWeight, 285 TimeInMeshWeight: maxInMeshScore / inMeshCap(), 286 TimeInMeshQuantum: inMeshTime(), 287 TimeInMeshCap: inMeshCap(), 288 FirstMessageDeliveriesWeight: 36, 289 FirstMessageDeliveriesDecay: scoreDecay(100 * oneEpochDuration()), 290 FirstMessageDeliveriesCap: 1, 291 MeshMessageDeliveriesWeight: 0, 292 MeshMessageDeliveriesDecay: 0, 293 MeshMessageDeliveriesCap: 0, 294 MeshMessageDeliveriesThreshold: 0, 295 MeshMessageDeliveriesWindow: 0, 296 MeshMessageDeliveriesActivation: 0, 297 MeshFailurePenaltyWeight: 0, 298 MeshFailurePenaltyDecay: 0, 299 InvalidMessageDeliveriesWeight: -2000, 300 InvalidMessageDeliveriesDecay: scoreDecay(50 * oneEpochDuration()), 301 } 302 } 303 304 func defaultProposerSlashingTopicParams() *pubsub.TopicScoreParams { 305 return &pubsub.TopicScoreParams{ 306 TopicWeight: proposerSlashingWeight, 307 TimeInMeshWeight: maxInMeshScore / inMeshCap(), 308 TimeInMeshQuantum: inMeshTime(), 309 TimeInMeshCap: inMeshCap(), 310 FirstMessageDeliveriesWeight: 36, 311 FirstMessageDeliveriesDecay: scoreDecay(100 * oneEpochDuration()), 312 FirstMessageDeliveriesCap: 1, 313 MeshMessageDeliveriesWeight: 0, 314 MeshMessageDeliveriesDecay: 0, 315 MeshMessageDeliveriesCap: 0, 316 MeshMessageDeliveriesThreshold: 0, 317 MeshMessageDeliveriesWindow: 0, 318 MeshMessageDeliveriesActivation: 0, 319 MeshFailurePenaltyWeight: 0, 320 MeshFailurePenaltyDecay: 0, 321 InvalidMessageDeliveriesWeight: -2000, 322 InvalidMessageDeliveriesDecay: scoreDecay(50 * oneEpochDuration()), 323 } 324 } 325 326 func defaultVoluntaryExitTopicParams() *pubsub.TopicScoreParams { 327 return &pubsub.TopicScoreParams{ 328 TopicWeight: voluntaryExitWeight, 329 TimeInMeshWeight: maxInMeshScore / inMeshCap(), 330 TimeInMeshQuantum: inMeshTime(), 331 TimeInMeshCap: inMeshCap(), 332 FirstMessageDeliveriesWeight: 2, 333 FirstMessageDeliveriesDecay: scoreDecay(100 * oneEpochDuration()), 334 FirstMessageDeliveriesCap: 5, 335 MeshMessageDeliveriesWeight: 0, 336 MeshMessageDeliveriesDecay: 0, 337 MeshMessageDeliveriesCap: 0, 338 MeshMessageDeliveriesThreshold: 0, 339 MeshMessageDeliveriesWindow: 0, 340 MeshMessageDeliveriesActivation: 0, 341 MeshFailurePenaltyWeight: 0, 342 MeshFailurePenaltyDecay: 0, 343 InvalidMessageDeliveriesWeight: -2000, 344 InvalidMessageDeliveriesDecay: scoreDecay(50 * oneEpochDuration()), 345 } 346 } 347 348 func oneSlotDuration() time.Duration { 349 return time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second 350 } 351 352 func oneEpochDuration() time.Duration { 353 return time.Duration(params.BeaconConfig().SlotsPerEpoch) * oneSlotDuration() 354 } 355 356 // determines the decay rate from the provided time period till 357 // the decayToZero value. Ex: ( 1 -> 0.01) 358 func scoreDecay(totalDurationDecay time.Duration) float64 { 359 numOfTimes := totalDurationDecay / oneSlotDuration() 360 return math.Pow(decayToZero, 1/float64(numOfTimes)) 361 } 362 363 // is used to determine the threshold from the decay limit with 364 // a provided growth rate. This applies the decay rate to a 365 // computed limit. 366 func decayThreshold(decayRate, rate float64) (float64, error) { 367 d, err := decayLimit(decayRate, rate) 368 if err != nil { 369 return 0, err 370 } 371 return d * decayRate, nil 372 } 373 374 // decayLimit provides the value till which a decay process will 375 // limit till provided with an expected growth rate. 376 func decayLimit(decayRate, rate float64) (float64, error) { 377 if 1 <= decayRate { 378 return 0, errors.Errorf("got an invalid decayLimit rate: %f", decayRate) 379 } 380 return rate / (1 - decayRate), nil 381 } 382 383 func committeeCountPerSlot(activeValidators uint64) uint64 { 384 // Use a static parameter for now rather than a dynamic one, we can use 385 // the actual parameter later when we have figured out how to fix a circular 386 // dependency in service startup order. 387 return helpers.SlotCommitteeCount(activeValidators) 388 } 389 390 // Uses a very rough gauge for total aggregator size per slot. 391 func aggregatorsPerSlot(activeValidators uint64) uint64 { 392 comms := committeeCountPerSlot(activeValidators) 393 totalAggs := comms * params.BeaconConfig().TargetAggregatorsPerCommittee 394 return totalAggs 395 } 396 397 // provides the relevant score by the provided weight and threshold. 398 func scoreByWeight(weight, threshold float64) float64 { 399 return maxScore() / (weight * threshold * threshold) 400 } 401 402 // maxScore attainable by a peer. 403 func maxScore() float64 { 404 totalWeight := beaconBlockWeight + aggregateWeight + attestationTotalWeight + 405 attesterSlashingWeight + proposerSlashingWeight + voluntaryExitWeight 406 return (maxInMeshScore + maxFirstDeliveryScore) * totalWeight 407 } 408 409 // denotes the unit time in mesh for scoring tallying. 410 func inMeshTime() time.Duration { 411 return 1 * oneSlotDuration() 412 } 413 414 // the cap for `inMesh` time scoring. 415 func inMeshCap() float64 { 416 return float64((3600 * time.Second) / inMeshTime()) 417 } 418 419 func logGossipParameters(topic string, params *pubsub.TopicScoreParams) { 420 // Exit early in the event the parameter struct is nil. 421 if params == nil { 422 return 423 } 424 rawParams := reflect.ValueOf(params).Elem() 425 numOfFields := rawParams.NumField() 426 427 fields := make(logrus.Fields, numOfFields) 428 for i := 0; i < numOfFields; i++ { 429 fields[reflect.TypeOf(params).Elem().Field(i).Name] = rawParams.Field(i).Interface() 430 } 431 log.WithFields(fields).Debugf("Topic Parameters for %s", topic) 432 }