github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/p2p/peers/scorers/block_providers_test.go (about)

     1  package scorers_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sort"
     7  	"strconv"
     8  	"testing"
     9  
    10  	"github.com/libp2p/go-libp2p-core/peer"
    11  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers"
    12  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/scorers"
    13  	"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
    14  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    15  	"github.com/prysmaticlabs/prysm/shared/rand"
    16  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    17  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    18  )
    19  
    20  func TestScorers_BlockProvider_Score(t *testing.T) {
    21  	ctx, cancel := context.WithCancel(context.Background())
    22  	defer cancel()
    23  
    24  	batchSize := uint64(flags.Get().BlockBatchLimit)
    25  	tests := []struct {
    26  		name   string
    27  		update func(scorer *scorers.BlockProviderScorer)
    28  		check  func(scorer *scorers.BlockProviderScorer)
    29  	}{
    30  		{
    31  			name: "nonexistent peer",
    32  			check: func(scorer *scorers.BlockProviderScorer) {
    33  				assert.Equal(t, scorer.MaxScore(), scorer.Score("peer1"), "Unexpected score")
    34  			},
    35  		},
    36  		{
    37  			name: "existent peer with zero score",
    38  			update: func(scorer *scorers.BlockProviderScorer) {
    39  				scorer.Touch("peer1")
    40  			},
    41  			check: func(scorer *scorers.BlockProviderScorer) {
    42  				assert.Equal(t, 0.0, scorer.Score("peer1"), "Unexpected score")
    43  			},
    44  		},
    45  		{
    46  			name: "existent peer via increment",
    47  			update: func(scorer *scorers.BlockProviderScorer) {
    48  				scorer.IncrementProcessedBlocks("peer1", 0)
    49  			},
    50  			check: func(scorer *scorers.BlockProviderScorer) {
    51  				assert.Equal(t, 0.0, scorer.Score("peer1"), "Unexpected score")
    52  			},
    53  		},
    54  		{
    55  			name: "boost score of stale peer",
    56  			update: func(scorer *scorers.BlockProviderScorer) {
    57  				batchWeight := scorer.Params().ProcessedBatchWeight
    58  				scorer.IncrementProcessedBlocks("peer1", batchSize*3)
    59  				assert.Equal(t, roundScore(batchWeight*3), scorer.Score("peer1"), "Unexpected score")
    60  				scorer.Touch("peer1", timeutils.Now().Add(-1*scorer.Params().StalePeerRefreshInterval))
    61  			},
    62  			check: func(scorer *scorers.BlockProviderScorer) {
    63  				assert.Equal(t, scorer.MaxScore(), scorer.Score("peer1"), "Unexpected score")
    64  			},
    65  		},
    66  		{
    67  			name: "increment with 0 score",
    68  			update: func(scorer *scorers.BlockProviderScorer) {
    69  				// Increment to zero (provider is added to cache but score is unchanged).
    70  				scorer.IncrementProcessedBlocks("peer1", 0)
    71  			},
    72  			check: func(scorer *scorers.BlockProviderScorer) {
    73  				assert.Equal(t, 0.0, scorer.Score("peer1"), "Unexpected score")
    74  			},
    75  		},
    76  		{
    77  			name: "partial score",
    78  			update: func(scorer *scorers.BlockProviderScorer) {
    79  				// Partial score (less than a single batch of blocks processed).
    80  				scorer.IncrementProcessedBlocks("peer1", batchSize/2)
    81  			},
    82  			check: func(scorer *scorers.BlockProviderScorer) {
    83  				assert.Equal(t, 0.0, scorer.Score("peer1"), "Unexpected score")
    84  			},
    85  		},
    86  		{
    87  			name: "single batch",
    88  			update: func(scorer *scorers.BlockProviderScorer) {
    89  				scorer.IncrementProcessedBlocks("peer1", batchSize)
    90  			},
    91  			check: func(scorer *scorers.BlockProviderScorer) {
    92  				batchWeight := scorer.Params().ProcessedBatchWeight
    93  				assert.Equal(t, roundScore(batchWeight), scorer.Score("peer1"), "Unexpected score")
    94  			},
    95  		},
    96  		{
    97  			name: "multiple batches",
    98  			update: func(scorer *scorers.BlockProviderScorer) {
    99  				scorer.IncrementProcessedBlocks("peer1", batchSize*7)
   100  			},
   101  			check: func(scorer *scorers.BlockProviderScorer) {
   102  				batchWeight := scorer.Params().ProcessedBatchWeight
   103  				assert.Equal(t, roundScore(batchWeight*7), scorer.Score("peer1"), "Unexpected score")
   104  			},
   105  		},
   106  		{
   107  			name: "maximum score cap",
   108  			update: func(scorer *scorers.BlockProviderScorer) {
   109  				batchWeight := scorer.Params().ProcessedBatchWeight
   110  				scorer.IncrementProcessedBlocks("peer1", batchSize*2)
   111  				assert.Equal(t, roundScore(batchWeight*2), scorer.Score("peer1"), "Unexpected score")
   112  				scorer.IncrementProcessedBlocks("peer1", scorer.Params().ProcessedBlocksCap)
   113  			},
   114  			check: func(scorer *scorers.BlockProviderScorer) {
   115  				assert.Equal(t, scorer.Params().ProcessedBlocksCap, scorer.ProcessedBlocks("peer1"))
   116  				assert.Equal(t, 1.0, scorer.Score("peer1"))
   117  			},
   118  		},
   119  	}
   120  
   121  	for _, tt := range tests {
   122  		t.Run(tt.name, func(t *testing.T) {
   123  			peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   124  				PeerLimit: 30,
   125  				ScorerParams: &scorers.Config{
   126  					BlockProviderScorerConfig: &scorers.BlockProviderScorerConfig{},
   127  				},
   128  			})
   129  			scorer := peerStatuses.Scorers().BlockProviderScorer()
   130  			if tt.update != nil {
   131  				tt.update(scorer)
   132  			}
   133  			tt.check(scorer)
   134  		})
   135  	}
   136  }
   137  
   138  func TestScorers_BlockProvider_GettersSetters(t *testing.T) {
   139  	ctx, cancel := context.WithCancel(context.Background())
   140  	defer cancel()
   141  
   142  	peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   143  		ScorerParams: &scorers.Config{},
   144  	})
   145  	scorer := peerStatuses.Scorers().BlockProviderScorer()
   146  
   147  	assert.Equal(t, uint64(0), scorer.ProcessedBlocks("peer1"), "Unexpected count for unregistered peer")
   148  	scorer.IncrementProcessedBlocks("peer1", 64)
   149  	assert.Equal(t, uint64(64), scorer.ProcessedBlocks("peer1"))
   150  }
   151  
   152  func TestScorers_BlockProvider_WeightSorted(t *testing.T) {
   153  	ctx, cancel := context.WithCancel(context.Background())
   154  	defer cancel()
   155  	peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   156  		ScorerParams: &scorers.Config{
   157  			BlockProviderScorerConfig: &scorers.BlockProviderScorerConfig{
   158  				ProcessedBatchWeight: 0.01,
   159  			},
   160  		},
   161  	})
   162  	scorer := peerStatuses.Scorers().BlockProviderScorer()
   163  	batchSize := uint64(flags.Get().BlockBatchLimit)
   164  	r := rand.NewDeterministicGenerator()
   165  
   166  	reverse := func(pids []peer.ID) []peer.ID {
   167  		tmp := make([]peer.ID, len(pids))
   168  		copy(tmp, pids)
   169  		for i, j := 0, len(tmp)-1; i < j; i, j = i+1, j-1 {
   170  			tmp[i], tmp[j] = tmp[j], tmp[i]
   171  		}
   172  		return tmp
   173  	}
   174  
   175  	shuffle := func(pids []peer.ID) []peer.ID {
   176  		tmp := make([]peer.ID, len(pids))
   177  		copy(tmp, pids)
   178  		r.Shuffle(len(tmp), func(i, j int) {
   179  			tmp[i], tmp[j] = tmp[j], tmp[i]
   180  		})
   181  		return tmp
   182  	}
   183  
   184  	var pids []peer.ID
   185  	for i := uint64(0); i < 10; i++ {
   186  		pid := peer.ID(strconv.FormatUint(i, 10))
   187  		scorer.IncrementProcessedBlocks(pid, i*batchSize)
   188  		pids = append(pids, pid)
   189  	}
   190  	// Make sure that peers scores are correct (peer(n).score > peer(n-1).score).
   191  	// Peers should be returned in descending order (by score).
   192  	assert.DeepEqual(t, reverse(pids), scorer.Sorted(pids, nil))
   193  
   194  	// Run weighted sort lots of time, to get accurate statistics of whether more heavy items
   195  	// are indeed preferred when sorting.
   196  	scores := make(map[peer.ID]int, len(pids))
   197  	for i := 0; i < 1000; i++ {
   198  		score := len(pids) - 1
   199  		// The earlier in the list the item is, the more of a score will it get.
   200  		for _, pid := range scorer.WeightSorted(r, shuffle(pids), nil) {
   201  			scores[pid] += score
   202  			score--
   203  		}
   204  	}
   205  	var scoredPIDs []peer.ID
   206  	for pid := range scores {
   207  		scoredPIDs = append(scoredPIDs, pid)
   208  	}
   209  	sort.Slice(scoredPIDs, func(i, j int) bool {
   210  		return scores[scoredPIDs[i]] > scores[scoredPIDs[j]]
   211  	})
   212  	assert.Equal(t, len(pids), len(scoredPIDs))
   213  	assert.DeepEqual(t, reverse(pids), scoredPIDs, "Expected items with more weight to be picked more often")
   214  }
   215  
   216  func TestScorers_BlockProvider_Sorted(t *testing.T) {
   217  	batchSize := uint64(flags.Get().BlockBatchLimit)
   218  	tests := []struct {
   219  		name   string
   220  		update func(s *scorers.BlockProviderScorer)
   221  		score  func(pid peer.ID, score float64) float64
   222  		have   []peer.ID
   223  		want   []peer.ID
   224  	}{
   225  		{
   226  			name:   "no peers",
   227  			update: func(s *scorers.BlockProviderScorer) {},
   228  			have:   []peer.ID{},
   229  			want:   []peer.ID{},
   230  		},
   231  		{
   232  			name: "same scores",
   233  			update: func(s *scorers.BlockProviderScorer) {
   234  				s.IncrementProcessedBlocks("peer1", 16)
   235  				s.IncrementProcessedBlocks("peer2", 16)
   236  				s.IncrementProcessedBlocks("peer3", 16)
   237  			},
   238  			have: []peer.ID{"peer1", "peer2", "peer3"},
   239  			want: []peer.ID{"peer1", "peer2", "peer3"},
   240  		},
   241  		{
   242  			name: "same scores multiple batches",
   243  			update: func(s *scorers.BlockProviderScorer) {
   244  				s.IncrementProcessedBlocks("peer1", batchSize*7+16)
   245  				s.IncrementProcessedBlocks("peer2", batchSize*7+16)
   246  				s.IncrementProcessedBlocks("peer3", batchSize*7+16)
   247  			},
   248  			have: []peer.ID{"peer1", "peer2", "peer3"},
   249  			want: []peer.ID{"peer1", "peer2", "peer3"},
   250  		},
   251  		{
   252  			name: "same scores multiple batches unequal blocks",
   253  			update: func(s *scorers.BlockProviderScorer) {
   254  				s.IncrementProcessedBlocks("peer1", batchSize*7+6)
   255  				s.IncrementProcessedBlocks("peer2", batchSize*7+16)
   256  				s.IncrementProcessedBlocks("peer3", batchSize*7+26)
   257  			},
   258  			have: []peer.ID{"peer1", "peer2", "peer3"},
   259  			want: []peer.ID{"peer1", "peer2", "peer3"},
   260  		},
   261  		{
   262  			name: "different scores",
   263  			update: func(s *scorers.BlockProviderScorer) {
   264  				s.IncrementProcessedBlocks("peer1", batchSize*3)
   265  				s.IncrementProcessedBlocks("peer2", batchSize*1)
   266  				s.IncrementProcessedBlocks("peer3", batchSize*2)
   267  			},
   268  			have: []peer.ID{"peer3", "peer2", "peer1"},
   269  			want: []peer.ID{"peer1", "peer3", "peer2"},
   270  		},
   271  		{
   272  			name: "custom scorer",
   273  			update: func(s *scorers.BlockProviderScorer) {
   274  				s.IncrementProcessedBlocks("peer1", batchSize*3)
   275  				s.IncrementProcessedBlocks("peer2", batchSize*1)
   276  				s.IncrementProcessedBlocks("peer3", batchSize*2)
   277  			},
   278  			score: func(pid peer.ID, score float64) float64 {
   279  				if pid == "peer2" {
   280  					return score + 0.3 // 0.2 + 0.3 = 0.5 > 0.4 (of peer3)
   281  				}
   282  				if pid == "peer1" {
   283  					return 0.0
   284  				}
   285  				return score
   286  			},
   287  			have: []peer.ID{"peer3", "peer2", "peer1"},
   288  			want: []peer.ID{"peer2", "peer3", "peer1"},
   289  		},
   290  	}
   291  	for _, tt := range tests {
   292  		t.Run(tt.name, func(t *testing.T) {
   293  			ctx, cancel := context.WithCancel(context.Background())
   294  			defer cancel()
   295  			peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   296  				ScorerParams: &scorers.Config{
   297  					BlockProviderScorerConfig: &scorers.BlockProviderScorerConfig{
   298  						ProcessedBatchWeight: 0.2,
   299  					},
   300  				},
   301  			})
   302  			scorer := peerStatuses.Scorers().BlockProviderScorer()
   303  			tt.update(scorer)
   304  			assert.DeepEqual(t, tt.want, scorer.Sorted(tt.have, tt.score))
   305  		})
   306  	}
   307  }
   308  
   309  func TestScorers_BlockProvider_MaxScore(t *testing.T) {
   310  	ctx, cancel := context.WithCancel(context.Background())
   311  	defer cancel()
   312  	batchSize := uint64(flags.Get().BlockBatchLimit)
   313  
   314  	tests := []struct {
   315  		name string
   316  		cfg  *scorers.BlockProviderScorerConfig
   317  		want float64
   318  	}{
   319  		{
   320  			name: "default config",
   321  			cfg:  &scorers.BlockProviderScorerConfig{},
   322  			want: 1.0,
   323  		},
   324  		{
   325  			name: "custom config",
   326  			cfg: &scorers.BlockProviderScorerConfig{
   327  				ProcessedBatchWeight: 0.5,
   328  				ProcessedBlocksCap:   batchSize * 300,
   329  			},
   330  			want: 150.0,
   331  		},
   332  	}
   333  
   334  	for _, tt := range tests {
   335  		t.Run(tt.name, func(t *testing.T) {
   336  			peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   337  				ScorerParams: &scorers.Config{
   338  					BlockProviderScorerConfig: tt.cfg,
   339  				},
   340  			})
   341  			scorer := peerStatuses.Scorers().BlockProviderScorer()
   342  			assert.Equal(t, tt.want, scorer.MaxScore())
   343  		})
   344  	}
   345  }
   346  
   347  func TestScorers_BlockProvider_FormatScorePretty(t *testing.T) {
   348  	ctx, cancel := context.WithCancel(context.Background())
   349  	defer cancel()
   350  	batchSize := uint64(flags.Get().BlockBatchLimit)
   351  	format := "[%0.1f%%, raw: %0.2f,  blocks: %d/1280]"
   352  
   353  	tests := []struct {
   354  		name   string
   355  		update func(s *scorers.BlockProviderScorer)
   356  		check  func(s *scorers.BlockProviderScorer)
   357  	}{
   358  		{
   359  			name:   "peer not registered",
   360  			update: nil,
   361  			check: func(s *scorers.BlockProviderScorer) {
   362  				assert.Equal(t, fmt.Sprintf(format, 100.0, 1.0, 0), s.FormatScorePretty("peer1"))
   363  			},
   364  		},
   365  		{
   366  			name: "peer registered zero blocks",
   367  			update: func(s *scorers.BlockProviderScorer) {
   368  				s.Touch("peer1")
   369  			},
   370  			check: func(s *scorers.BlockProviderScorer) {
   371  				assert.Equal(t, fmt.Sprintf(format, 0.0, 0.0, 0), s.FormatScorePretty("peer1"))
   372  			},
   373  		},
   374  		{
   375  			name: "partial batch",
   376  			update: func(s *scorers.BlockProviderScorer) {
   377  				s.IncrementProcessedBlocks("peer1", batchSize/4)
   378  			},
   379  			check: func(s *scorers.BlockProviderScorer) {
   380  				assert.Equal(t, fmt.Sprintf(format, 0.0, 0.0, batchSize/4), s.FormatScorePretty("peer1"))
   381  			},
   382  		},
   383  		{
   384  			name: "single batch",
   385  			update: func(s *scorers.BlockProviderScorer) {
   386  				s.IncrementProcessedBlocks("peer1", batchSize)
   387  			},
   388  			check: func(s *scorers.BlockProviderScorer) {
   389  				assert.Equal(t, fmt.Sprintf(format, 5.0, 0.05, batchSize), s.FormatScorePretty("peer1"))
   390  			},
   391  		},
   392  		{
   393  			name: "3/2 of a batch",
   394  			update: func(s *scorers.BlockProviderScorer) {
   395  				s.IncrementProcessedBlocks("peer1", batchSize*3/2)
   396  			},
   397  			check: func(s *scorers.BlockProviderScorer) {
   398  				assert.Equal(t, fmt.Sprintf(format, 5.0, 0.05, batchSize*3/2), s.FormatScorePretty("peer1"))
   399  			},
   400  		},
   401  		{
   402  			name: "multiple batches",
   403  			update: func(s *scorers.BlockProviderScorer) {
   404  				s.IncrementProcessedBlocks("peer1", batchSize*5)
   405  			},
   406  			check: func(s *scorers.BlockProviderScorer) {
   407  				assert.Equal(t, fmt.Sprintf(format, 25.0, 0.05*5, batchSize*5), s.FormatScorePretty("peer1"))
   408  			},
   409  		},
   410  		{
   411  			name: "multiple batches max score",
   412  			update: func(s *scorers.BlockProviderScorer) {
   413  				s.IncrementProcessedBlocks("peer1", s.Params().ProcessedBlocksCap*5)
   414  			},
   415  			check: func(s *scorers.BlockProviderScorer) {
   416  				want := fmt.Sprintf(format, 100.0, 1.0, s.Params().ProcessedBlocksCap)
   417  				assert.Equal(t, want, s.FormatScorePretty("peer1"))
   418  			},
   419  		},
   420  		{
   421  			name: "decaying",
   422  			update: func(s *scorers.BlockProviderScorer) {
   423  				s.IncrementProcessedBlocks("peer1", batchSize*5)
   424  				s.IncrementProcessedBlocks("peer1", batchSize)
   425  				s.IncrementProcessedBlocks("peer1", batchSize/4)
   426  				want := fmt.Sprintf(format, 30.0, 0.05*6, batchSize*6+batchSize/4)
   427  				assert.Equal(t, want, s.FormatScorePretty("peer1"))
   428  				// Maximize block count.
   429  				s.IncrementProcessedBlocks("peer1", s.Params().ProcessedBlocksCap)
   430  				want = fmt.Sprintf(format, 100.0, 1.0, s.Params().ProcessedBlocksCap)
   431  				assert.Equal(t, want, s.FormatScorePretty("peer1"))
   432  				// Half of blocks is to be decayed.
   433  				s.Decay()
   434  			},
   435  			check: func(s *scorers.BlockProviderScorer) {
   436  				want := fmt.Sprintf(format, 50.0, 0.5, s.Params().ProcessedBlocksCap/2)
   437  				assert.Equal(t, want, s.FormatScorePretty("peer1"))
   438  			},
   439  		},
   440  	}
   441  
   442  	peerStatusGen := func() *peers.Status {
   443  		return peers.NewStatus(ctx, &peers.StatusConfig{
   444  			ScorerParams: &scorers.Config{
   445  				BlockProviderScorerConfig: &scorers.BlockProviderScorerConfig{
   446  					ProcessedBatchWeight: 0.05,
   447  					ProcessedBlocksCap:   20 * batchSize,
   448  					Decay:                10 * batchSize,
   449  				},
   450  			},
   451  		})
   452  	}
   453  	for _, tt := range tests {
   454  		t.Run(tt.name, func(t *testing.T) {
   455  			peerStatuses := peerStatusGen()
   456  			scorer := peerStatuses.Scorers().BlockProviderScorer()
   457  			if tt.update != nil {
   458  				tt.update(scorer)
   459  			}
   460  			tt.check(scorer)
   461  		})
   462  	}
   463  
   464  	t.Run("peer scorer disabled", func(t *testing.T) {
   465  		resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{
   466  			EnablePeerScorer: false,
   467  		})
   468  		defer resetCfg()
   469  		peerStatuses := peerStatusGen()
   470  		scorer := peerStatuses.Scorers().BlockProviderScorer()
   471  		assert.Equal(t, "disabled", scorer.FormatScorePretty("peer1"))
   472  	})
   473  }
   474  
   475  func TestScorers_BlockProvider_BadPeerMarking(t *testing.T) {
   476  	ctx, cancel := context.WithCancel(context.Background())
   477  	defer cancel()
   478  
   479  	peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   480  		ScorerParams: &scorers.Config{},
   481  	})
   482  	scorer := peerStatuses.Scorers().BlockProviderScorer()
   483  
   484  	assert.Equal(t, false, scorer.IsBadPeer("peer1"), "Unexpected status for unregistered peer")
   485  	scorer.IncrementProcessedBlocks("peer1", 64)
   486  	assert.Equal(t, false, scorer.IsBadPeer("peer1"))
   487  	assert.Equal(t, 0, len(scorer.BadPeers()))
   488  }