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 }