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

     1  package scorers_test
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/libp2p/go-libp2p-core/network"
     9  	"github.com/libp2p/go-libp2p-core/peer"
    10  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers"
    11  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers/scorers"
    12  	"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
    13  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    14  )
    15  
    16  func TestScorers_Service_Init(t *testing.T) {
    17  	ctx, cancel := context.WithCancel(context.Background())
    18  	defer cancel()
    19  
    20  	batchSize := uint64(flags.Get().BlockBatchLimit)
    21  
    22  	t.Run("default config", func(t *testing.T) {
    23  		peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
    24  			PeerLimit:    30,
    25  			ScorerParams: &scorers.Config{},
    26  		})
    27  
    28  		t.Run("bad responses scorer", func(t *testing.T) {
    29  			params := peerStatuses.Scorers().BadResponsesScorer().Params()
    30  			assert.Equal(t, scorers.DefaultBadResponsesThreshold, params.Threshold, "Unexpected threshold value")
    31  			assert.Equal(t, scorers.DefaultBadResponsesDecayInterval,
    32  				params.DecayInterval, "Unexpected decay interval value")
    33  		})
    34  
    35  		t.Run("block providers scorer", func(t *testing.T) {
    36  			params := peerStatuses.Scorers().BlockProviderScorer().Params()
    37  			assert.Equal(t, scorers.DefaultBlockProviderProcessedBatchWeight, params.ProcessedBatchWeight)
    38  			assert.Equal(t, scorers.DefaultBlockProviderProcessedBlocksCap, params.ProcessedBlocksCap)
    39  			assert.Equal(t, scorers.DefaultBlockProviderDecayInterval, params.DecayInterval)
    40  			assert.Equal(t, scorers.DefaultBlockProviderDecay, params.Decay)
    41  			assert.Equal(t, scorers.DefaultBlockProviderStalePeerRefreshInterval, params.StalePeerRefreshInterval)
    42  		})
    43  	})
    44  
    45  	t.Run("explicit config", func(t *testing.T) {
    46  		peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
    47  			PeerLimit: 30,
    48  			ScorerParams: &scorers.Config{
    49  				BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{
    50  					Threshold:     2,
    51  					DecayInterval: 1 * time.Minute,
    52  				},
    53  				BlockProviderScorerConfig: &scorers.BlockProviderScorerConfig{
    54  					ProcessedBatchWeight:     0.2,
    55  					ProcessedBlocksCap:       batchSize * 5,
    56  					DecayInterval:            1 * time.Minute,
    57  					Decay:                    16,
    58  					StalePeerRefreshInterval: 5 * time.Hour,
    59  				},
    60  			},
    61  		})
    62  
    63  		t.Run("bad responses scorer", func(t *testing.T) {
    64  			params := peerStatuses.Scorers().BadResponsesScorer().Params()
    65  			assert.Equal(t, 2, params.Threshold, "Unexpected threshold value")
    66  			assert.Equal(t, 1*time.Minute, params.DecayInterval, "Unexpected decay interval value")
    67  		})
    68  
    69  		t.Run("block provider scorer", func(t *testing.T) {
    70  			params := peerStatuses.Scorers().BlockProviderScorer().Params()
    71  			assert.Equal(t, 0.2, params.ProcessedBatchWeight)
    72  			assert.Equal(t, batchSize*5, params.ProcessedBlocksCap)
    73  			assert.Equal(t, 1*time.Minute, params.DecayInterval)
    74  			assert.Equal(t, uint64(16), params.Decay)
    75  			assert.Equal(t, 5*time.Hour, params.StalePeerRefreshInterval)
    76  			assert.Equal(t, 1.0, peerStatuses.Scorers().BlockProviderScorer().MaxScore())
    77  		})
    78  	})
    79  }
    80  
    81  func TestScorers_Service_Score(t *testing.T) {
    82  	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    83  	defer cancel()
    84  
    85  	batchSize := uint64(flags.Get().BlockBatchLimit)
    86  
    87  	peerScores := func(s *scorers.Service, pids []peer.ID) map[string]float64 {
    88  		scores := make(map[string]float64, len(pids))
    89  		for _, pid := range pids {
    90  			scores[string(pid)] = s.Score(pid)
    91  		}
    92  		return scores
    93  	}
    94  
    95  	pack := func(scorer *scorers.Service, s1, s2, s3 float64) map[string]float64 {
    96  		return map[string]float64{
    97  			"peer1": roundScore(s1),
    98  			"peer2": roundScore(s2),
    99  			"peer3": roundScore(s3),
   100  		}
   101  	}
   102  
   103  	setupScorer := func() (*scorers.Service, []peer.ID) {
   104  		peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   105  			PeerLimit: 30,
   106  			ScorerParams: &scorers.Config{
   107  				BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{
   108  					Threshold: 5,
   109  				},
   110  				BlockProviderScorerConfig: &scorers.BlockProviderScorerConfig{
   111  					Decay: 64,
   112  				},
   113  			},
   114  		})
   115  		s := peerStatuses.Scorers()
   116  		pids := []peer.ID{"peer1", "peer2", "peer3"}
   117  		for _, pid := range pids {
   118  			peerStatuses.Add(nil, pid, nil, network.DirUnknown)
   119  			// Not yet used peer gets boosted score.
   120  			startScore := s.BlockProviderScorer().MaxScore()
   121  			assert.Equal(t, startScore/float64(s.ActiveScorersCount()), s.Score(pid), "Unexpected score for not yet used peer")
   122  		}
   123  		return s, pids
   124  	}
   125  
   126  	t.Run("no peer registered", func(t *testing.T) {
   127  		peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   128  			ScorerParams: &scorers.Config{},
   129  		})
   130  		s := peerStatuses.Scorers()
   131  		assert.Equal(t, 0.0, s.BadResponsesScorer().Score("peer1"))
   132  		assert.Equal(t, s.BlockProviderScorer().MaxScore(), s.BlockProviderScorer().Score("peer1"))
   133  		assert.Equal(t, 0.0, s.Score("peer1"))
   134  	})
   135  
   136  	t.Run("bad responses score", func(t *testing.T) {
   137  		s, pids := setupScorer()
   138  		// Peers start with boosted start score (new peers are boosted by block provider).
   139  		startScore := s.BlockProviderScorer().MaxScore() / float64(s.ActiveScorersCount())
   140  		penalty := (-1 / float64(s.BadResponsesScorer().Params().Threshold)) / float64(s.ActiveScorersCount())
   141  
   142  		// Update peers' stats and test the effect on peer order.
   143  		s.BadResponsesScorer().Increment("peer2")
   144  		assert.DeepEqual(t, pack(s, startScore, startScore+penalty, startScore), peerScores(s, pids))
   145  		s.BadResponsesScorer().Increment("peer1")
   146  		s.BadResponsesScorer().Increment("peer1")
   147  		assert.DeepEqual(t, pack(s, startScore+2*penalty, startScore+penalty, startScore), peerScores(s, pids))
   148  
   149  		// See how decaying affects order of peers.
   150  		s.BadResponsesScorer().Decay()
   151  		assert.DeepEqual(t, pack(s, startScore+penalty, startScore, startScore), peerScores(s, pids))
   152  		s.BadResponsesScorer().Decay()
   153  		assert.DeepEqual(t, pack(s, startScore, startScore, startScore), peerScores(s, pids))
   154  	})
   155  
   156  	t.Run("block providers score", func(t *testing.T) {
   157  		s, pids := setupScorer()
   158  		s1 := s.BlockProviderScorer()
   159  		startScore := s.BlockProviderScorer().MaxScore() / 2
   160  		batchWeight := s1.Params().ProcessedBatchWeight / 2
   161  
   162  		// Partial batch.
   163  		s1.IncrementProcessedBlocks("peer1", batchSize/4)
   164  		assert.Equal(t, 0.0, s.Score("peer1"), "Unexpected %q score", "peer1")
   165  
   166  		// Single batch.
   167  		s1.IncrementProcessedBlocks("peer1", batchSize)
   168  		assert.DeepEqual(t, pack(s, batchWeight, startScore, startScore), peerScores(s, pids), "Unexpected scores")
   169  
   170  		// Multiple batches.
   171  		s1.IncrementProcessedBlocks("peer2", batchSize*4)
   172  		assert.DeepEqual(t, pack(s, batchWeight, batchWeight*4, startScore), peerScores(s, pids), "Unexpected scores")
   173  
   174  		// Partial batch.
   175  		s1.IncrementProcessedBlocks("peer3", batchSize/2)
   176  		assert.DeepEqual(t, pack(s, batchWeight, batchWeight*4, 0), peerScores(s, pids), "Unexpected scores")
   177  
   178  		// See effect of decaying.
   179  		assert.Equal(t, batchSize+batchSize/4, s1.ProcessedBlocks("peer1"))
   180  		assert.Equal(t, batchSize*4, s1.ProcessedBlocks("peer2"))
   181  		assert.Equal(t, batchSize/2, s1.ProcessedBlocks("peer3"))
   182  		assert.DeepEqual(t, pack(s, batchWeight, batchWeight*4, 0), peerScores(s, pids), "Unexpected scores")
   183  		s1.Decay()
   184  		assert.Equal(t, batchSize/4, s1.ProcessedBlocks("peer1"))
   185  		assert.Equal(t, batchSize*3, s1.ProcessedBlocks("peer2"))
   186  		assert.Equal(t, uint64(0), s1.ProcessedBlocks("peer3"))
   187  		assert.DeepEqual(t, pack(s, 0, batchWeight*3, 0), peerScores(s, pids), "Unexpected scores")
   188  	})
   189  
   190  	t.Run("overall score", func(t *testing.T) {
   191  		s, _ := setupScorer()
   192  		s1 := s.BlockProviderScorer()
   193  		s2 := s.BadResponsesScorer()
   194  		batchWeight := s1.Params().ProcessedBatchWeight / float64(s.ActiveScorersCount())
   195  		penalty := (-1 / float64(s.BadResponsesScorer().Params().Threshold)) / float64(s.ActiveScorersCount())
   196  
   197  		// Full score, no penalty.
   198  		s1.IncrementProcessedBlocks("peer1", batchSize*5)
   199  		assert.Equal(t, roundScore(batchWeight*5), s.Score("peer1"))
   200  		// Now, adjust score by introducing penalty for bad responses.
   201  		s2.Increment("peer1")
   202  		s2.Increment("peer1")
   203  		assert.Equal(t, roundScore(batchWeight*5+2*penalty), s.Score("peer1"), "Unexpected overall score")
   204  		// If peer continues to misbehave, score becomes negative.
   205  		s2.Increment("peer1")
   206  		assert.Equal(t, roundScore(batchWeight*5+3*penalty), s.Score("peer1"), "Unexpected overall score")
   207  	})
   208  }
   209  
   210  func TestScorers_Service_loop(t *testing.T) {
   211  	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
   212  	defer cancel()
   213  
   214  	peerStatuses := peers.NewStatus(ctx, &peers.StatusConfig{
   215  		PeerLimit: 30,
   216  		ScorerParams: &scorers.Config{
   217  			BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{
   218  				Threshold:     5,
   219  				DecayInterval: 50 * time.Millisecond,
   220  			},
   221  			BlockProviderScorerConfig: &scorers.BlockProviderScorerConfig{
   222  				DecayInterval: 25 * time.Millisecond,
   223  				Decay:         64,
   224  			},
   225  		},
   226  	})
   227  	s1 := peerStatuses.Scorers().BadResponsesScorer()
   228  	s2 := peerStatuses.Scorers().BlockProviderScorer()
   229  
   230  	pid1 := peer.ID("peer1")
   231  	peerStatuses.Add(nil, pid1, nil, network.DirUnknown)
   232  	for i := 0; i < s1.Params().Threshold+5; i++ {
   233  		s1.Increment(pid1)
   234  	}
   235  	assert.Equal(t, true, s1.IsBadPeer(pid1), "Peer should be marked as bad")
   236  
   237  	s2.IncrementProcessedBlocks("peer1", 221)
   238  	assert.Equal(t, uint64(221), s2.ProcessedBlocks("peer1"))
   239  
   240  	done := make(chan struct{}, 1)
   241  	go func() {
   242  		defer func() {
   243  			done <- struct{}{}
   244  		}()
   245  		ticker := time.NewTicker(50 * time.Millisecond)
   246  		defer ticker.Stop()
   247  		for {
   248  			select {
   249  			case <-ticker.C:
   250  				if s1.IsBadPeer(pid1) == false && s2.ProcessedBlocks("peer1") == 0 {
   251  					return
   252  				}
   253  			case <-ctx.Done():
   254  				t.Error("Timed out")
   255  				return
   256  			}
   257  		}
   258  	}()
   259  
   260  	<-done
   261  	assert.Equal(t, false, s1.IsBadPeer(pid1), "Peer should not be marked as bad")
   262  	assert.Equal(t, uint64(0), s2.ProcessedBlocks("peer1"), "No blocks are expected")
   263  }
   264  
   265  func TestScorers_Service_IsBadPeer(t *testing.T) {
   266  	peerStatuses := peers.NewStatus(context.Background(), &peers.StatusConfig{
   267  		PeerLimit: 30,
   268  		ScorerParams: &scorers.Config{
   269  			BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{
   270  				Threshold:     2,
   271  				DecayInterval: 50 * time.Second,
   272  			},
   273  		},
   274  	})
   275  
   276  	assert.Equal(t, false, peerStatuses.Scorers().IsBadPeer("peer1"))
   277  	peerStatuses.Scorers().BadResponsesScorer().Increment("peer1")
   278  	peerStatuses.Scorers().BadResponsesScorer().Increment("peer1")
   279  	assert.Equal(t, true, peerStatuses.Scorers().IsBadPeer("peer1"))
   280  }
   281  
   282  func TestScorers_Service_BadPeers(t *testing.T) {
   283  	peerStatuses := peers.NewStatus(context.Background(), &peers.StatusConfig{
   284  		PeerLimit: 30,
   285  		ScorerParams: &scorers.Config{
   286  			BadResponsesScorerConfig: &scorers.BadResponsesScorerConfig{
   287  				Threshold:     2,
   288  				DecayInterval: 50 * time.Second,
   289  			},
   290  		},
   291  	})
   292  
   293  	assert.Equal(t, false, peerStatuses.Scorers().IsBadPeer("peer1"))
   294  	assert.Equal(t, false, peerStatuses.Scorers().IsBadPeer("peer2"))
   295  	assert.Equal(t, false, peerStatuses.Scorers().IsBadPeer("peer3"))
   296  	assert.Equal(t, 0, len(peerStatuses.Scorers().BadPeers()))
   297  	for _, pid := range []peer.ID{"peer1", "peer3"} {
   298  		peerStatuses.Scorers().BadResponsesScorer().Increment(pid)
   299  		peerStatuses.Scorers().BadResponsesScorer().Increment(pid)
   300  	}
   301  	assert.Equal(t, true, peerStatuses.Scorers().IsBadPeer("peer1"))
   302  	assert.Equal(t, false, peerStatuses.Scorers().IsBadPeer("peer2"))
   303  	assert.Equal(t, true, peerStatuses.Scorers().IsBadPeer("peer3"))
   304  	assert.Equal(t, 2, len(peerStatuses.Scorers().BadPeers()))
   305  }