github.com/vipernet-xyz/tm@v0.34.24/blockchain/v2/scheduler_test.go (about) 1 package v2 2 3 import ( 4 "fmt" 5 "math" 6 "sort" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 "github.com/vipernet-xyz/tm/p2p" 14 "github.com/vipernet-xyz/tm/state" 15 "github.com/vipernet-xyz/tm/types" 16 ) 17 18 type scTestParams struct { 19 peers map[string]*scPeer 20 initHeight int64 21 height int64 22 allB []int64 23 pending map[int64]p2p.ID 24 pendingTime map[int64]time.Time 25 received map[int64]p2p.ID 26 peerTimeout time.Duration 27 minRecvRate int64 28 targetPending int 29 startTime time.Time 30 syncTimeout time.Duration 31 } 32 33 func verifyScheduler(sc *scheduler) { 34 missing := 0 35 if sc.maxHeight() >= sc.height { 36 missing = int(math.Min(float64(sc.targetPending), float64(sc.maxHeight()-sc.height+1))) 37 } 38 if len(sc.blockStates) != missing { 39 panic(fmt.Sprintf("scheduler block length %d different than target %d", len(sc.blockStates), missing)) 40 } 41 } 42 43 func newTestScheduler(params scTestParams) *scheduler { 44 peers := make(map[p2p.ID]*scPeer) 45 var maxHeight int64 46 47 initHeight := params.initHeight 48 if initHeight == 0 { 49 initHeight = 1 50 } 51 sc := newScheduler(initHeight, params.startTime) 52 if params.height != 0 { 53 sc.height = params.height 54 } 55 56 for id, peer := range params.peers { 57 peer.peerID = p2p.ID(id) 58 peers[p2p.ID(id)] = peer 59 if maxHeight < peer.height { 60 maxHeight = peer.height 61 } 62 } 63 for _, h := range params.allB { 64 sc.blockStates[h] = blockStateNew 65 } 66 for h, pid := range params.pending { 67 sc.blockStates[h] = blockStatePending 68 sc.pendingBlocks[h] = pid 69 } 70 for h, tm := range params.pendingTime { 71 sc.pendingTime[h] = tm 72 } 73 for h, pid := range params.received { 74 sc.blockStates[h] = blockStateReceived 75 sc.receivedBlocks[h] = pid 76 } 77 78 sc.peers = peers 79 sc.peerTimeout = params.peerTimeout 80 if params.syncTimeout == 0 { 81 sc.syncTimeout = 10 * time.Second 82 } else { 83 sc.syncTimeout = params.syncTimeout 84 } 85 86 if params.targetPending == 0 { 87 sc.targetPending = 10 88 } else { 89 sc.targetPending = params.targetPending 90 } 91 92 sc.minRecvRate = params.minRecvRate 93 94 verifyScheduler(sc) 95 96 return sc 97 } 98 99 func TestScInit(t *testing.T) { 100 var ( 101 initHeight int64 = 5 102 sc = newScheduler(initHeight, time.Now()) 103 ) 104 assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(initHeight-1)) 105 assert.Equal(t, blockStateUnknown, sc.getStateAtHeight(initHeight)) 106 assert.Equal(t, blockStateUnknown, sc.getStateAtHeight(initHeight+1)) 107 } 108 109 func TestScMaxHeights(t *testing.T) { 110 111 tests := []struct { 112 name string 113 sc scheduler 114 wantMax int64 115 }{ 116 { 117 name: "no peers", 118 sc: scheduler{height: 11}, 119 wantMax: 10, 120 }, 121 { 122 name: "one ready peer", 123 sc: scheduler{ 124 height: 3, 125 peers: map[p2p.ID]*scPeer{"P1": {height: 6, state: peerStateReady}}, 126 }, 127 wantMax: 6, 128 }, 129 { 130 name: "ready and removed peers", 131 sc: scheduler{ 132 height: 1, 133 peers: map[p2p.ID]*scPeer{ 134 "P1": {height: 4, state: peerStateReady}, 135 "P2": {height: 10, state: peerStateRemoved}}, 136 }, 137 wantMax: 4, 138 }, 139 { 140 name: "removed peers", 141 sc: scheduler{ 142 height: 1, 143 peers: map[p2p.ID]*scPeer{ 144 "P1": {height: 4, state: peerStateRemoved}, 145 "P2": {height: 10, state: peerStateRemoved}}, 146 }, 147 wantMax: 0, 148 }, 149 { 150 name: "new peers", 151 sc: scheduler{ 152 height: 1, 153 peers: map[p2p.ID]*scPeer{ 154 "P1": {base: -1, height: -1, state: peerStateNew}, 155 "P2": {base: -1, height: -1, state: peerStateNew}}, 156 }, 157 wantMax: 0, 158 }, 159 { 160 name: "mixed peers", 161 sc: scheduler{ 162 height: 1, 163 peers: map[p2p.ID]*scPeer{ 164 "P1": {height: -1, state: peerStateNew}, 165 "P2": {height: 10, state: peerStateReady}, 166 "P3": {height: 20, state: peerStateRemoved}, 167 "P4": {height: 22, state: peerStateReady}, 168 }, 169 }, 170 wantMax: 22, 171 }, 172 } 173 174 for _, tt := range tests { 175 tt := tt 176 t.Run(tt.name, func(t *testing.T) { 177 // maxHeight() should not mutate the scheduler 178 wantSc := tt.sc 179 180 resMax := tt.sc.maxHeight() 181 assert.Equal(t, tt.wantMax, resMax) 182 assert.Equal(t, wantSc, tt.sc) 183 }) 184 } 185 } 186 187 func TestScEnsurePeer(t *testing.T) { 188 189 type args struct { 190 peerID p2p.ID 191 } 192 tests := []struct { 193 name string 194 fields scTestParams 195 args args 196 wantFields scTestParams 197 }{ 198 { 199 name: "add first peer", 200 fields: scTestParams{}, 201 args: args{peerID: "P1"}, 202 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {base: -1, height: -1, state: peerStateNew}}}, 203 }, 204 { 205 name: "add second peer", 206 fields: scTestParams{peers: map[string]*scPeer{"P1": {base: -1, height: -1, state: peerStateNew}}}, 207 args: args{peerID: "P2"}, 208 wantFields: scTestParams{peers: map[string]*scPeer{ 209 "P1": {base: -1, height: -1, state: peerStateNew}, 210 "P2": {base: -1, height: -1, state: peerStateNew}}}, 211 }, 212 { 213 name: "add duplicate peer is fine", 214 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}}, 215 args: args{peerID: "P1"}, 216 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}}, 217 }, 218 { 219 name: "add duplicate peer with existing peer in Ready state is noop", 220 fields: scTestParams{ 221 peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 3}}, 222 allB: []int64{1, 2, 3}, 223 }, 224 args: args{peerID: "P1"}, 225 wantFields: scTestParams{ 226 peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 3}}, 227 allB: []int64{1, 2, 3}, 228 }, 229 }, 230 } 231 232 for _, tt := range tests { 233 tt := tt 234 t.Run(tt.name, func(t *testing.T) { 235 sc := newTestScheduler(tt.fields) 236 sc.ensurePeer(tt.args.peerID) 237 wantSc := newTestScheduler(tt.wantFields) 238 assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers) 239 }) 240 } 241 } 242 243 func TestScTouchPeer(t *testing.T) { 244 now := time.Now() 245 246 type args struct { 247 peerID p2p.ID 248 time time.Time 249 } 250 251 tests := []struct { 252 name string 253 fields scTestParams 254 args args 255 wantFields scTestParams 256 wantErr bool 257 }{ 258 { 259 name: "attempt to touch non existing peer", 260 fields: scTestParams{ 261 peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 5}}, 262 allB: []int64{1, 2, 3, 4, 5}, 263 }, 264 args: args{peerID: "P2", time: now}, 265 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, height: 5}}, 266 allB: []int64{1, 2, 3, 4, 5}, 267 }, 268 wantErr: true, 269 }, 270 { 271 name: "attempt to touch peer in state New", 272 fields: scTestParams{peers: map[string]*scPeer{"P1": {}}}, 273 args: args{peerID: "P1", time: now}, 274 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {}}}, 275 wantErr: true, 276 }, 277 { 278 name: "attempt to touch peer in state Removed", 279 fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved}, "P2": {state: peerStateReady}}}, 280 args: args{peerID: "P1", time: now}, 281 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved}, "P2": {state: peerStateReady}}}, 282 wantErr: true, 283 }, 284 { 285 name: "touch peer in state Ready", 286 fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastTouched: now}}}, 287 args: args{peerID: "P1", time: now.Add(3 * time.Second)}, 288 wantFields: scTestParams{peers: map[string]*scPeer{ 289 "P1": {state: peerStateReady, lastTouched: now.Add(3 * time.Second)}}}, 290 }, 291 } 292 293 for _, tt := range tests { 294 tt := tt 295 t.Run(tt.name, func(t *testing.T) { 296 sc := newTestScheduler(tt.fields) 297 if err := sc.touchPeer(tt.args.peerID, tt.args.time); (err != nil) != tt.wantErr { 298 t.Errorf("touchPeer() wantErr %v, error = %v", tt.wantErr, err) 299 } 300 wantSc := newTestScheduler(tt.wantFields) 301 assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers) 302 }) 303 } 304 } 305 306 func TestScPrunablePeers(t *testing.T) { 307 now := time.Now() 308 309 type args struct { 310 threshold time.Duration 311 time time.Time 312 minSpeed int64 313 } 314 315 tests := []struct { 316 name string 317 fields scTestParams 318 args args 319 wantResult []p2p.ID 320 }{ 321 { 322 name: "no peers", 323 fields: scTestParams{peers: map[string]*scPeer{}}, 324 args: args{threshold: time.Second, time: now.Add(time.Second + time.Millisecond), minSpeed: 100}, 325 wantResult: []p2p.ID{}, 326 }, 327 { 328 name: "mixed peers", 329 fields: scTestParams{peers: map[string]*scPeer{ 330 // X - removed, active, fast 331 "P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101}, 332 // X - ready, active, fast 333 "P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101}, 334 // X - removed, active, equal 335 "P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100}, 336 // V - ready, inactive, equal 337 "P4": {state: peerStateReady, lastTouched: now, lastRate: 100}, 338 // V - ready, inactive, slow 339 "P5": {state: peerStateReady, lastTouched: now, lastRate: 99}, 340 // V - ready, active, slow 341 "P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90}, 342 }}, 343 args: args{threshold: time.Second, time: now.Add(time.Second + time.Millisecond), minSpeed: 100}, 344 wantResult: []p2p.ID{"P4", "P5", "P6"}, 345 }, 346 } 347 348 for _, tt := range tests { 349 tt := tt 350 t.Run(tt.name, func(t *testing.T) { 351 sc := newTestScheduler(tt.fields) 352 // peersSlowerThan should not mutate the scheduler 353 wantSc := sc 354 res := sc.prunablePeers(tt.args.threshold, tt.args.minSpeed, tt.args.time) 355 assert.Equal(t, tt.wantResult, res) 356 assert.Equal(t, wantSc, sc) 357 }) 358 } 359 } 360 361 func TestScRemovePeer(t *testing.T) { 362 363 type args struct { 364 peerID p2p.ID 365 } 366 tests := []struct { 367 name string 368 fields scTestParams 369 args args 370 wantFields scTestParams 371 wantErr bool 372 }{ 373 { 374 name: "remove non existing peer", 375 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}}, 376 args: args{peerID: "P2"}, 377 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}}, 378 }, 379 { 380 name: "remove single New peer", 381 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}}, 382 args: args{peerID: "P1"}, 383 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateRemoved}}}, 384 }, 385 { 386 name: "remove one of two New peers", 387 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}, "P2": {height: -1}}}, 388 args: args{peerID: "P1"}, 389 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateRemoved}, "P2": {height: -1}}}, 390 }, 391 { 392 name: "remove one Ready peer, all peers removed", 393 fields: scTestParams{ 394 peers: map[string]*scPeer{ 395 "P1": {height: 10, state: peerStateRemoved}, 396 "P2": {height: 5, state: peerStateReady}}, 397 allB: []int64{1, 2, 3, 4, 5}, 398 }, 399 args: args{peerID: "P2"}, 400 wantFields: scTestParams{peers: map[string]*scPeer{ 401 "P1": {height: 10, state: peerStateRemoved}, 402 "P2": {height: 5, state: peerStateRemoved}}, 403 }, 404 }, 405 { 406 name: "attempt to remove already removed peer", 407 fields: scTestParams{ 408 height: 8, 409 peers: map[string]*scPeer{ 410 "P1": {height: 10, state: peerStateRemoved}, 411 "P2": {height: 11, state: peerStateReady}}, 412 allB: []int64{8, 9, 10, 11}, 413 }, 414 args: args{peerID: "P1"}, 415 wantFields: scTestParams{ 416 height: 8, 417 peers: map[string]*scPeer{ 418 "P1": {height: 10, state: peerStateRemoved}, 419 "P2": {height: 11, state: peerStateReady}}, 420 allB: []int64{8, 9, 10, 11}}, 421 }, 422 { 423 name: "remove Ready peer with blocks requested", 424 fields: scTestParams{ 425 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 426 allB: []int64{1, 2, 3}, 427 pending: map[int64]p2p.ID{1: "P1"}, 428 }, 429 args: args{peerID: "P1"}, 430 wantFields: scTestParams{ 431 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateRemoved}}, 432 allB: []int64{}, 433 pending: map[int64]p2p.ID{}, 434 }, 435 }, 436 { 437 name: "remove Ready peer with blocks received", 438 fields: scTestParams{ 439 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 440 allB: []int64{1, 2, 3}, 441 received: map[int64]p2p.ID{1: "P1"}, 442 }, 443 args: args{peerID: "P1"}, 444 wantFields: scTestParams{ 445 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateRemoved}}, 446 allB: []int64{}, 447 received: map[int64]p2p.ID{}, 448 }, 449 }, 450 { 451 name: "remove Ready peer with blocks received and requested (not yet received)", 452 fields: scTestParams{ 453 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 454 allB: []int64{1, 2, 3, 4}, 455 pending: map[int64]p2p.ID{1: "P1", 3: "P1"}, 456 received: map[int64]p2p.ID{2: "P1", 4: "P1"}, 457 }, 458 args: args{peerID: "P1"}, 459 wantFields: scTestParams{ 460 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}, 461 allB: []int64{}, 462 pending: map[int64]p2p.ID{}, 463 received: map[int64]p2p.ID{}, 464 }, 465 }, 466 { 467 name: "remove Ready peer from multiple peers set, with blocks received and requested (not yet received)", 468 fields: scTestParams{ 469 peers: map[string]*scPeer{ 470 "P1": {height: 6, state: peerStateReady}, 471 "P2": {height: 6, state: peerStateReady}, 472 }, 473 allB: []int64{1, 2, 3, 4, 5, 6}, 474 pending: map[int64]p2p.ID{1: "P1", 3: "P2", 6: "P1"}, 475 received: map[int64]p2p.ID{2: "P1", 4: "P2", 5: "P2"}, 476 }, 477 args: args{peerID: "P1"}, 478 wantFields: scTestParams{ 479 peers: map[string]*scPeer{ 480 "P1": {height: 6, state: peerStateRemoved}, 481 "P2": {height: 6, state: peerStateReady}, 482 }, 483 allB: []int64{1, 2, 3, 4, 5, 6}, 484 pending: map[int64]p2p.ID{3: "P2"}, 485 received: map[int64]p2p.ID{4: "P2", 5: "P2"}, 486 }, 487 }, 488 } 489 490 for _, tt := range tests { 491 tt := tt 492 t.Run(tt.name, func(t *testing.T) { 493 sc := newTestScheduler(tt.fields) 494 sc.removePeer(tt.args.peerID) 495 wantSc := newTestScheduler(tt.wantFields) 496 assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers) 497 }) 498 } 499 } 500 501 func TestScSetPeerRange(t *testing.T) { 502 503 type args struct { 504 peerID p2p.ID 505 base int64 506 height int64 507 } 508 tests := []struct { 509 name string 510 fields scTestParams 511 args args 512 wantFields scTestParams 513 wantErr bool 514 }{ 515 { 516 name: "change height of non existing peer", 517 fields: scTestParams{ 518 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 519 allB: []int64{1, 2}}, 520 args: args{peerID: "P2", height: 4}, 521 wantFields: scTestParams{ 522 peers: map[string]*scPeer{ 523 "P1": {height: 2, state: peerStateReady}, 524 "P2": {height: 4, state: peerStateReady}, 525 }, 526 allB: []int64{1, 2, 3, 4}}, 527 }, 528 { 529 name: "increase height of removed peer", 530 fields: scTestParams{ 531 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, 532 args: args{peerID: "P1", height: 4}, 533 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, 534 }, 535 { 536 name: "decrease height of single peer", 537 fields: scTestParams{ 538 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 539 allB: []int64{1, 2, 3, 4}}, 540 args: args{peerID: "P1", height: 2}, 541 wantFields: scTestParams{ 542 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}, 543 allB: []int64{}}, 544 wantErr: true, 545 }, 546 { 547 name: "increase height of single peer", 548 fields: scTestParams{ 549 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 550 allB: []int64{1, 2}}, 551 args: args{peerID: "P1", height: 4}, 552 wantFields: scTestParams{ 553 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 554 allB: []int64{1, 2, 3, 4}}, 555 }, 556 { 557 name: "noop height change of single peer", 558 fields: scTestParams{ 559 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 560 allB: []int64{1, 2, 3, 4}}, 561 args: args{peerID: "P1", height: 4}, 562 wantFields: scTestParams{ 563 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 564 allB: []int64{1, 2, 3, 4}}, 565 }, 566 { 567 name: "add peer with huge height 10**10 ", 568 fields: scTestParams{ 569 peers: map[string]*scPeer{"P2": {height: -1, state: peerStateNew}}, 570 targetPending: 4, 571 }, 572 args: args{peerID: "P2", height: 10000000000}, 573 wantFields: scTestParams{ 574 targetPending: 4, 575 peers: map[string]*scPeer{"P2": {height: 10000000000, state: peerStateReady}}, 576 allB: []int64{1, 2, 3, 4}}, 577 }, 578 { 579 name: "add peer with base > height should error", 580 fields: scTestParams{ 581 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 582 allB: []int64{1, 2, 3, 4}}, 583 args: args{peerID: "P1", base: 6, height: 5}, 584 wantFields: scTestParams{ 585 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}}, 586 wantErr: true, 587 }, 588 { 589 name: "add peer with base == height is fine", 590 fields: scTestParams{ 591 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateNew}}, 592 targetPending: 4, 593 }, 594 args: args{peerID: "P1", base: 6, height: 6}, 595 wantFields: scTestParams{ 596 targetPending: 4, 597 peers: map[string]*scPeer{"P1": {base: 6, height: 6, state: peerStateReady}}, 598 allB: []int64{1, 2, 3, 4}}, 599 }, 600 } 601 602 for _, tt := range tests { 603 tt := tt 604 t.Run(tt.name, func(t *testing.T) { 605 sc := newTestScheduler(tt.fields) 606 err := sc.setPeerRange(tt.args.peerID, tt.args.base, tt.args.height) 607 if (err != nil) != tt.wantErr { 608 t.Errorf("setPeerHeight() wantErr %v, error = %v", tt.wantErr, err) 609 } 610 wantSc := newTestScheduler(tt.wantFields) 611 assert.Equal(t, wantSc, sc, "wanted peers %v, got %v", wantSc.peers, sc.peers) 612 }) 613 } 614 } 615 616 func TestScGetPeersWithHeight(t *testing.T) { 617 618 type args struct { 619 height int64 620 } 621 tests := []struct { 622 name string 623 fields scTestParams 624 args args 625 wantResult []p2p.ID 626 }{ 627 { 628 name: "no peers", 629 fields: scTestParams{peers: map[string]*scPeer{}}, 630 args: args{height: 10}, 631 wantResult: []p2p.ID{}, 632 }, 633 { 634 name: "only new peers", 635 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}}, 636 args: args{height: 10}, 637 wantResult: []p2p.ID{}, 638 }, 639 { 640 name: "only Removed peers", 641 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}}, 642 args: args{height: 2}, 643 wantResult: []p2p.ID{}, 644 }, 645 { 646 name: "one Ready shorter peer", 647 fields: scTestParams{ 648 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 649 allB: []int64{1, 2, 3, 4}, 650 }, 651 args: args{height: 5}, 652 wantResult: []p2p.ID{}, 653 }, 654 { 655 name: "one Ready equal peer", 656 fields: scTestParams{ 657 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 658 allB: []int64{1, 2, 3, 4}, 659 }, 660 args: args{height: 4}, 661 wantResult: []p2p.ID{"P1"}, 662 }, 663 { 664 name: "one Ready higher peer", 665 fields: scTestParams{ 666 targetPending: 4, 667 peers: map[string]*scPeer{"P1": {height: 20, state: peerStateReady}}, 668 allB: []int64{1, 2, 3, 4}, 669 }, 670 args: args{height: 4}, 671 wantResult: []p2p.ID{"P1"}, 672 }, 673 { 674 name: "one Ready higher peer at base", 675 fields: scTestParams{ 676 targetPending: 4, 677 peers: map[string]*scPeer{"P1": {base: 4, height: 20, state: peerStateReady}}, 678 allB: []int64{1, 2, 3, 4}, 679 }, 680 args: args{height: 4}, 681 wantResult: []p2p.ID{"P1"}, 682 }, 683 { 684 name: "one Ready higher peer with higher base", 685 fields: scTestParams{ 686 targetPending: 4, 687 peers: map[string]*scPeer{"P1": {base: 10, height: 20, state: peerStateReady}}, 688 allB: []int64{1, 2, 3, 4}, 689 }, 690 args: args{height: 4}, 691 wantResult: []p2p.ID{}, 692 }, 693 { 694 name: "multiple mixed peers", 695 fields: scTestParams{ 696 height: 8, 697 peers: map[string]*scPeer{ 698 "P1": {height: -1, state: peerStateNew}, 699 "P2": {height: 10, state: peerStateReady}, 700 "P3": {height: 5, state: peerStateReady}, 701 "P4": {height: 20, state: peerStateRemoved}, 702 "P5": {height: 11, state: peerStateReady}}, 703 allB: []int64{8, 9, 10, 11}, 704 }, 705 args: args{height: 8}, 706 wantResult: []p2p.ID{"P2", "P5"}, 707 }, 708 } 709 710 for _, tt := range tests { 711 tt := tt 712 t.Run(tt.name, func(t *testing.T) { 713 sc := newTestScheduler(tt.fields) 714 // getPeersWithHeight should not mutate the scheduler 715 wantSc := sc 716 res := sc.getPeersWithHeight(tt.args.height) 717 sort.Sort(PeerByID(res)) 718 assert.Equal(t, tt.wantResult, res) 719 assert.Equal(t, wantSc, sc) 720 }) 721 } 722 } 723 724 func TestScMarkPending(t *testing.T) { 725 now := time.Now() 726 727 type args struct { 728 peerID p2p.ID 729 height int64 730 tm time.Time 731 } 732 tests := []struct { 733 name string 734 fields scTestParams 735 args args 736 wantFields scTestParams 737 wantErr bool 738 }{ 739 { 740 name: "attempt mark pending an unknown block above height", 741 fields: scTestParams{ 742 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 743 allB: []int64{1, 2}}, 744 args: args{peerID: "P1", height: 3, tm: now}, 745 wantFields: scTestParams{ 746 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 747 allB: []int64{1, 2}}, 748 wantErr: true, 749 }, 750 { 751 name: "attempt mark pending an unknown block below base", 752 fields: scTestParams{ 753 peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}}, 754 allB: []int64{1, 2, 3, 4, 5, 6}}, 755 args: args{peerID: "P1", height: 3, tm: now}, 756 wantFields: scTestParams{ 757 peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}}, 758 allB: []int64{1, 2, 3, 4, 5, 6}}, 759 wantErr: true, 760 }, 761 { 762 name: "attempt mark pending from non existing peer", 763 fields: scTestParams{ 764 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 765 allB: []int64{1, 2}}, 766 args: args{peerID: "P2", height: 1, tm: now}, 767 wantFields: scTestParams{ 768 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 769 allB: []int64{1, 2}}, 770 wantErr: true, 771 }, 772 { 773 name: "mark pending from Removed peer", 774 fields: scTestParams{ 775 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, 776 args: args{peerID: "P1", height: 1, tm: now}, 777 wantFields: scTestParams{ 778 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, 779 wantErr: true, 780 }, 781 { 782 name: "mark pending from New peer", 783 fields: scTestParams{ 784 peers: map[string]*scPeer{ 785 "P1": {height: 4, state: peerStateReady}, 786 "P2": {height: 4, state: peerStateNew}, 787 }, 788 allB: []int64{1, 2, 3, 4}, 789 }, 790 args: args{peerID: "P2", height: 2, tm: now}, 791 wantFields: scTestParams{ 792 peers: map[string]*scPeer{ 793 "P1": {height: 4, state: peerStateReady}, 794 "P2": {height: 4, state: peerStateNew}, 795 }, 796 allB: []int64{1, 2, 3, 4}, 797 }, 798 wantErr: true, 799 }, 800 { 801 name: "mark pending from short peer", 802 fields: scTestParams{ 803 peers: map[string]*scPeer{ 804 "P1": {height: 4, state: peerStateReady}, 805 "P2": {height: 2, state: peerStateReady}, 806 }, 807 allB: []int64{1, 2, 3, 4}, 808 }, 809 args: args{peerID: "P2", height: 3, tm: now}, 810 wantFields: scTestParams{ 811 peers: map[string]*scPeer{ 812 "P1": {height: 4, state: peerStateReady}, 813 "P2": {height: 2, state: peerStateReady}, 814 }, 815 allB: []int64{1, 2, 3, 4}, 816 }, 817 wantErr: true, 818 }, 819 { 820 name: "mark pending all good", 821 fields: scTestParams{ 822 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 823 allB: []int64{1, 2}, 824 pending: map[int64]p2p.ID{1: "P1"}, 825 pendingTime: map[int64]time.Time{1: now}, 826 }, 827 args: args{peerID: "P1", height: 2, tm: now.Add(time.Millisecond)}, 828 wantFields: scTestParams{ 829 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 830 allB: []int64{1, 2}, 831 pending: map[int64]p2p.ID{1: "P1", 2: "P1"}, 832 pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Millisecond)}, 833 }, 834 }, 835 } 836 837 for _, tt := range tests { 838 tt := tt 839 t.Run(tt.name, func(t *testing.T) { 840 sc := newTestScheduler(tt.fields) 841 if err := sc.markPending(tt.args.peerID, tt.args.height, tt.args.tm); (err != nil) != tt.wantErr { 842 t.Errorf("markPending() wantErr %v, error = %v", tt.wantErr, err) 843 } 844 wantSc := newTestScheduler(tt.wantFields) 845 assert.Equal(t, wantSc, sc) 846 }) 847 } 848 } 849 850 func TestScMarkReceived(t *testing.T) { 851 now := time.Now() 852 853 type args struct { 854 peerID p2p.ID 855 height int64 856 size int 857 tm time.Time 858 } 859 tests := []struct { 860 name string 861 fields scTestParams 862 args args 863 wantFields scTestParams 864 wantErr bool 865 }{ 866 { 867 name: "received from non existing peer", 868 fields: scTestParams{ 869 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 870 allB: []int64{1, 2}}, 871 args: args{peerID: "P2", height: 1, size: 1000, tm: now}, 872 wantFields: scTestParams{ 873 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 874 allB: []int64{1, 2}}, 875 wantErr: true, 876 }, 877 { 878 name: "received from removed peer", 879 fields: scTestParams{ 880 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, 881 args: args{peerID: "P1", height: 1, size: 1000, tm: now}, 882 wantFields: scTestParams{ 883 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, 884 wantErr: true, 885 }, 886 { 887 name: "received from unsolicited peer", 888 fields: scTestParams{ 889 peers: map[string]*scPeer{ 890 "P1": {height: 4, state: peerStateReady}, 891 "P2": {height: 4, state: peerStateReady}, 892 }, 893 allB: []int64{1, 2, 3, 4}, 894 pending: map[int64]p2p.ID{1: "P1", 2: "P2", 3: "P2", 4: "P1"}, 895 }, 896 args: args{peerID: "P1", height: 2, size: 1000, tm: now}, 897 wantFields: scTestParams{ 898 peers: map[string]*scPeer{ 899 "P1": {height: 4, state: peerStateReady}, 900 "P2": {height: 4, state: peerStateReady}, 901 }, 902 allB: []int64{1, 2, 3, 4}, 903 pending: map[int64]p2p.ID{1: "P1", 2: "P2", 3: "P2", 4: "P1"}, 904 }, 905 wantErr: true, 906 }, 907 { 908 name: "received but blockRequest not sent", 909 fields: scTestParams{ 910 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 911 allB: []int64{1, 2, 3, 4}, 912 pending: map[int64]p2p.ID{}, 913 }, 914 args: args{peerID: "P1", height: 2, size: 1000, tm: now}, 915 wantFields: scTestParams{ 916 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 917 allB: []int64{1, 2, 3, 4}, 918 pending: map[int64]p2p.ID{}, 919 }, 920 wantErr: true, 921 }, 922 { 923 name: "received with bad timestamp", 924 fields: scTestParams{ 925 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 926 allB: []int64{1, 2}, 927 pending: map[int64]p2p.ID{1: "P1", 2: "P1"}, 928 pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Second)}, 929 }, 930 args: args{peerID: "P1", height: 2, size: 1000, tm: now}, 931 wantFields: scTestParams{ 932 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 933 allB: []int64{1, 2}, 934 pending: map[int64]p2p.ID{1: "P1", 2: "P1"}, 935 pendingTime: map[int64]time.Time{1: now, 2: now.Add(time.Second)}, 936 }, 937 wantErr: true, 938 }, 939 { 940 name: "received all good", 941 fields: scTestParams{ 942 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 943 allB: []int64{1, 2}, 944 pending: map[int64]p2p.ID{1: "P1", 2: "P1"}, 945 pendingTime: map[int64]time.Time{1: now, 2: now}, 946 }, 947 args: args{peerID: "P1", height: 2, size: 1000, tm: now.Add(time.Millisecond)}, 948 wantFields: scTestParams{ 949 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 950 allB: []int64{1, 2}, 951 pending: map[int64]p2p.ID{1: "P1"}, 952 pendingTime: map[int64]time.Time{1: now}, 953 received: map[int64]p2p.ID{2: "P1"}, 954 }, 955 }, 956 } 957 958 for _, tt := range tests { 959 tt := tt 960 t.Run(tt.name, func(t *testing.T) { 961 sc := newTestScheduler(tt.fields) 962 if err := sc.markReceived( 963 tt.args.peerID, 964 tt.args.height, 965 tt.args.size, 966 now.Add(time.Second)); (err != nil) != tt.wantErr { 967 t.Errorf("markReceived() wantErr %v, error = %v", tt.wantErr, err) 968 } 969 wantSc := newTestScheduler(tt.wantFields) 970 assert.Equal(t, wantSc, sc) 971 }) 972 } 973 } 974 975 func TestScMarkProcessed(t *testing.T) { 976 now := time.Now() 977 978 type args struct { 979 height int64 980 } 981 tests := []struct { 982 name string 983 fields scTestParams 984 args args 985 wantFields scTestParams 986 wantErr bool 987 }{ 988 { 989 name: "processed an unreceived block", 990 fields: scTestParams{ 991 height: 2, 992 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 993 allB: []int64{2}, 994 pending: map[int64]p2p.ID{2: "P1"}, 995 pendingTime: map[int64]time.Time{2: now}, 996 targetPending: 1, 997 }, 998 args: args{height: 2}, 999 wantFields: scTestParams{ 1000 height: 3, 1001 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1002 allB: []int64{3}, 1003 targetPending: 1, 1004 }, 1005 }, 1006 { 1007 name: "mark processed success", 1008 fields: scTestParams{ 1009 height: 1, 1010 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 1011 allB: []int64{1, 2}, 1012 pending: map[int64]p2p.ID{2: "P1"}, 1013 pendingTime: map[int64]time.Time{2: now}, 1014 received: map[int64]p2p.ID{1: "P1"}}, 1015 args: args{height: 1}, 1016 wantFields: scTestParams{ 1017 height: 2, 1018 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 1019 allB: []int64{2}, 1020 pending: map[int64]p2p.ID{2: "P1"}, 1021 pendingTime: map[int64]time.Time{2: now}}, 1022 }, 1023 } 1024 1025 for _, tt := range tests { 1026 tt := tt 1027 t.Run(tt.name, func(t *testing.T) { 1028 sc := newTestScheduler(tt.fields) 1029 oldBlockState := sc.getStateAtHeight(tt.args.height) 1030 if err := sc.markProcessed(tt.args.height); (err != nil) != tt.wantErr { 1031 t.Errorf("markProcessed() wantErr %v, error = %v", tt.wantErr, err) 1032 } 1033 if tt.wantErr { 1034 assert.Equal(t, oldBlockState, sc.getStateAtHeight(tt.args.height)) 1035 } else { 1036 assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(tt.args.height)) 1037 } 1038 wantSc := newTestScheduler(tt.wantFields) 1039 checkSameScheduler(t, wantSc, sc) 1040 }) 1041 } 1042 } 1043 1044 func TestScResetState(t *testing.T) { 1045 tests := []struct { 1046 name string 1047 fields scTestParams 1048 state state.State 1049 wantFields scTestParams 1050 }{ 1051 { 1052 name: "updates height and initHeight", 1053 fields: scTestParams{ 1054 height: 0, 1055 initHeight: 0, 1056 }, 1057 state: state.State{LastBlockHeight: 7}, 1058 wantFields: scTestParams{ 1059 height: 8, 1060 initHeight: 8, 1061 }, 1062 }, 1063 } 1064 1065 for _, tt := range tests { 1066 tt := tt 1067 t.Run(tt.name, func(t *testing.T) { 1068 sc := newTestScheduler(tt.fields) 1069 e, err := sc.handleResetState(bcResetState{state: tt.state}) 1070 require.NoError(t, err) 1071 assert.Equal(t, e, noOp) 1072 wantSc := newTestScheduler(tt.wantFields) 1073 checkSameScheduler(t, wantSc, sc) 1074 }) 1075 } 1076 } 1077 1078 func TestScAllBlocksProcessed(t *testing.T) { 1079 now := time.Now() 1080 1081 tests := []struct { 1082 name string 1083 fields scTestParams 1084 wantResult bool 1085 }{ 1086 { 1087 name: "no blocks, no peers", 1088 fields: scTestParams{}, 1089 wantResult: false, 1090 }, 1091 { 1092 name: "only New blocks", 1093 fields: scTestParams{ 1094 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1095 allB: []int64{1, 2, 3, 4}, 1096 }, 1097 wantResult: false, 1098 }, 1099 { 1100 name: "only Pending blocks", 1101 fields: scTestParams{ 1102 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1103 allB: []int64{1, 2, 3, 4}, 1104 pending: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"}, 1105 pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now}, 1106 }, 1107 wantResult: false, 1108 }, 1109 { 1110 name: "only Received blocks", 1111 fields: scTestParams{ 1112 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1113 allB: []int64{1, 2, 3, 4}, 1114 received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"}, 1115 }, 1116 wantResult: false, 1117 }, 1118 { 1119 name: "only Processed blocks plus highest is received", 1120 fields: scTestParams{ 1121 height: 4, 1122 peers: map[string]*scPeer{ 1123 "P1": {height: 4, state: peerStateReady}}, 1124 allB: []int64{4}, 1125 received: map[int64]p2p.ID{4: "P1"}, 1126 }, 1127 wantResult: true, 1128 }, 1129 { 1130 name: "mixed block states", 1131 fields: scTestParams{ 1132 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1133 allB: []int64{1, 2, 3, 4}, 1134 pending: map[int64]p2p.ID{2: "P1", 4: "P1"}, 1135 pendingTime: map[int64]time.Time{2: now, 4: now}, 1136 }, 1137 wantResult: false, 1138 }, 1139 } 1140 1141 for _, tt := range tests { 1142 tt := tt 1143 t.Run(tt.name, func(t *testing.T) { 1144 sc := newTestScheduler(tt.fields) 1145 // allBlocksProcessed() should not mutate the scheduler 1146 wantSc := sc 1147 res := sc.allBlocksProcessed() 1148 assert.Equal(t, tt.wantResult, res) 1149 checkSameScheduler(t, wantSc, sc) 1150 }) 1151 } 1152 } 1153 1154 func TestScNextHeightToSchedule(t *testing.T) { 1155 now := time.Now() 1156 1157 tests := []struct { 1158 name string 1159 fields scTestParams 1160 wantHeight int64 1161 }{ 1162 { 1163 name: "no blocks", 1164 fields: scTestParams{initHeight: 11, height: 11}, 1165 wantHeight: -1, 1166 }, 1167 { 1168 name: "only New blocks", 1169 fields: scTestParams{ 1170 initHeight: 3, 1171 peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}}, 1172 allB: []int64{3, 4, 5, 6}, 1173 }, 1174 wantHeight: 3, 1175 }, 1176 { 1177 name: "only Pending blocks", 1178 fields: scTestParams{ 1179 initHeight: 1, 1180 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1181 allB: []int64{1, 2, 3, 4}, 1182 pending: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"}, 1183 pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now}, 1184 }, 1185 wantHeight: -1, 1186 }, 1187 { 1188 name: "only Received blocks", 1189 fields: scTestParams{ 1190 initHeight: 1, 1191 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1192 allB: []int64{1, 2, 3, 4}, 1193 received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"}, 1194 }, 1195 wantHeight: -1, 1196 }, 1197 { 1198 name: "only Processed blocks", 1199 fields: scTestParams{ 1200 initHeight: 1, 1201 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1202 allB: []int64{1, 2, 3, 4}, 1203 }, 1204 wantHeight: 1, 1205 }, 1206 { 1207 name: "mixed block states", 1208 fields: scTestParams{ 1209 initHeight: 1, 1210 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1211 allB: []int64{1, 2, 3, 4}, 1212 pending: map[int64]p2p.ID{2: "P1"}, 1213 pendingTime: map[int64]time.Time{2: now}, 1214 }, 1215 wantHeight: 1, 1216 }, 1217 } 1218 1219 for _, tt := range tests { 1220 tt := tt 1221 t.Run(tt.name, func(t *testing.T) { 1222 sc := newTestScheduler(tt.fields) 1223 // nextHeightToSchedule() should not mutate the scheduler 1224 wantSc := sc 1225 1226 resMin := sc.nextHeightToSchedule() 1227 assert.Equal(t, tt.wantHeight, resMin) 1228 checkSameScheduler(t, wantSc, sc) 1229 }) 1230 } 1231 } 1232 1233 func TestScSelectPeer(t *testing.T) { 1234 1235 type args struct { 1236 height int64 1237 } 1238 tests := []struct { 1239 name string 1240 fields scTestParams 1241 args args 1242 wantResult p2p.ID 1243 wantError bool 1244 }{ 1245 { 1246 name: "no peers", 1247 fields: scTestParams{peers: map[string]*scPeer{}}, 1248 args: args{height: 10}, 1249 wantResult: "", 1250 wantError: true, 1251 }, 1252 { 1253 name: "only new peers", 1254 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}}, 1255 args: args{height: 10}, 1256 wantResult: "", 1257 wantError: true, 1258 }, 1259 { 1260 name: "only Removed peers", 1261 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}}, 1262 args: args{height: 2}, 1263 wantResult: "", 1264 wantError: true, 1265 }, 1266 { 1267 name: "one Ready shorter peer", 1268 fields: scTestParams{ 1269 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1270 allB: []int64{1, 2, 3, 4}, 1271 }, 1272 args: args{height: 5}, 1273 wantResult: "", 1274 wantError: true, 1275 }, 1276 { 1277 name: "one Ready equal peer", 1278 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1279 allB: []int64{1, 2, 3, 4}, 1280 }, 1281 args: args{height: 4}, 1282 wantResult: "P1", 1283 }, 1284 { 1285 name: "one Ready higher peer", 1286 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}}, 1287 allB: []int64{1, 2, 3, 4, 5, 6}, 1288 }, 1289 args: args{height: 4}, 1290 wantResult: "P1", 1291 }, 1292 { 1293 name: "one Ready higher peer with higher base", 1294 fields: scTestParams{ 1295 peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}}, 1296 allB: []int64{1, 2, 3, 4, 5, 6}, 1297 }, 1298 args: args{height: 3}, 1299 wantResult: "", 1300 wantError: true, 1301 }, 1302 { 1303 name: "many Ready higher peers with different number of pending requests", 1304 fields: scTestParams{ 1305 height: 4, 1306 peers: map[string]*scPeer{ 1307 "P1": {height: 8, state: peerStateReady}, 1308 "P2": {height: 9, state: peerStateReady}}, 1309 allB: []int64{4, 5, 6, 7, 8, 9}, 1310 pending: map[int64]p2p.ID{ 1311 4: "P1", 6: "P1", 1312 5: "P2", 1313 }, 1314 }, 1315 args: args{height: 4}, 1316 wantResult: "P2", 1317 }, 1318 { 1319 name: "many Ready higher peers with same number of pending requests", 1320 fields: scTestParams{ 1321 peers: map[string]*scPeer{ 1322 "P2": {height: 20, state: peerStateReady}, 1323 "P1": {height: 15, state: peerStateReady}, 1324 "P3": {height: 15, state: peerStateReady}}, 1325 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 1326 pending: map[int64]p2p.ID{ 1327 1: "P1", 2: "P1", 1328 3: "P3", 4: "P3", 1329 5: "P2", 6: "P2", 1330 }, 1331 }, 1332 args: args{height: 7}, 1333 wantResult: "P1", 1334 }, 1335 } 1336 1337 for _, tt := range tests { 1338 tt := tt 1339 t.Run(tt.name, func(t *testing.T) { 1340 sc := newTestScheduler(tt.fields) 1341 // selectPeer should not mutate the scheduler 1342 wantSc := sc 1343 res, err := sc.selectPeer(tt.args.height) 1344 assert.Equal(t, tt.wantResult, res) 1345 assert.Equal(t, tt.wantError, err != nil) 1346 checkSameScheduler(t, wantSc, sc) 1347 }) 1348 } 1349 } 1350 1351 // makeScBlock makes an empty block. 1352 func makeScBlock(height int64) *types.Block { 1353 return &types.Block{Header: types.Header{Height: height}} 1354 } 1355 1356 // used in place of assert.Equal(t, want, actual) to avoid failures due to 1357 // scheduler.lastAdvanced timestamp inequalities. 1358 func checkSameScheduler(t *testing.T, want *scheduler, actual *scheduler) { 1359 assert.Equal(t, want.initHeight, actual.initHeight) 1360 assert.Equal(t, want.height, actual.height) 1361 assert.Equal(t, want.peers, actual.peers) 1362 assert.Equal(t, want.blockStates, actual.blockStates) 1363 assert.Equal(t, want.pendingBlocks, actual.pendingBlocks) 1364 assert.Equal(t, want.pendingTime, actual.pendingTime) 1365 assert.Equal(t, want.blockStates, actual.blockStates) 1366 assert.Equal(t, want.receivedBlocks, actual.receivedBlocks) 1367 assert.Equal(t, want.blockStates, actual.blockStates) 1368 } 1369 1370 // checkScResults checks scheduler handler test results 1371 func checkScResults(t *testing.T, wantErr bool, err error, wantEvent Event, event Event) { 1372 if (err != nil) != wantErr { 1373 t.Errorf("error = %v, wantErr %v", err, wantErr) 1374 return 1375 } 1376 switch wantEvent := wantEvent.(type) { 1377 case scPeerError: 1378 assert.Equal(t, wantEvent.peerID, event.(scPeerError).peerID) 1379 assert.Equal(t, wantEvent.reason != nil, event.(scPeerError).reason != nil) 1380 case scBlockReceived: 1381 assert.Equal(t, wantEvent.peerID, event.(scBlockReceived).peerID) 1382 assert.Equal(t, wantEvent.block, event.(scBlockReceived).block) 1383 case scSchedulerFail: 1384 assert.Equal(t, wantEvent.reason != nil, event.(scSchedulerFail).reason != nil) 1385 } 1386 } 1387 1388 func TestScHandleBlockResponse(t *testing.T) { 1389 now := time.Now() 1390 block6FromP1 := bcBlockResponse{ 1391 time: now.Add(time.Millisecond), 1392 peerID: p2p.ID("P1"), 1393 size: 100, 1394 block: makeScBlock(6), 1395 } 1396 1397 type args struct { 1398 event bcBlockResponse 1399 } 1400 1401 tests := []struct { 1402 name string 1403 fields scTestParams 1404 args args 1405 wantEvent Event 1406 wantErr bool 1407 }{ 1408 { 1409 name: "empty scheduler", 1410 fields: scTestParams{}, 1411 args: args{event: block6FromP1}, 1412 wantEvent: noOpEvent{}, 1413 }, 1414 { 1415 name: "block from removed peer", 1416 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1417 args: args{event: block6FromP1}, 1418 wantEvent: noOpEvent{}, 1419 }, 1420 { 1421 name: "block we haven't asked for", 1422 fields: scTestParams{ 1423 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1424 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}}, 1425 args: args{event: block6FromP1}, 1426 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1427 }, 1428 { 1429 name: "block from wrong peer", 1430 fields: scTestParams{ 1431 peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}}, 1432 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1433 pending: map[int64]p2p.ID{6: "P2"}, 1434 pendingTime: map[int64]time.Time{6: now}, 1435 }, 1436 args: args{event: block6FromP1}, 1437 wantEvent: noOpEvent{}, 1438 }, 1439 { 1440 name: "block with bad timestamp", 1441 fields: scTestParams{ 1442 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1443 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1444 pending: map[int64]p2p.ID{6: "P1"}, 1445 pendingTime: map[int64]time.Time{6: now.Add(time.Second)}, 1446 }, 1447 args: args{event: block6FromP1}, 1448 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1449 }, 1450 { 1451 name: "good block, accept", 1452 fields: scTestParams{ 1453 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1454 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1455 pending: map[int64]p2p.ID{6: "P1"}, 1456 pendingTime: map[int64]time.Time{6: now}, 1457 }, 1458 args: args{event: block6FromP1}, 1459 wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(6)}, 1460 }, 1461 } 1462 1463 for _, tt := range tests { 1464 tt := tt 1465 t.Run(tt.name, func(t *testing.T) { 1466 sc := newTestScheduler(tt.fields) 1467 event, err := sc.handleBlockResponse(tt.args.event) 1468 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1469 }) 1470 } 1471 } 1472 1473 func TestScHandleNoBlockResponse(t *testing.T) { 1474 now := time.Now() 1475 noBlock6FromP1 := bcNoBlockResponse{ 1476 time: now.Add(time.Millisecond), 1477 peerID: p2p.ID("P1"), 1478 height: 6, 1479 } 1480 1481 tests := []struct { 1482 name string 1483 fields scTestParams 1484 wantEvent Event 1485 wantFields scTestParams 1486 wantErr bool 1487 }{ 1488 { 1489 name: "empty scheduler", 1490 fields: scTestParams{}, 1491 wantEvent: noOpEvent{}, 1492 wantFields: scTestParams{}, 1493 }, 1494 { 1495 name: "noBlock from removed peer", 1496 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1497 wantEvent: noOpEvent{}, 1498 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1499 }, 1500 { 1501 name: "for block we haven't asked for", 1502 fields: scTestParams{ 1503 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1504 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}}, 1505 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1506 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1507 }, 1508 { 1509 name: "noBlock from peer we don't have", 1510 fields: scTestParams{ 1511 peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}}, 1512 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1513 pending: map[int64]p2p.ID{6: "P2"}, 1514 pendingTime: map[int64]time.Time{6: now}, 1515 }, 1516 wantEvent: noOpEvent{}, 1517 wantFields: scTestParams{ 1518 peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}}, 1519 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1520 pending: map[int64]p2p.ID{6: "P2"}, 1521 pendingTime: map[int64]time.Time{6: now}, 1522 }, 1523 }, 1524 { 1525 name: "noBlock from existing peer", 1526 fields: scTestParams{ 1527 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1528 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1529 pending: map[int64]p2p.ID{6: "P1"}, 1530 pendingTime: map[int64]time.Time{6: now}, 1531 }, 1532 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1533 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1534 }, 1535 } 1536 1537 for _, tt := range tests { 1538 tt := tt 1539 t.Run(tt.name, func(t *testing.T) { 1540 sc := newTestScheduler(tt.fields) 1541 event, err := sc.handleNoBlockResponse(noBlock6FromP1) 1542 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1543 wantSc := newTestScheduler(tt.wantFields) 1544 assert.Equal(t, wantSc, sc) 1545 }) 1546 } 1547 } 1548 1549 func TestScHandleBlockProcessed(t *testing.T) { 1550 now := time.Now() 1551 processed6FromP1 := pcBlockProcessed{ 1552 peerID: p2p.ID("P1"), 1553 height: 6, 1554 } 1555 1556 type args struct { 1557 event pcBlockProcessed 1558 } 1559 1560 tests := []struct { 1561 name string 1562 fields scTestParams 1563 args args 1564 wantEvent Event 1565 wantErr bool 1566 }{ 1567 { 1568 name: "empty scheduler", 1569 fields: scTestParams{height: 6}, 1570 args: args{event: processed6FromP1}, 1571 wantEvent: noOpEvent{}, 1572 }, 1573 { 1574 name: "processed block we don't have", 1575 fields: scTestParams{ 1576 initHeight: 6, 1577 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1578 allB: []int64{6, 7, 8}, 1579 pending: map[int64]p2p.ID{6: "P1"}, 1580 pendingTime: map[int64]time.Time{6: now}, 1581 }, 1582 args: args{event: processed6FromP1}, 1583 wantEvent: noOpEvent{}, 1584 }, 1585 { 1586 name: "processed block ok, we processed all blocks", 1587 fields: scTestParams{ 1588 initHeight: 6, 1589 peers: map[string]*scPeer{"P1": {height: 7, state: peerStateReady}}, 1590 allB: []int64{6, 7}, 1591 received: map[int64]p2p.ID{6: "P1", 7: "P1"}, 1592 }, 1593 args: args{event: processed6FromP1}, 1594 wantEvent: scFinishedEv{}, 1595 }, 1596 { 1597 name: "processed block ok, we still have blocks to process", 1598 fields: scTestParams{ 1599 initHeight: 6, 1600 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1601 allB: []int64{6, 7, 8}, 1602 pending: map[int64]p2p.ID{7: "P1", 8: "P1"}, 1603 received: map[int64]p2p.ID{6: "P1"}, 1604 }, 1605 args: args{event: processed6FromP1}, 1606 wantEvent: noOpEvent{}, 1607 }, 1608 } 1609 1610 for _, tt := range tests { 1611 tt := tt 1612 t.Run(tt.name, func(t *testing.T) { 1613 sc := newTestScheduler(tt.fields) 1614 event, err := sc.handleBlockProcessed(tt.args.event) 1615 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1616 }) 1617 } 1618 } 1619 1620 func TestScHandleBlockVerificationFailure(t *testing.T) { 1621 now := time.Now() 1622 1623 type args struct { 1624 event pcBlockVerificationFailure 1625 } 1626 1627 tests := []struct { 1628 name string 1629 fields scTestParams 1630 args args 1631 wantEvent Event 1632 wantErr bool 1633 }{ 1634 { 1635 name: "empty scheduler", 1636 fields: scTestParams{}, 1637 args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}}, 1638 wantEvent: noOpEvent{}, 1639 }, 1640 { 1641 name: "failed block we don't have, single peer is still removed", 1642 fields: scTestParams{ 1643 initHeight: 6, 1644 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1645 allB: []int64{6, 7, 8}, 1646 pending: map[int64]p2p.ID{6: "P1"}, 1647 pendingTime: map[int64]time.Time{6: now}, 1648 }, 1649 args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}}, 1650 wantEvent: scFinishedEv{}, 1651 }, 1652 { 1653 name: "failed block we don't have, one of two peers are removed", 1654 fields: scTestParams{ 1655 initHeight: 6, 1656 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}}, 1657 allB: []int64{6, 7, 8}, 1658 pending: map[int64]p2p.ID{6: "P1"}, 1659 pendingTime: map[int64]time.Time{6: now}, 1660 }, 1661 args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}}, 1662 wantEvent: noOpEvent{}, 1663 }, 1664 { 1665 name: "failed block, all blocks are processed after removal", 1666 fields: scTestParams{ 1667 initHeight: 6, 1668 peers: map[string]*scPeer{"P1": {height: 7, state: peerStateReady}}, 1669 allB: []int64{6, 7}, 1670 received: map[int64]p2p.ID{6: "P1", 7: "P1"}, 1671 }, 1672 args: args{event: pcBlockVerificationFailure{height: 7, firstPeerID: "P1", secondPeerID: "P1"}}, 1673 wantEvent: scFinishedEv{}, 1674 }, 1675 { 1676 name: "failed block, we still have blocks to process", 1677 fields: scTestParams{ 1678 initHeight: 5, 1679 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}}, 1680 allB: []int64{5, 6, 7, 8}, 1681 pending: map[int64]p2p.ID{7: "P1", 8: "P1"}, 1682 received: map[int64]p2p.ID{5: "P1", 6: "P1"}, 1683 }, 1684 args: args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P1"}}, 1685 wantEvent: noOpEvent{}, 1686 }, 1687 { 1688 name: "failed block, H+1 and H+2 delivered by different peers, we still have blocks to process", 1689 fields: scTestParams{ 1690 initHeight: 5, 1691 peers: map[string]*scPeer{ 1692 "P1": {height: 8, state: peerStateReady}, 1693 "P2": {height: 8, state: peerStateReady}, 1694 "P3": {height: 8, state: peerStateReady}, 1695 }, 1696 allB: []int64{5, 6, 7, 8}, 1697 pending: map[int64]p2p.ID{7: "P1", 8: "P1"}, 1698 received: map[int64]p2p.ID{5: "P1", 6: "P1"}, 1699 }, 1700 args: args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P2"}}, 1701 wantEvent: noOpEvent{}, 1702 }, 1703 } 1704 1705 for _, tt := range tests { 1706 tt := tt 1707 t.Run(tt.name, func(t *testing.T) { 1708 sc := newTestScheduler(tt.fields) 1709 event, err := sc.handleBlockProcessError(tt.args.event) 1710 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1711 }) 1712 } 1713 } 1714 1715 func TestScHandleAddNewPeer(t *testing.T) { 1716 addP1 := bcAddNewPeer{ 1717 peerID: p2p.ID("P1"), 1718 } 1719 type args struct { 1720 event bcAddNewPeer 1721 } 1722 1723 tests := []struct { 1724 name string 1725 fields scTestParams 1726 args args 1727 wantEvent Event 1728 wantErr bool 1729 }{ 1730 { 1731 name: "add P1 to empty scheduler", 1732 fields: scTestParams{}, 1733 args: args{event: addP1}, 1734 wantEvent: noOpEvent{}, 1735 }, 1736 { 1737 name: "add duplicate peer", 1738 fields: scTestParams{ 1739 initHeight: 6, 1740 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1741 allB: []int64{6, 7, 8}, 1742 }, 1743 args: args{event: addP1}, 1744 wantEvent: noOpEvent{}, 1745 }, 1746 { 1747 name: "add P1 to non empty scheduler", 1748 fields: scTestParams{ 1749 initHeight: 6, 1750 peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}}, 1751 allB: []int64{6, 7, 8}, 1752 }, 1753 args: args{event: addP1}, 1754 wantEvent: noOpEvent{}, 1755 }, 1756 } 1757 1758 for _, tt := range tests { 1759 tt := tt 1760 t.Run(tt.name, func(t *testing.T) { 1761 sc := newTestScheduler(tt.fields) 1762 event, err := sc.handleAddNewPeer(tt.args.event) 1763 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1764 }) 1765 } 1766 } 1767 1768 func TestScHandleTryPrunePeer(t *testing.T) { 1769 now := time.Now() 1770 1771 pruneEv := rTryPrunePeer{ 1772 time: now.Add(time.Second + time.Millisecond), 1773 } 1774 type args struct { 1775 event rTryPrunePeer 1776 } 1777 1778 tests := []struct { 1779 name string 1780 fields scTestParams 1781 args args 1782 wantEvent Event 1783 wantErr bool 1784 }{ 1785 { 1786 name: "no peers", 1787 fields: scTestParams{}, 1788 args: args{event: pruneEv}, 1789 wantEvent: noOpEvent{}, 1790 }, 1791 { 1792 name: "no prunable peers", 1793 fields: scTestParams{ 1794 minRecvRate: 100, 1795 peers: map[string]*scPeer{ 1796 // X - removed, active, fast 1797 "P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101}, 1798 // X - ready, active, fast 1799 "P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101}, 1800 // X - removed, active, equal 1801 "P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100}}, 1802 peerTimeout: time.Second, 1803 }, 1804 args: args{event: pruneEv}, 1805 wantEvent: noOpEvent{}, 1806 }, 1807 { 1808 name: "mixed peers", 1809 fields: scTestParams{ 1810 minRecvRate: 100, 1811 peers: map[string]*scPeer{ 1812 // X - removed, active, fast 1813 "P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5}, 1814 // X - ready, active, fast 1815 "P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5}, 1816 // X - removed, active, equal 1817 "P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5}, 1818 // V - ready, inactive, equal 1819 "P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7}, 1820 // V - ready, inactive, slow 1821 "P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7}, 1822 // V - ready, active, slow 1823 "P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7}, 1824 }, 1825 allB: []int64{1, 2, 3, 4, 5, 6, 7}, 1826 peerTimeout: time.Second}, 1827 args: args{event: pruneEv}, 1828 wantEvent: scPeersPruned{peers: []p2p.ID{"P4", "P5", "P6"}}, 1829 }, 1830 { 1831 name: "mixed peers, finish after pruning", 1832 fields: scTestParams{ 1833 minRecvRate: 100, 1834 height: 6, 1835 peers: map[string]*scPeer{ 1836 // X - removed, active, fast 1837 "P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5}, 1838 // X - ready, active, fast 1839 "P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5}, 1840 // X - removed, active, equal 1841 "P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5}, 1842 // V - ready, inactive, equal 1843 "P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7}, 1844 // V - ready, inactive, slow 1845 "P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7}, 1846 // V - ready, active, slow 1847 "P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7}, 1848 }, 1849 allB: []int64{6, 7}, 1850 peerTimeout: time.Second}, 1851 args: args{event: pruneEv}, 1852 wantEvent: scFinishedEv{}, 1853 }, 1854 } 1855 1856 for _, tt := range tests { 1857 tt := tt 1858 t.Run(tt.name, func(t *testing.T) { 1859 sc := newTestScheduler(tt.fields) 1860 event, err := sc.handleTryPrunePeer(tt.args.event) 1861 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1862 }) 1863 } 1864 } 1865 1866 func TestScHandleTrySchedule(t *testing.T) { 1867 now := time.Now() 1868 tryEv := rTrySchedule{ 1869 time: now.Add(time.Second + time.Millisecond), 1870 } 1871 1872 type args struct { 1873 event rTrySchedule 1874 } 1875 tests := []struct { 1876 name string 1877 fields scTestParams 1878 args args 1879 wantEvent Event 1880 wantErr bool 1881 }{ 1882 { 1883 name: "no peers", 1884 fields: scTestParams{startTime: now, peers: map[string]*scPeer{}}, 1885 args: args{event: tryEv}, 1886 wantEvent: noOpEvent{}, 1887 }, 1888 { 1889 name: "only new peers", 1890 fields: scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}}, 1891 args: args{event: tryEv}, 1892 wantEvent: noOpEvent{}, 1893 }, 1894 { 1895 name: "only Removed peers", 1896 fields: scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}}, 1897 args: args{event: tryEv}, 1898 wantEvent: noOpEvent{}, 1899 }, 1900 { 1901 name: "one Ready shorter peer", 1902 fields: scTestParams{ 1903 startTime: now, 1904 height: 6, 1905 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}}, 1906 args: args{event: tryEv}, 1907 wantEvent: noOpEvent{}, 1908 }, 1909 { 1910 name: "one Ready equal peer", 1911 fields: scTestParams{ 1912 startTime: now, 1913 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1914 allB: []int64{1, 2, 3, 4}}, 1915 args: args{event: tryEv}, 1916 wantEvent: scBlockRequest{peerID: "P1", height: 1}, 1917 }, 1918 { 1919 name: "many Ready higher peers with different number of pending requests", 1920 fields: scTestParams{ 1921 startTime: now, 1922 peers: map[string]*scPeer{ 1923 "P1": {height: 4, state: peerStateReady}, 1924 "P2": {height: 5, state: peerStateReady}}, 1925 allB: []int64{1, 2, 3, 4, 5}, 1926 pending: map[int64]p2p.ID{ 1927 1: "P1", 2: "P1", 1928 3: "P2", 1929 }, 1930 }, 1931 args: args{event: tryEv}, 1932 wantEvent: scBlockRequest{peerID: "P2", height: 4}, 1933 }, 1934 1935 { 1936 name: "many Ready higher peers with same number of pending requests", 1937 fields: scTestParams{ 1938 startTime: now, 1939 peers: map[string]*scPeer{ 1940 "P2": {height: 8, state: peerStateReady}, 1941 "P1": {height: 8, state: peerStateReady}, 1942 "P3": {height: 8, state: peerStateReady}}, 1943 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1944 pending: map[int64]p2p.ID{ 1945 1: "P1", 2: "P1", 1946 3: "P3", 4: "P3", 1947 5: "P2", 6: "P2", 1948 }, 1949 }, 1950 args: args{event: tryEv}, 1951 wantEvent: scBlockRequest{peerID: "P1", height: 7}, 1952 }, 1953 } 1954 1955 for _, tt := range tests { 1956 tt := tt 1957 t.Run(tt.name, func(t *testing.T) { 1958 sc := newTestScheduler(tt.fields) 1959 event, err := sc.handleTrySchedule(tt.args.event) 1960 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1961 }) 1962 } 1963 } 1964 1965 func TestScHandleStatusResponse(t *testing.T) { 1966 now := time.Now() 1967 statusRespP1Ev := bcStatusResponse{ 1968 time: now.Add(time.Second + time.Millisecond), 1969 peerID: "P1", 1970 height: 6, 1971 } 1972 1973 type args struct { 1974 event bcStatusResponse 1975 } 1976 tests := []struct { 1977 name string 1978 fields scTestParams 1979 args args 1980 wantEvent Event 1981 wantErr bool 1982 }{ 1983 { 1984 name: "change height of non existing peer", 1985 fields: scTestParams{ 1986 peers: map[string]*scPeer{"P2": {height: 2, state: peerStateReady}}, 1987 allB: []int64{1, 2}, 1988 }, 1989 args: args{event: statusRespP1Ev}, 1990 wantEvent: noOpEvent{}, 1991 }, 1992 1993 { 1994 name: "increase height of removed peer", 1995 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, 1996 args: args{event: statusRespP1Ev}, 1997 wantEvent: noOpEvent{}, 1998 }, 1999 2000 { 2001 name: "decrease height of single peer", 2002 fields: scTestParams{ 2003 height: 5, 2004 peers: map[string]*scPeer{"P1": {height: 10, state: peerStateReady}}, 2005 allB: []int64{5, 6, 7, 8, 9, 10}, 2006 }, 2007 args: args{event: statusRespP1Ev}, 2008 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 2009 }, 2010 2011 { 2012 name: "increase height of single peer", 2013 fields: scTestParams{ 2014 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 2015 allB: []int64{1, 2}}, 2016 args: args{event: statusRespP1Ev}, 2017 wantEvent: noOpEvent{}, 2018 }, 2019 { 2020 name: "noop height change of single peer", 2021 fields: scTestParams{ 2022 peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}}, 2023 allB: []int64{1, 2, 3, 4, 5, 6}}, 2024 args: args{event: statusRespP1Ev}, 2025 wantEvent: noOpEvent{}, 2026 }, 2027 } 2028 2029 for _, tt := range tests { 2030 tt := tt 2031 t.Run(tt.name, func(t *testing.T) { 2032 sc := newTestScheduler(tt.fields) 2033 event, err := sc.handleStatusResponse(tt.args.event) 2034 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 2035 }) 2036 } 2037 } 2038 2039 func TestScHandle(t *testing.T) { 2040 now := time.Now() 2041 2042 type unknownEv struct { 2043 priorityNormal 2044 } 2045 2046 t0 := time.Now() 2047 tick := make([]time.Time, 100) 2048 for i := range tick { 2049 tick[i] = t0.Add(time.Duration(i) * time.Millisecond) 2050 } 2051 2052 type args struct { 2053 event Event 2054 } 2055 type scStep struct { 2056 currentSc *scTestParams 2057 args args 2058 wantEvent Event 2059 wantErr bool 2060 wantSc *scTestParams 2061 } 2062 tests := []struct { 2063 name string 2064 steps []scStep 2065 }{ 2066 { 2067 name: "unknown event", 2068 steps: []scStep{ 2069 { // add P1 2070 currentSc: &scTestParams{}, 2071 args: args{event: unknownEv{}}, 2072 wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")}, 2073 wantSc: &scTestParams{}, 2074 }, 2075 }, 2076 }, 2077 { 2078 name: "single peer, sync 3 blocks", 2079 steps: []scStep{ 2080 { // add P1 2081 currentSc: &scTestParams{startTime: now, peers: map[string]*scPeer{}, height: 1}, 2082 args: args{event: bcAddNewPeer{peerID: "P1"}}, 2083 wantEvent: noOpEvent{}, 2084 wantSc: &scTestParams{startTime: now, peers: map[string]*scPeer{ 2085 "P1": {base: -1, height: -1, state: peerStateNew}}, height: 1}, 2086 }, 2087 { // set height of P1 2088 args: args{event: bcStatusResponse{peerID: "P1", time: tick[0], height: 3}}, 2089 wantEvent: noOpEvent{}, 2090 wantSc: &scTestParams{ 2091 startTime: now, 2092 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 2093 allB: []int64{1, 2, 3}, 2094 height: 1, 2095 }, 2096 }, 2097 { // schedule block 1 2098 args: args{event: rTrySchedule{time: tick[1]}}, 2099 wantEvent: scBlockRequest{peerID: "P1", height: 1}, 2100 wantSc: &scTestParams{ 2101 startTime: now, 2102 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 2103 allB: []int64{1, 2, 3}, 2104 pending: map[int64]p2p.ID{1: "P1"}, 2105 pendingTime: map[int64]time.Time{1: tick[1]}, 2106 height: 1, 2107 }, 2108 }, 2109 { // schedule block 2 2110 args: args{event: rTrySchedule{time: tick[2]}}, 2111 wantEvent: scBlockRequest{peerID: "P1", height: 2}, 2112 wantSc: &scTestParams{ 2113 startTime: now, 2114 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 2115 allB: []int64{1, 2, 3}, 2116 pending: map[int64]p2p.ID{1: "P1", 2: "P1"}, 2117 pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2]}, 2118 height: 1, 2119 }, 2120 }, 2121 { // schedule block 3 2122 args: args{event: rTrySchedule{time: tick[3]}}, 2123 wantEvent: scBlockRequest{peerID: "P1", height: 3}, 2124 wantSc: &scTestParams{ 2125 startTime: now, 2126 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 2127 allB: []int64{1, 2, 3}, 2128 pending: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"}, 2129 pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2], 3: tick[3]}, 2130 height: 1, 2131 }, 2132 }, 2133 { // block response 1 2134 args: args{event: bcBlockResponse{peerID: "P1", time: tick[4], size: 100, block: makeScBlock(1)}}, 2135 wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(1)}, 2136 wantSc: &scTestParams{ 2137 startTime: now, 2138 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[4]}}, 2139 allB: []int64{1, 2, 3}, 2140 pending: map[int64]p2p.ID{2: "P1", 3: "P1"}, 2141 pendingTime: map[int64]time.Time{2: tick[2], 3: tick[3]}, 2142 received: map[int64]p2p.ID{1: "P1"}, 2143 height: 1, 2144 }, 2145 }, 2146 { // block response 2 2147 args: args{event: bcBlockResponse{peerID: "P1", time: tick[5], size: 100, block: makeScBlock(2)}}, 2148 wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(2)}, 2149 wantSc: &scTestParams{ 2150 startTime: now, 2151 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[5]}}, 2152 allB: []int64{1, 2, 3}, 2153 pending: map[int64]p2p.ID{3: "P1"}, 2154 pendingTime: map[int64]time.Time{3: tick[3]}, 2155 received: map[int64]p2p.ID{1: "P1", 2: "P1"}, 2156 height: 1, 2157 }, 2158 }, 2159 { // block response 3 2160 args: args{event: bcBlockResponse{peerID: "P1", time: tick[6], size: 100, block: makeScBlock(3)}}, 2161 wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(3)}, 2162 wantSc: &scTestParams{ 2163 startTime: now, 2164 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2165 allB: []int64{1, 2, 3}, 2166 received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"}, 2167 height: 1, 2168 }, 2169 }, 2170 { // processed block 1 2171 args: args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 1}}, 2172 wantEvent: noOpEvent{}, 2173 wantSc: &scTestParams{ 2174 startTime: now, 2175 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2176 allB: []int64{2, 3}, 2177 received: map[int64]p2p.ID{2: "P1", 3: "P1"}, 2178 height: 2, 2179 }, 2180 }, 2181 { // processed block 2 2182 args: args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 2}}, 2183 wantEvent: scFinishedEv{}, 2184 wantSc: &scTestParams{ 2185 startTime: now, 2186 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2187 allB: []int64{3}, 2188 received: map[int64]p2p.ID{3: "P1"}, 2189 height: 3, 2190 }, 2191 }, 2192 }, 2193 }, 2194 { 2195 name: "block verification failure", 2196 steps: []scStep{ 2197 { // failure processing block 1 2198 currentSc: &scTestParams{ 2199 startTime: now, 2200 peers: map[string]*scPeer{ 2201 "P1": {height: 4, state: peerStateReady, lastTouched: tick[6]}, 2202 "P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2203 allB: []int64{1, 2, 3, 4}, 2204 received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"}, 2205 height: 1, 2206 }, 2207 args: args{event: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"}}, 2208 wantEvent: noOpEvent{}, 2209 wantSc: &scTestParams{ 2210 startTime: now, 2211 peers: map[string]*scPeer{ 2212 "P1": {height: 4, state: peerStateRemoved, lastTouched: tick[6]}, 2213 "P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2214 allB: []int64{1, 2, 3}, 2215 received: map[int64]p2p.ID{}, 2216 height: 1, 2217 }, 2218 }, 2219 }, 2220 }, 2221 } 2222 2223 for _, tt := range tests { 2224 tt := tt 2225 t.Run(tt.name, func(t *testing.T) { 2226 var sc *scheduler 2227 for i, step := range tt.steps { 2228 // First step must always initialise the currentState as state. 2229 if step.currentSc != nil { 2230 sc = newTestScheduler(*step.currentSc) 2231 } 2232 if sc == nil { 2233 panic("Bad (initial?) step") 2234 } 2235 2236 nextEvent, err := sc.handle(step.args.event) 2237 wantSc := newTestScheduler(*step.wantSc) 2238 2239 t.Logf("step %d(%v): %s", i, step.args.event, sc) 2240 checkSameScheduler(t, wantSc, sc) 2241 2242 checkScResults(t, step.wantErr, err, step.wantEvent, nextEvent) 2243 2244 // Next step may use the wantedState as their currentState. 2245 sc = newTestScheduler(*step.wantSc) 2246 } 2247 }) 2248 } 2249 }