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