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 }