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