You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2293 lines
66 KiB

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