github.com/okex/exchain@v1.8.0/libs/tendermint/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/okex/exchain/libs/tendermint/p2p" 13 "github.com/okex/exchain/libs/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 height: 2, 995 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 996 allB: []int64{2}, 997 pending: map[int64]p2p.ID{2: "P1"}, 998 pendingTime: map[int64]time.Time{2: now}, 999 targetPending: 1, 1000 }, 1001 args: args{height: 2}, 1002 wantFields: scTestParams{ 1003 height: 3, 1004 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1005 allB: []int64{3}, 1006 targetPending: 1, 1007 }, 1008 }, 1009 { 1010 name: "mark processed success", 1011 fields: scTestParams{ 1012 height: 1, 1013 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 1014 allB: []int64{1, 2}, 1015 pending: map[int64]p2p.ID{2: "P1"}, 1016 pendingTime: map[int64]time.Time{2: now}, 1017 received: map[int64]p2p.ID{1: "P1"}}, 1018 args: args{height: 1}, 1019 wantFields: scTestParams{ 1020 height: 2, 1021 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 1022 allB: []int64{2}, 1023 pending: map[int64]p2p.ID{2: "P1"}, 1024 pendingTime: map[int64]time.Time{2: now}}, 1025 }, 1026 } 1027 1028 for _, tt := range tests { 1029 tt := tt 1030 t.Run(tt.name, func(t *testing.T) { 1031 sc := newTestScheduler(tt.fields) 1032 oldBlockState := sc.getStateAtHeight(tt.args.height) 1033 if err := sc.markProcessed(tt.args.height); (err != nil) != tt.wantErr { 1034 t.Errorf("markProcessed() wantErr %v, error = %v", tt.wantErr, err) 1035 } 1036 if tt.wantErr { 1037 assert.Equal(t, oldBlockState, sc.getStateAtHeight(tt.args.height)) 1038 } else { 1039 assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(tt.args.height)) 1040 } 1041 wantSc := newTestScheduler(tt.wantFields) 1042 checkSameScheduler(t, wantSc, sc) 1043 }) 1044 } 1045 } 1046 1047 func TestScAllBlocksProcessed(t *testing.T) { 1048 now := time.Now() 1049 1050 tests := []struct { 1051 name string 1052 fields scTestParams 1053 wantResult bool 1054 }{ 1055 { 1056 name: "no blocks, no peers", 1057 fields: scTestParams{}, 1058 wantResult: false, 1059 }, 1060 { 1061 name: "only New blocks", 1062 fields: scTestParams{ 1063 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1064 allB: []int64{1, 2, 3, 4}, 1065 }, 1066 wantResult: false, 1067 }, 1068 { 1069 name: "only Pending blocks", 1070 fields: scTestParams{ 1071 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1072 allB: []int64{1, 2, 3, 4}, 1073 pending: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"}, 1074 pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now}, 1075 }, 1076 wantResult: false, 1077 }, 1078 { 1079 name: "only Received blocks", 1080 fields: scTestParams{ 1081 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1082 allB: []int64{1, 2, 3, 4}, 1083 received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"}, 1084 }, 1085 wantResult: false, 1086 }, 1087 { 1088 name: "only Processed blocks plus highest is received", 1089 fields: scTestParams{ 1090 height: 4, 1091 peers: map[string]*scPeer{ 1092 "P1": {height: 4, state: peerStateReady}}, 1093 allB: []int64{4}, 1094 received: map[int64]p2p.ID{4: "P1"}, 1095 }, 1096 wantResult: true, 1097 }, 1098 { 1099 name: "mixed block states", 1100 fields: scTestParams{ 1101 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1102 allB: []int64{1, 2, 3, 4}, 1103 pending: map[int64]p2p.ID{2: "P1", 4: "P1"}, 1104 pendingTime: map[int64]time.Time{2: now, 4: now}, 1105 }, 1106 wantResult: false, 1107 }, 1108 } 1109 1110 for _, tt := range tests { 1111 tt := tt 1112 t.Run(tt.name, func(t *testing.T) { 1113 sc := newTestScheduler(tt.fields) 1114 // allBlocksProcessed() should not mutate the scheduler 1115 wantSc := sc 1116 res := sc.allBlocksProcessed() 1117 assert.Equal(t, tt.wantResult, res) 1118 checkSameScheduler(t, wantSc, sc) 1119 }) 1120 } 1121 } 1122 1123 func TestScNextHeightToSchedule(t *testing.T) { 1124 now := time.Now() 1125 1126 tests := []struct { 1127 name string 1128 fields scTestParams 1129 wantHeight int64 1130 }{ 1131 { 1132 name: "no blocks", 1133 fields: scTestParams{initHeight: 10, height: 11}, 1134 wantHeight: -1, 1135 }, 1136 { 1137 name: "only New blocks", 1138 fields: scTestParams{ 1139 initHeight: 2, 1140 height: 3, 1141 peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}}, 1142 allB: []int64{3, 4, 5, 6}, 1143 }, 1144 wantHeight: 3, 1145 }, 1146 { 1147 name: "only Pending blocks", 1148 fields: scTestParams{ 1149 height: 1, 1150 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1151 allB: []int64{1, 2, 3, 4}, 1152 pending: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"}, 1153 pendingTime: map[int64]time.Time{1: now, 2: now, 3: now, 4: now}, 1154 }, 1155 wantHeight: -1, 1156 }, 1157 { 1158 name: "only Received blocks", 1159 fields: scTestParams{ 1160 height: 1, 1161 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1162 allB: []int64{1, 2, 3, 4}, 1163 received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1", 4: "P1"}, 1164 }, 1165 wantHeight: -1, 1166 }, 1167 { 1168 name: "only Processed blocks", 1169 fields: scTestParams{ 1170 height: 1, 1171 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1172 allB: []int64{1, 2, 3, 4}, 1173 }, 1174 wantHeight: 1, 1175 }, 1176 { 1177 name: "mixed block states", 1178 fields: scTestParams{ 1179 height: 1, 1180 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1181 allB: []int64{1, 2, 3, 4}, 1182 pending: map[int64]p2p.ID{2: "P1"}, 1183 pendingTime: map[int64]time.Time{2: now}, 1184 }, 1185 wantHeight: 1, 1186 }, 1187 } 1188 1189 for _, tt := range tests { 1190 tt := tt 1191 t.Run(tt.name, func(t *testing.T) { 1192 sc := newTestScheduler(tt.fields) 1193 // nextHeightToSchedule() should not mutate the scheduler 1194 wantSc := sc 1195 1196 resMin := sc.nextHeightToSchedule() 1197 assert.Equal(t, tt.wantHeight, resMin) 1198 checkSameScheduler(t, wantSc, sc) 1199 }) 1200 } 1201 } 1202 1203 func TestScSelectPeer(t *testing.T) { 1204 1205 type args struct { 1206 height int64 1207 } 1208 tests := []struct { 1209 name string 1210 fields scTestParams 1211 args args 1212 wantResult p2p.ID 1213 wantError bool 1214 }{ 1215 { 1216 name: "no peers", 1217 fields: scTestParams{peers: map[string]*scPeer{}}, 1218 args: args{height: 10}, 1219 wantResult: "", 1220 wantError: true, 1221 }, 1222 { 1223 name: "only new peers", 1224 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}}, 1225 args: args{height: 10}, 1226 wantResult: "", 1227 wantError: true, 1228 }, 1229 { 1230 name: "only Removed peers", 1231 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}}, 1232 args: args{height: 2}, 1233 wantResult: "", 1234 wantError: true, 1235 }, 1236 { 1237 name: "one Ready shorter peer", 1238 fields: scTestParams{ 1239 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1240 allB: []int64{1, 2, 3, 4}, 1241 }, 1242 args: args{height: 5}, 1243 wantResult: "", 1244 wantError: true, 1245 }, 1246 { 1247 name: "one Ready equal peer", 1248 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1249 allB: []int64{1, 2, 3, 4}, 1250 }, 1251 args: args{height: 4}, 1252 wantResult: "P1", 1253 }, 1254 { 1255 name: "one Ready higher peer", 1256 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}}, 1257 allB: []int64{1, 2, 3, 4, 5, 6}, 1258 }, 1259 args: args{height: 4}, 1260 wantResult: "P1", 1261 }, 1262 { 1263 name: "one Ready higher peer with higher base", 1264 fields: scTestParams{ 1265 peers: map[string]*scPeer{"P1": {base: 4, height: 6, state: peerStateReady}}, 1266 allB: []int64{1, 2, 3, 4, 5, 6}, 1267 }, 1268 args: args{height: 3}, 1269 wantResult: "", 1270 wantError: true, 1271 }, 1272 { 1273 name: "many Ready higher peers with different number of pending requests", 1274 fields: scTestParams{ 1275 height: 4, 1276 peers: map[string]*scPeer{ 1277 "P1": {height: 8, state: peerStateReady}, 1278 "P2": {height: 9, state: peerStateReady}}, 1279 allB: []int64{4, 5, 6, 7, 8, 9}, 1280 pending: map[int64]p2p.ID{ 1281 4: "P1", 6: "P1", 1282 5: "P2", 1283 }, 1284 }, 1285 args: args{height: 4}, 1286 wantResult: "P2", 1287 }, 1288 { 1289 name: "many Ready higher peers with same number of pending requests", 1290 fields: scTestParams{ 1291 peers: map[string]*scPeer{ 1292 "P2": {height: 20, state: peerStateReady}, 1293 "P1": {height: 15, state: peerStateReady}, 1294 "P3": {height: 15, state: peerStateReady}}, 1295 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 1296 pending: map[int64]p2p.ID{ 1297 1: "P1", 2: "P1", 1298 3: "P3", 4: "P3", 1299 5: "P2", 6: "P2", 1300 }, 1301 }, 1302 args: args{height: 7}, 1303 wantResult: "P1", 1304 }, 1305 } 1306 1307 for _, tt := range tests { 1308 tt := tt 1309 t.Run(tt.name, func(t *testing.T) { 1310 sc := newTestScheduler(tt.fields) 1311 // selectPeer should not mutate the scheduler 1312 wantSc := sc 1313 res, err := sc.selectPeer(tt.args.height) 1314 assert.Equal(t, tt.wantResult, res) 1315 assert.Equal(t, tt.wantError, err != nil) 1316 checkSameScheduler(t, wantSc, sc) 1317 }) 1318 } 1319 } 1320 1321 // makeScBlock makes an empty block. 1322 func makeScBlock(height int64) *types.Block { 1323 return &types.Block{Header: types.Header{Height: height}} 1324 } 1325 1326 // used in place of assert.Equal(t, want, actual) to avoid failures due to 1327 // scheduler.lastAdvanced timestamp inequalities. 1328 func checkSameScheduler(t *testing.T, want *scheduler, actual *scheduler) { 1329 assert.Equal(t, want.initHeight, actual.initHeight) 1330 assert.Equal(t, want.height, actual.height) 1331 assert.Equal(t, want.peers, actual.peers) 1332 assert.Equal(t, want.blockStates, actual.blockStates) 1333 assert.Equal(t, want.pendingBlocks, actual.pendingBlocks) 1334 assert.Equal(t, want.pendingTime, actual.pendingTime) 1335 assert.Equal(t, want.blockStates, actual.blockStates) 1336 assert.Equal(t, want.receivedBlocks, actual.receivedBlocks) 1337 assert.Equal(t, want.blockStates, actual.blockStates) 1338 } 1339 1340 // checkScResults checks scheduler handler test results 1341 func checkScResults(t *testing.T, wantErr bool, err error, wantEvent Event, event Event) { 1342 if (err != nil) != wantErr { 1343 t.Errorf("error = %v, wantErr %v", err, wantErr) 1344 return 1345 } 1346 switch wantEvent := wantEvent.(type) { 1347 case scPeerError: 1348 assert.Equal(t, wantEvent.peerID, event.(scPeerError).peerID) 1349 assert.Equal(t, wantEvent.reason != nil, event.(scPeerError).reason != nil) 1350 case scBlockReceived: 1351 assert.Equal(t, wantEvent.peerID, event.(scBlockReceived).peerID) 1352 assert.Equal(t, wantEvent.block, event.(scBlockReceived).block) 1353 case scSchedulerFail: 1354 assert.Equal(t, wantEvent.reason != nil, event.(scSchedulerFail).reason != nil) 1355 } 1356 } 1357 1358 func TestScHandleBlockResponse(t *testing.T) { 1359 now := time.Now() 1360 block6FromP1 := bcBlockResponse{ 1361 time: now.Add(time.Millisecond), 1362 peerID: p2p.ID("P1"), 1363 size: 100, 1364 block: makeScBlock(6), 1365 } 1366 1367 type args struct { 1368 event bcBlockResponse 1369 } 1370 1371 tests := []struct { 1372 name string 1373 fields scTestParams 1374 args args 1375 wantEvent Event 1376 wantErr bool 1377 }{ 1378 { 1379 name: "empty scheduler", 1380 fields: scTestParams{}, 1381 args: args{event: block6FromP1}, 1382 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1383 }, 1384 { 1385 name: "block from removed peer", 1386 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1387 args: args{event: block6FromP1}, 1388 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1389 }, 1390 { 1391 name: "block we haven't asked for", 1392 fields: scTestParams{ 1393 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1394 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}}, 1395 args: args{event: block6FromP1}, 1396 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1397 }, 1398 { 1399 name: "block from wrong peer", 1400 fields: scTestParams{ 1401 peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}}, 1402 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1403 pending: map[int64]p2p.ID{6: "P2"}, 1404 pendingTime: map[int64]time.Time{6: now}, 1405 }, 1406 args: args{event: block6FromP1}, 1407 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1408 }, 1409 { 1410 name: "block with bad timestamp", 1411 fields: scTestParams{ 1412 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1413 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1414 pending: map[int64]p2p.ID{6: "P1"}, 1415 pendingTime: map[int64]time.Time{6: now.Add(time.Second)}, 1416 }, 1417 args: args{event: block6FromP1}, 1418 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1419 }, 1420 { 1421 name: "good block, accept", 1422 fields: scTestParams{ 1423 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1424 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1425 pending: map[int64]p2p.ID{6: "P1"}, 1426 pendingTime: map[int64]time.Time{6: now}, 1427 }, 1428 args: args{event: block6FromP1}, 1429 wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(6)}, 1430 }, 1431 } 1432 1433 for _, tt := range tests { 1434 tt := tt 1435 t.Run(tt.name, func(t *testing.T) { 1436 sc := newTestScheduler(tt.fields) 1437 event, err := sc.handleBlockResponse(tt.args.event) 1438 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1439 }) 1440 } 1441 } 1442 1443 func TestScHandleNoBlockResponse(t *testing.T) { 1444 now := time.Now() 1445 noBlock6FromP1 := bcNoBlockResponse{ 1446 time: now.Add(time.Millisecond), 1447 peerID: p2p.ID("P1"), 1448 height: 6, 1449 } 1450 1451 tests := []struct { 1452 name string 1453 fields scTestParams 1454 wantEvent Event 1455 wantFields scTestParams 1456 wantErr bool 1457 }{ 1458 { 1459 name: "empty scheduler", 1460 fields: scTestParams{}, 1461 wantEvent: noOpEvent{}, 1462 wantFields: scTestParams{}, 1463 }, 1464 { 1465 name: "noBlock from removed peer", 1466 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1467 wantEvent: noOpEvent{}, 1468 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1469 }, 1470 { 1471 name: "for block we haven't asked for", 1472 fields: scTestParams{ 1473 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1474 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}}, 1475 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1476 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1477 }, 1478 { 1479 name: "noBlock from peer we don't have", 1480 fields: scTestParams{ 1481 peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}}, 1482 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1483 pending: map[int64]p2p.ID{6: "P2"}, 1484 pendingTime: map[int64]time.Time{6: now}, 1485 }, 1486 wantEvent: noOpEvent{}, 1487 wantFields: scTestParams{ 1488 peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}}, 1489 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1490 pending: map[int64]p2p.ID{6: "P2"}, 1491 pendingTime: map[int64]time.Time{6: now}, 1492 }, 1493 }, 1494 { 1495 name: "noBlock from existing peer", 1496 fields: scTestParams{ 1497 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1498 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1499 pending: map[int64]p2p.ID{6: "P1"}, 1500 pendingTime: map[int64]time.Time{6: now}, 1501 }, 1502 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1503 wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}}, 1504 }, 1505 } 1506 1507 for _, tt := range tests { 1508 tt := tt 1509 t.Run(tt.name, func(t *testing.T) { 1510 sc := newTestScheduler(tt.fields) 1511 event, err := sc.handleNoBlockResponse(noBlock6FromP1) 1512 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1513 wantSc := newTestScheduler(tt.wantFields) 1514 assert.Equal(t, wantSc, sc) 1515 }) 1516 } 1517 } 1518 1519 func TestScHandleBlockProcessed(t *testing.T) { 1520 now := time.Now() 1521 processed6FromP1 := pcBlockProcessed{ 1522 peerID: p2p.ID("P1"), 1523 height: 6, 1524 } 1525 1526 type args struct { 1527 event pcBlockProcessed 1528 } 1529 1530 tests := []struct { 1531 name string 1532 fields scTestParams 1533 args args 1534 wantEvent Event 1535 wantErr bool 1536 }{ 1537 { 1538 name: "empty scheduler", 1539 fields: scTestParams{height: 6}, 1540 args: args{event: processed6FromP1}, 1541 wantEvent: noOpEvent{}, 1542 }, 1543 { 1544 name: "processed block we don't have", 1545 fields: scTestParams{ 1546 initHeight: 5, 1547 height: 6, 1548 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1549 allB: []int64{6, 7, 8}, 1550 pending: map[int64]p2p.ID{6: "P1"}, 1551 pendingTime: map[int64]time.Time{6: now}, 1552 }, 1553 args: args{event: processed6FromP1}, 1554 wantEvent: noOpEvent{}, 1555 }, 1556 { 1557 name: "processed block ok, we processed all blocks", 1558 fields: scTestParams{ 1559 initHeight: 5, 1560 height: 6, 1561 peers: map[string]*scPeer{"P1": {height: 7, state: peerStateReady}}, 1562 allB: []int64{6, 7}, 1563 received: map[int64]p2p.ID{6: "P1", 7: "P1"}, 1564 }, 1565 args: args{event: processed6FromP1}, 1566 wantEvent: scFinishedEv{}, 1567 }, 1568 { 1569 name: "processed block ok, we still have blocks to process", 1570 fields: scTestParams{ 1571 initHeight: 5, 1572 height: 6, 1573 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1574 allB: []int64{6, 7, 8}, 1575 pending: map[int64]p2p.ID{7: "P1", 8: "P1"}, 1576 received: map[int64]p2p.ID{6: "P1"}, 1577 }, 1578 args: args{event: processed6FromP1}, 1579 wantEvent: noOpEvent{}, 1580 }, 1581 } 1582 1583 for _, tt := range tests { 1584 tt := tt 1585 t.Run(tt.name, func(t *testing.T) { 1586 sc := newTestScheduler(tt.fields) 1587 event, err := sc.handleBlockProcessed(tt.args.event) 1588 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1589 }) 1590 } 1591 } 1592 1593 func TestScHandleBlockVerificationFailure(t *testing.T) { 1594 now := time.Now() 1595 1596 type args struct { 1597 event pcBlockVerificationFailure 1598 } 1599 1600 tests := []struct { 1601 name string 1602 fields scTestParams 1603 args args 1604 wantEvent Event 1605 wantErr bool 1606 }{ 1607 { 1608 name: "empty scheduler", 1609 fields: scTestParams{}, 1610 args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}}, 1611 wantEvent: noOpEvent{}, 1612 }, 1613 { 1614 name: "failed block we don't have, single peer is still removed", 1615 fields: scTestParams{ 1616 initHeight: 5, 1617 height: 6, 1618 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1619 allB: []int64{6, 7, 8}, 1620 pending: map[int64]p2p.ID{6: "P1"}, 1621 pendingTime: map[int64]time.Time{6: now}, 1622 }, 1623 args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}}, 1624 wantEvent: scFinishedEv{}, 1625 }, 1626 { 1627 name: "failed block we don't have, one of two peers are removed", 1628 fields: scTestParams{ 1629 initHeight: 5, 1630 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}}, 1631 allB: []int64{6, 7, 8}, 1632 pending: map[int64]p2p.ID{6: "P1"}, 1633 pendingTime: map[int64]time.Time{6: now}, 1634 }, 1635 args: args{event: pcBlockVerificationFailure{height: 10, firstPeerID: "P1", secondPeerID: "P1"}}, 1636 wantEvent: noOpEvent{}, 1637 }, 1638 { 1639 name: "failed block, all blocks are processed after removal", 1640 fields: scTestParams{ 1641 initHeight: 5, 1642 height: 6, 1643 peers: map[string]*scPeer{"P1": {height: 7, state: peerStateReady}}, 1644 allB: []int64{6, 7}, 1645 received: map[int64]p2p.ID{6: "P1", 7: "P1"}, 1646 }, 1647 args: args{event: pcBlockVerificationFailure{height: 7, firstPeerID: "P1", secondPeerID: "P1"}}, 1648 wantEvent: scFinishedEv{}, 1649 }, 1650 { 1651 name: "failed block, we still have blocks to process", 1652 fields: scTestParams{ 1653 initHeight: 4, 1654 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}}, 1655 allB: []int64{5, 6, 7, 8}, 1656 pending: map[int64]p2p.ID{7: "P1", 8: "P1"}, 1657 received: map[int64]p2p.ID{5: "P1", 6: "P1"}, 1658 }, 1659 args: args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P1"}}, 1660 wantEvent: noOpEvent{}, 1661 }, 1662 { 1663 name: "failed block, H+1 and H+2 delivered by different peers, we still have blocks to process", 1664 fields: scTestParams{ 1665 initHeight: 4, 1666 peers: map[string]*scPeer{ 1667 "P1": {height: 8, state: peerStateReady}, 1668 "P2": {height: 8, state: peerStateReady}, 1669 "P3": {height: 8, state: peerStateReady}, 1670 }, 1671 allB: []int64{5, 6, 7, 8}, 1672 pending: map[int64]p2p.ID{7: "P1", 8: "P1"}, 1673 received: map[int64]p2p.ID{5: "P1", 6: "P1"}, 1674 }, 1675 args: args{event: pcBlockVerificationFailure{height: 5, firstPeerID: "P1", secondPeerID: "P2"}}, 1676 wantEvent: noOpEvent{}, 1677 }, 1678 } 1679 1680 for _, tt := range tests { 1681 tt := tt 1682 t.Run(tt.name, func(t *testing.T) { 1683 sc := newTestScheduler(tt.fields) 1684 event, err := sc.handleBlockProcessError(tt.args.event) 1685 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1686 }) 1687 } 1688 } 1689 1690 func TestScHandleAddNewPeer(t *testing.T) { 1691 addP1 := bcAddNewPeer{ 1692 peerID: p2p.ID("P1"), 1693 } 1694 type args struct { 1695 event bcAddNewPeer 1696 } 1697 1698 tests := []struct { 1699 name string 1700 fields scTestParams 1701 args args 1702 wantEvent Event 1703 wantErr bool 1704 }{ 1705 { 1706 name: "add P1 to empty scheduler", 1707 fields: scTestParams{}, 1708 args: args{event: addP1}, 1709 wantEvent: noOpEvent{}, 1710 }, 1711 { 1712 name: "add duplicate peer", 1713 fields: scTestParams{ 1714 height: 6, 1715 peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}}, 1716 allB: []int64{6, 7, 8}, 1717 }, 1718 args: args{event: addP1}, 1719 wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")}, 1720 }, 1721 { 1722 name: "add P1 to non empty scheduler", 1723 fields: scTestParams{ 1724 height: 6, 1725 peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}}, 1726 allB: []int64{6, 7, 8}, 1727 }, 1728 args: args{event: addP1}, 1729 wantEvent: noOpEvent{}, 1730 }, 1731 } 1732 1733 for _, tt := range tests { 1734 tt := tt 1735 t.Run(tt.name, func(t *testing.T) { 1736 sc := newTestScheduler(tt.fields) 1737 event, err := sc.handleAddNewPeer(tt.args.event) 1738 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1739 }) 1740 } 1741 } 1742 1743 func TestScHandleTryPrunePeer(t *testing.T) { 1744 now := time.Now() 1745 1746 pruneEv := rTryPrunePeer{ 1747 time: now.Add(time.Second + time.Millisecond), 1748 } 1749 type args struct { 1750 event rTryPrunePeer 1751 } 1752 1753 tests := []struct { 1754 name string 1755 fields scTestParams 1756 args args 1757 wantEvent Event 1758 wantErr bool 1759 }{ 1760 { 1761 name: "no peers", 1762 fields: scTestParams{}, 1763 args: args{event: pruneEv}, 1764 wantEvent: noOpEvent{}, 1765 }, 1766 { 1767 name: "no prunable peers", 1768 fields: scTestParams{ 1769 minRecvRate: 100, 1770 peers: map[string]*scPeer{ 1771 // X - removed, active, fast 1772 "P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101}, 1773 // X - ready, active, fast 1774 "P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101}, 1775 // X - removed, active, equal 1776 "P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100}}, 1777 peerTimeout: time.Second, 1778 }, 1779 args: args{event: pruneEv}, 1780 wantEvent: noOpEvent{}, 1781 }, 1782 { 1783 name: "mixed peers", 1784 fields: scTestParams{ 1785 minRecvRate: 100, 1786 peers: map[string]*scPeer{ 1787 // X - removed, active, fast 1788 "P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5}, 1789 // X - ready, active, fast 1790 "P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5}, 1791 // X - removed, active, equal 1792 "P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5}, 1793 // V - ready, inactive, equal 1794 "P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7}, 1795 // V - ready, inactive, slow 1796 "P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7}, 1797 // V - ready, active, slow 1798 "P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7}, 1799 }, 1800 allB: []int64{1, 2, 3, 4, 5, 6, 7}, 1801 peerTimeout: time.Second}, 1802 args: args{event: pruneEv}, 1803 wantEvent: scPeersPruned{peers: []p2p.ID{"P4", "P5", "P6"}}, 1804 }, 1805 { 1806 name: "mixed peers, finish after pruning", 1807 fields: scTestParams{ 1808 minRecvRate: 100, 1809 height: 6, 1810 peers: map[string]*scPeer{ 1811 // X - removed, active, fast 1812 "P1": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 101, height: 5}, 1813 // X - ready, active, fast 1814 "P2": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 101, height: 5}, 1815 // X - removed, active, equal 1816 "P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second), lastRate: 100, height: 5}, 1817 // V - ready, inactive, equal 1818 "P4": {state: peerStateReady, lastTouched: now, lastRate: 100, height: 7}, 1819 // V - ready, inactive, slow 1820 "P5": {state: peerStateReady, lastTouched: now, lastRate: 99, height: 7}, 1821 // V - ready, active, slow 1822 "P6": {state: peerStateReady, lastTouched: now.Add(time.Second), lastRate: 90, height: 7}, 1823 }, 1824 allB: []int64{6, 7}, 1825 peerTimeout: time.Second}, 1826 args: args{event: pruneEv}, 1827 wantEvent: scFinishedEv{}, 1828 }, 1829 } 1830 1831 for _, tt := range tests { 1832 tt := tt 1833 t.Run(tt.name, func(t *testing.T) { 1834 sc := newTestScheduler(tt.fields) 1835 event, err := sc.handleTryPrunePeer(tt.args.event) 1836 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1837 }) 1838 } 1839 } 1840 1841 func TestScHandleTrySchedule(t *testing.T) { 1842 now := time.Now() 1843 tryEv := rTrySchedule{ 1844 time: now.Add(time.Second + time.Millisecond), 1845 } 1846 1847 type args struct { 1848 event rTrySchedule 1849 } 1850 tests := []struct { 1851 name string 1852 fields scTestParams 1853 args args 1854 wantEvent Event 1855 wantErr bool 1856 }{ 1857 { 1858 name: "no peers", 1859 fields: scTestParams{startTime: now, peers: map[string]*scPeer{}}, 1860 args: args{event: tryEv}, 1861 wantEvent: noOpEvent{}, 1862 }, 1863 { 1864 name: "only new peers", 1865 fields: scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}}, 1866 args: args{event: tryEv}, 1867 wantEvent: noOpEvent{}, 1868 }, 1869 { 1870 name: "only Removed peers", 1871 fields: scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}}, 1872 args: args{event: tryEv}, 1873 wantEvent: noOpEvent{}, 1874 }, 1875 { 1876 name: "one Ready shorter peer", 1877 fields: scTestParams{ 1878 startTime: now, 1879 height: 6, 1880 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}}, 1881 args: args{event: tryEv}, 1882 wantEvent: noOpEvent{}, 1883 }, 1884 { 1885 name: "one Ready equal peer", 1886 fields: scTestParams{ 1887 startTime: now, 1888 peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}, 1889 allB: []int64{1, 2, 3, 4}}, 1890 args: args{event: tryEv}, 1891 wantEvent: scBlockRequest{peerID: "P1", height: 1}, 1892 }, 1893 { 1894 name: "many Ready higher peers with different number of pending requests", 1895 fields: scTestParams{ 1896 startTime: now, 1897 peers: map[string]*scPeer{ 1898 "P1": {height: 4, state: peerStateReady}, 1899 "P2": {height: 5, state: peerStateReady}}, 1900 allB: []int64{1, 2, 3, 4, 5}, 1901 pending: map[int64]p2p.ID{ 1902 1: "P1", 2: "P1", 1903 3: "P2", 1904 }, 1905 }, 1906 args: args{event: tryEv}, 1907 wantEvent: scBlockRequest{peerID: "P2", height: 4}, 1908 }, 1909 1910 { 1911 name: "many Ready higher peers with same number of pending requests", 1912 fields: scTestParams{ 1913 startTime: now, 1914 peers: map[string]*scPeer{ 1915 "P2": {height: 8, state: peerStateReady}, 1916 "P1": {height: 8, state: peerStateReady}, 1917 "P3": {height: 8, state: peerStateReady}}, 1918 allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}, 1919 pending: map[int64]p2p.ID{ 1920 1: "P1", 2: "P1", 1921 3: "P3", 4: "P3", 1922 5: "P2", 6: "P2", 1923 }, 1924 }, 1925 args: args{event: tryEv}, 1926 wantEvent: scBlockRequest{peerID: "P1", height: 7}, 1927 }, 1928 } 1929 1930 for _, tt := range tests { 1931 tt := tt 1932 t.Run(tt.name, func(t *testing.T) { 1933 sc := newTestScheduler(tt.fields) 1934 event, err := sc.handleTrySchedule(tt.args.event) 1935 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 1936 }) 1937 } 1938 } 1939 1940 func TestScHandleStatusResponse(t *testing.T) { 1941 now := time.Now() 1942 statusRespP1Ev := bcStatusResponse{ 1943 time: now.Add(time.Second + time.Millisecond), 1944 peerID: "P1", 1945 height: 6, 1946 } 1947 1948 type args struct { 1949 event bcStatusResponse 1950 } 1951 tests := []struct { 1952 name string 1953 fields scTestParams 1954 args args 1955 wantEvent Event 1956 wantErr bool 1957 }{ 1958 { 1959 name: "change height of non existing peer", 1960 fields: scTestParams{ 1961 peers: map[string]*scPeer{"P2": {height: 2, state: peerStateReady}}, 1962 allB: []int64{1, 2}, 1963 }, 1964 args: args{event: statusRespP1Ev}, 1965 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1966 }, 1967 1968 { 1969 name: "increase height of removed peer", 1970 fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 2, state: peerStateRemoved}}}, 1971 args: args{event: statusRespP1Ev}, 1972 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1973 }, 1974 1975 { 1976 name: "decrease height of single peer", 1977 fields: scTestParams{ 1978 height: 5, 1979 peers: map[string]*scPeer{"P1": {height: 10, state: peerStateReady}}, 1980 allB: []int64{5, 6, 7, 8, 9, 10}, 1981 }, 1982 args: args{event: statusRespP1Ev}, 1983 wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")}, 1984 }, 1985 1986 { 1987 name: "increase height of single peer", 1988 fields: scTestParams{ 1989 peers: map[string]*scPeer{"P1": {height: 2, state: peerStateReady}}, 1990 allB: []int64{1, 2}}, 1991 args: args{event: statusRespP1Ev}, 1992 wantEvent: noOpEvent{}, 1993 }, 1994 { 1995 name: "noop height change of single peer", 1996 fields: scTestParams{ 1997 peers: map[string]*scPeer{"P1": {height: 6, state: peerStateReady}}, 1998 allB: []int64{1, 2, 3, 4, 5, 6}}, 1999 args: args{event: statusRespP1Ev}, 2000 wantEvent: noOpEvent{}, 2001 }, 2002 } 2003 2004 for _, tt := range tests { 2005 tt := tt 2006 t.Run(tt.name, func(t *testing.T) { 2007 sc := newTestScheduler(tt.fields) 2008 event, err := sc.handleStatusResponse(tt.args.event) 2009 checkScResults(t, tt.wantErr, err, tt.wantEvent, event) 2010 }) 2011 } 2012 } 2013 2014 func TestScHandle(t *testing.T) { 2015 now := time.Now() 2016 2017 type unknownEv struct { 2018 priorityNormal 2019 } 2020 2021 t0 := time.Now() 2022 tick := make([]time.Time, 100) 2023 for i := range tick { 2024 tick[i] = t0.Add(time.Duration(i) * time.Millisecond) 2025 } 2026 2027 type args struct { 2028 event Event 2029 } 2030 type scStep struct { 2031 currentSc *scTestParams 2032 args args 2033 wantEvent Event 2034 wantErr bool 2035 wantSc *scTestParams 2036 } 2037 tests := []struct { 2038 name string 2039 steps []scStep 2040 }{ 2041 { 2042 name: "unknown event", 2043 steps: []scStep{ 2044 { // add P1 2045 currentSc: &scTestParams{}, 2046 args: args{event: unknownEv{}}, 2047 wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")}, 2048 wantSc: &scTestParams{}, 2049 }, 2050 }, 2051 }, 2052 { 2053 name: "single peer, sync 3 blocks", 2054 steps: []scStep{ 2055 { // add P1 2056 currentSc: &scTestParams{startTime: now, peers: map[string]*scPeer{}, height: 1}, 2057 args: args{event: bcAddNewPeer{peerID: "P1"}}, 2058 wantEvent: noOpEvent{}, 2059 wantSc: &scTestParams{startTime: now, peers: map[string]*scPeer{ 2060 "P1": {base: -1, height: -1, state: peerStateNew}}, height: 1}, 2061 }, 2062 { // set height of P1 2063 args: args{event: bcStatusResponse{peerID: "P1", time: tick[0], height: 3}}, 2064 wantEvent: noOpEvent{}, 2065 wantSc: &scTestParams{ 2066 startTime: now, 2067 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 2068 allB: []int64{1, 2, 3}, 2069 height: 1, 2070 }, 2071 }, 2072 { // schedule block 1 2073 args: args{event: rTrySchedule{time: tick[1]}}, 2074 wantEvent: scBlockRequest{peerID: "P1", height: 1}, 2075 wantSc: &scTestParams{ 2076 startTime: now, 2077 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 2078 allB: []int64{1, 2, 3}, 2079 pending: map[int64]p2p.ID{1: "P1"}, 2080 pendingTime: map[int64]time.Time{1: tick[1]}, 2081 height: 1, 2082 }, 2083 }, 2084 { // schedule block 2 2085 args: args{event: rTrySchedule{time: tick[2]}}, 2086 wantEvent: scBlockRequest{peerID: "P1", height: 2}, 2087 wantSc: &scTestParams{ 2088 startTime: now, 2089 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 2090 allB: []int64{1, 2, 3}, 2091 pending: map[int64]p2p.ID{1: "P1", 2: "P1"}, 2092 pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2]}, 2093 height: 1, 2094 }, 2095 }, 2096 { // schedule block 3 2097 args: args{event: rTrySchedule{time: tick[3]}}, 2098 wantEvent: scBlockRequest{peerID: "P1", height: 3}, 2099 wantSc: &scTestParams{ 2100 startTime: now, 2101 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}}, 2102 allB: []int64{1, 2, 3}, 2103 pending: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"}, 2104 pendingTime: map[int64]time.Time{1: tick[1], 2: tick[2], 3: tick[3]}, 2105 height: 1, 2106 }, 2107 }, 2108 { // block response 1 2109 args: args{event: bcBlockResponse{peerID: "P1", time: tick[4], size: 100, block: makeScBlock(1)}}, 2110 wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(1)}, 2111 wantSc: &scTestParams{ 2112 startTime: now, 2113 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[4]}}, 2114 allB: []int64{1, 2, 3}, 2115 pending: map[int64]p2p.ID{2: "P1", 3: "P1"}, 2116 pendingTime: map[int64]time.Time{2: tick[2], 3: tick[3]}, 2117 received: map[int64]p2p.ID{1: "P1"}, 2118 height: 1, 2119 }, 2120 }, 2121 { // block response 2 2122 args: args{event: bcBlockResponse{peerID: "P1", time: tick[5], size: 100, block: makeScBlock(2)}}, 2123 wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(2)}, 2124 wantSc: &scTestParams{ 2125 startTime: now, 2126 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[5]}}, 2127 allB: []int64{1, 2, 3}, 2128 pending: map[int64]p2p.ID{3: "P1"}, 2129 pendingTime: map[int64]time.Time{3: tick[3]}, 2130 received: map[int64]p2p.ID{1: "P1", 2: "P1"}, 2131 height: 1, 2132 }, 2133 }, 2134 { // block response 3 2135 args: args{event: bcBlockResponse{peerID: "P1", time: tick[6], size: 100, block: makeScBlock(3)}}, 2136 wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(3)}, 2137 wantSc: &scTestParams{ 2138 startTime: now, 2139 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2140 allB: []int64{1, 2, 3}, 2141 received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"}, 2142 height: 1, 2143 }, 2144 }, 2145 { // processed block 1 2146 args: args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 1}}, 2147 wantEvent: noOpEvent{}, 2148 wantSc: &scTestParams{ 2149 startTime: now, 2150 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2151 allB: []int64{2, 3}, 2152 received: map[int64]p2p.ID{2: "P1", 3: "P1"}, 2153 height: 2, 2154 }, 2155 }, 2156 { // processed block 2 2157 args: args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 2}}, 2158 wantEvent: scFinishedEv{}, 2159 wantSc: &scTestParams{ 2160 startTime: now, 2161 peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2162 allB: []int64{3}, 2163 received: map[int64]p2p.ID{3: "P1"}, 2164 height: 3, 2165 }, 2166 }, 2167 }, 2168 }, 2169 { 2170 name: "block verification failure", 2171 steps: []scStep{ 2172 { // failure processing block 1 2173 currentSc: &scTestParams{ 2174 startTime: now, 2175 peers: map[string]*scPeer{ 2176 "P1": {height: 4, state: peerStateReady, lastTouched: tick[6]}, 2177 "P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2178 allB: []int64{1, 2, 3, 4}, 2179 received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"}, 2180 height: 1, 2181 }, 2182 args: args{event: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"}}, 2183 wantEvent: noOpEvent{}, 2184 wantSc: &scTestParams{ 2185 startTime: now, 2186 peers: map[string]*scPeer{ 2187 "P1": {height: 4, state: peerStateRemoved, lastTouched: tick[6]}, 2188 "P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}}, 2189 allB: []int64{1, 2, 3}, 2190 received: map[int64]p2p.ID{}, 2191 height: 1, 2192 }, 2193 }, 2194 }, 2195 }, 2196 } 2197 2198 for _, tt := range tests { 2199 tt := tt 2200 t.Run(tt.name, func(t *testing.T) { 2201 var sc *scheduler 2202 for i, step := range tt.steps { 2203 // First step must always initialise the currentState as state. 2204 if step.currentSc != nil { 2205 sc = newTestScheduler(*step.currentSc) 2206 } 2207 if sc == nil { 2208 panic("Bad (initial?) step") 2209 } 2210 2211 nextEvent, err := sc.handle(step.args.event) 2212 wantSc := newTestScheduler(*step.wantSc) 2213 2214 t.Logf("step %d(%v): %s", i, step.args.event, sc) 2215 checkSameScheduler(t, wantSc, sc) 2216 2217 checkScResults(t, step.wantErr, err, step.wantEvent, nextEvent) 2218 2219 // Next step may use the wantedState as their currentState. 2220 sc = newTestScheduler(*step.wantSc) 2221 } 2222 }) 2223 } 2224 }