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  }