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.

561 lines
15 KiB

  1. package statesync
  2. import (
  3. "os"
  4. "testing"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/require"
  7. "github.com/tendermint/tendermint/types"
  8. )
  9. func setupChunkQueue(t *testing.T) (*chunkQueue, func()) {
  10. snapshot := &snapshot{
  11. Height: 3,
  12. Format: 1,
  13. Chunks: 5,
  14. Hash: []byte{7},
  15. Metadata: nil,
  16. }
  17. queue, err := newChunkQueue(snapshot, t.TempDir())
  18. require.NoError(t, err)
  19. teardown := func() {
  20. err := queue.Close()
  21. require.NoError(t, err)
  22. }
  23. return queue, teardown
  24. }
  25. func TestNewChunkQueue_TempDir(t *testing.T) {
  26. snapshot := &snapshot{
  27. Height: 3,
  28. Format: 1,
  29. Chunks: 5,
  30. Hash: []byte{7},
  31. Metadata: nil,
  32. }
  33. dir := t.TempDir()
  34. queue, err := newChunkQueue(snapshot, dir)
  35. require.NoError(t, err)
  36. files, err := os.ReadDir(dir)
  37. require.NoError(t, err)
  38. assert.Len(t, files, 1)
  39. err = queue.Close()
  40. require.NoError(t, err)
  41. files, err = os.ReadDir(dir)
  42. require.NoError(t, err)
  43. assert.Len(t, files, 0)
  44. }
  45. func TestChunkQueue(t *testing.T) {
  46. queue, teardown := setupChunkQueue(t)
  47. defer teardown()
  48. // Adding the first chunk should be fine
  49. added, err := queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{3, 1, 0}})
  50. require.NoError(t, err)
  51. assert.True(t, added)
  52. // Adding the last chunk should also be fine
  53. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 4, Chunk: []byte{3, 1, 4}})
  54. require.NoError(t, err)
  55. assert.True(t, added)
  56. // Adding the first or last chunks again should return false
  57. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{3, 1, 0}})
  58. require.NoError(t, err)
  59. assert.False(t, added)
  60. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 4, Chunk: []byte{3, 1, 4}})
  61. require.NoError(t, err)
  62. assert.False(t, added)
  63. // Adding the remaining chunks in reverse should be fine
  64. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 3, Chunk: []byte{3, 1, 3}})
  65. require.NoError(t, err)
  66. assert.True(t, added)
  67. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 2, Chunk: []byte{3, 1, 2}})
  68. require.NoError(t, err)
  69. assert.True(t, added)
  70. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 1, Chunk: []byte{3, 1, 1}})
  71. require.NoError(t, err)
  72. assert.True(t, added)
  73. // At this point, we should be able to retrieve them all via Next
  74. for i := 0; i < 5; i++ {
  75. c, err := queue.Next()
  76. require.NoError(t, err)
  77. assert.Equal(t, &chunk{Height: 3, Format: 1, Index: uint32(i), Chunk: []byte{3, 1, byte(i)}}, c)
  78. }
  79. _, err = queue.Next()
  80. require.Error(t, err)
  81. assert.Equal(t, errDone, err)
  82. // It should still be possible to try to add chunks (which will be ignored)
  83. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{3, 1, 0}})
  84. require.NoError(t, err)
  85. assert.False(t, added)
  86. // After closing the queue it will also return false
  87. err = queue.Close()
  88. require.NoError(t, err)
  89. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{3, 1, 0}})
  90. require.NoError(t, err)
  91. assert.False(t, added)
  92. // Closing the queue again should also be fine
  93. err = queue.Close()
  94. require.NoError(t, err)
  95. }
  96. func TestChunkQueue_Add_ChunkErrors(t *testing.T) {
  97. testcases := map[string]struct {
  98. chunk *chunk
  99. }{
  100. "nil chunk": {nil},
  101. "nil body": {&chunk{Height: 3, Format: 1, Index: 0, Chunk: nil}},
  102. "wrong height": {&chunk{Height: 9, Format: 1, Index: 0, Chunk: []byte{3, 1, 0}}},
  103. "wrong format": {&chunk{Height: 3, Format: 9, Index: 0, Chunk: []byte{3, 1, 0}}},
  104. "invalid index": {&chunk{Height: 3, Format: 1, Index: 5, Chunk: []byte{3, 1, 0}}},
  105. }
  106. for name, tc := range testcases {
  107. tc := tc
  108. t.Run(name, func(t *testing.T) {
  109. queue, teardown := setupChunkQueue(t)
  110. defer teardown()
  111. _, err := queue.Add(tc.chunk)
  112. require.Error(t, err)
  113. })
  114. }
  115. }
  116. func TestChunkQueue_Allocate(t *testing.T) {
  117. queue, teardown := setupChunkQueue(t)
  118. defer teardown()
  119. for i := uint32(0); i < queue.Size(); i++ {
  120. index, err := queue.Allocate()
  121. require.NoError(t, err)
  122. assert.EqualValues(t, i, index)
  123. }
  124. _, err := queue.Allocate()
  125. require.Error(t, err)
  126. assert.Equal(t, errDone, err)
  127. for i := uint32(0); i < queue.Size(); i++ {
  128. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: i, Chunk: []byte{byte(i)}})
  129. require.NoError(t, err)
  130. }
  131. // After all chunks have been allocated and retrieved, discarding a chunk will reallocate it.
  132. err = queue.Discard(2)
  133. require.NoError(t, err)
  134. index, err := queue.Allocate()
  135. require.NoError(t, err)
  136. assert.EqualValues(t, 2, index)
  137. _, err = queue.Allocate()
  138. require.Error(t, err)
  139. assert.Equal(t, errDone, err)
  140. // Discarding a chunk the closing the queue will return errDone.
  141. err = queue.Discard(2)
  142. require.NoError(t, err)
  143. err = queue.Close()
  144. require.NoError(t, err)
  145. _, err = queue.Allocate()
  146. require.Error(t, err)
  147. assert.Equal(t, errDone, err)
  148. }
  149. func TestChunkQueue_Discard(t *testing.T) {
  150. queue, teardown := setupChunkQueue(t)
  151. defer teardown()
  152. // Add a few chunks to the queue and fetch a couple
  153. _, err := queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{byte(0)}})
  154. require.NoError(t, err)
  155. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 1, Chunk: []byte{byte(1)}})
  156. require.NoError(t, err)
  157. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 2, Chunk: []byte{byte(2)}})
  158. require.NoError(t, err)
  159. c, err := queue.Next()
  160. require.NoError(t, err)
  161. assert.EqualValues(t, 0, c.Index)
  162. c, err = queue.Next()
  163. require.NoError(t, err)
  164. assert.EqualValues(t, 1, c.Index)
  165. // Discarding the first chunk and re-adding it should cause it to be returned
  166. // immediately by Next(), before procceeding with chunk 2
  167. err = queue.Discard(0)
  168. require.NoError(t, err)
  169. added, err := queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{byte(0)}})
  170. require.NoError(t, err)
  171. assert.True(t, added)
  172. c, err = queue.Next()
  173. require.NoError(t, err)
  174. assert.EqualValues(t, 0, c.Index)
  175. c, err = queue.Next()
  176. require.NoError(t, err)
  177. assert.EqualValues(t, 2, c.Index)
  178. // Discard then allocate, add and fetch all chunks
  179. for i := uint32(0); i < queue.Size(); i++ {
  180. err := queue.Discard(i)
  181. require.NoError(t, err)
  182. }
  183. for i := uint32(0); i < queue.Size(); i++ {
  184. _, err := queue.Allocate()
  185. require.NoError(t, err)
  186. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: i, Chunk: []byte{byte(i)}})
  187. require.NoError(t, err)
  188. c, err = queue.Next()
  189. require.NoError(t, err)
  190. assert.EqualValues(t, i, c.Index)
  191. }
  192. // Discarding a non-existent chunk does nothing.
  193. err = queue.Discard(99)
  194. require.NoError(t, err)
  195. // When discard a couple of chunks, we should be able to allocate, add, and fetch them again.
  196. err = queue.Discard(3)
  197. require.NoError(t, err)
  198. err = queue.Discard(1)
  199. require.NoError(t, err)
  200. index, err := queue.Allocate()
  201. require.NoError(t, err)
  202. assert.EqualValues(t, 1, index)
  203. index, err = queue.Allocate()
  204. require.NoError(t, err)
  205. assert.EqualValues(t, 3, index)
  206. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 3, Chunk: []byte{3}})
  207. require.NoError(t, err)
  208. assert.True(t, added)
  209. added, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 1, Chunk: []byte{1}})
  210. require.NoError(t, err)
  211. assert.True(t, added)
  212. chunk, err := queue.Next()
  213. require.NoError(t, err)
  214. assert.EqualValues(t, 1, chunk.Index)
  215. chunk, err = queue.Next()
  216. require.NoError(t, err)
  217. assert.EqualValues(t, 3, chunk.Index)
  218. _, err = queue.Next()
  219. require.Error(t, err)
  220. assert.Equal(t, errDone, err)
  221. // After closing the queue, discarding does nothing
  222. err = queue.Close()
  223. require.NoError(t, err)
  224. err = queue.Discard(2)
  225. require.NoError(t, err)
  226. }
  227. func TestChunkQueue_DiscardSender(t *testing.T) {
  228. queue, teardown := setupChunkQueue(t)
  229. defer teardown()
  230. // Allocate and add all chunks to the queue
  231. senders := []types.NodeID{types.NodeID("a"), types.NodeID("b"), types.NodeID("c")}
  232. for i := uint32(0); i < queue.Size(); i++ {
  233. _, err := queue.Allocate()
  234. require.NoError(t, err)
  235. _, err = queue.Add(&chunk{
  236. Height: 3,
  237. Format: 1,
  238. Index: i,
  239. Chunk: []byte{byte(i)},
  240. Sender: senders[int(i)%len(senders)],
  241. })
  242. require.NoError(t, err)
  243. }
  244. // Fetch the first three chunks
  245. for i := uint32(0); i < 3; i++ {
  246. _, err := queue.Next()
  247. require.NoError(t, err)
  248. }
  249. // Discarding an unknown sender should do nothing
  250. err := queue.DiscardSender(types.NodeID("x"))
  251. require.NoError(t, err)
  252. _, err = queue.Allocate()
  253. assert.Equal(t, errDone, err)
  254. // Discarding sender b should discard chunk 4, but not chunk 1 which has already been
  255. // returned.
  256. err = queue.DiscardSender(types.NodeID("b"))
  257. require.NoError(t, err)
  258. index, err := queue.Allocate()
  259. require.NoError(t, err)
  260. assert.EqualValues(t, 4, index)
  261. _, err = queue.Allocate()
  262. assert.Equal(t, errDone, err)
  263. }
  264. func TestChunkQueue_GetSender(t *testing.T) {
  265. queue, teardown := setupChunkQueue(t)
  266. defer teardown()
  267. peerAID := types.NodeID("aa")
  268. peerBID := types.NodeID("bb")
  269. _, err := queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{1}, Sender: peerAID})
  270. require.NoError(t, err)
  271. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 1, Chunk: []byte{2}, Sender: peerBID})
  272. require.NoError(t, err)
  273. assert.EqualValues(t, "aa", queue.GetSender(0))
  274. assert.EqualValues(t, "bb", queue.GetSender(1))
  275. assert.EqualValues(t, "", queue.GetSender(2))
  276. // After the chunk has been processed, we should still know who the sender was
  277. chunk, err := queue.Next()
  278. require.NoError(t, err)
  279. require.NotNil(t, chunk)
  280. require.EqualValues(t, 0, chunk.Index)
  281. assert.EqualValues(t, "aa", queue.GetSender(0))
  282. }
  283. func TestChunkQueue_Next(t *testing.T) {
  284. queue, teardown := setupChunkQueue(t)
  285. defer teardown()
  286. // Next should block waiting for the next chunks, even when given out of order.
  287. chNext := make(chan *chunk, 10)
  288. go func() {
  289. for {
  290. c, err := queue.Next()
  291. if err == errDone {
  292. close(chNext)
  293. break
  294. }
  295. require.NoError(t, err)
  296. chNext <- c
  297. }
  298. }()
  299. assert.Empty(t, chNext)
  300. _, err := queue.Add(&chunk{Height: 3, Format: 1, Index: 1, Chunk: []byte{3, 1, 1}, Sender: types.NodeID("b")})
  301. require.NoError(t, err)
  302. select {
  303. case <-chNext:
  304. assert.Fail(t, "channel should be empty")
  305. default:
  306. }
  307. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{3, 1, 0}, Sender: types.NodeID("a")})
  308. require.NoError(t, err)
  309. assert.Equal(t,
  310. &chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{3, 1, 0}, Sender: types.NodeID("a")},
  311. <-chNext)
  312. assert.Equal(t,
  313. &chunk{Height: 3, Format: 1, Index: 1, Chunk: []byte{3, 1, 1}, Sender: types.NodeID("b")},
  314. <-chNext)
  315. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 4, Chunk: []byte{3, 1, 4}, Sender: types.NodeID("e")})
  316. require.NoError(t, err)
  317. select {
  318. case <-chNext:
  319. assert.Fail(t, "channel should be empty")
  320. default:
  321. }
  322. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 2, Chunk: []byte{3, 1, 2}, Sender: types.NodeID("c")})
  323. require.NoError(t, err)
  324. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 3, Chunk: []byte{3, 1, 3}, Sender: types.NodeID("d")})
  325. require.NoError(t, err)
  326. assert.Equal(t,
  327. &chunk{Height: 3, Format: 1, Index: 2, Chunk: []byte{3, 1, 2}, Sender: types.NodeID("c")},
  328. <-chNext)
  329. assert.Equal(t,
  330. &chunk{Height: 3, Format: 1, Index: 3, Chunk: []byte{3, 1, 3}, Sender: types.NodeID("d")},
  331. <-chNext)
  332. assert.Equal(t,
  333. &chunk{Height: 3, Format: 1, Index: 4, Chunk: []byte{3, 1, 4}, Sender: types.NodeID("e")},
  334. <-chNext)
  335. _, ok := <-chNext
  336. assert.False(t, ok, "channel should be closed")
  337. // Calling next on a finished queue should return done
  338. _, err = queue.Next()
  339. assert.Equal(t, errDone, err)
  340. }
  341. func TestChunkQueue_Next_Closed(t *testing.T) {
  342. queue, teardown := setupChunkQueue(t)
  343. defer teardown()
  344. // Calling Next on a closed queue should return done
  345. _, err := queue.Add(&chunk{Height: 3, Format: 1, Index: 1, Chunk: []byte{3, 1, 1}})
  346. require.NoError(t, err)
  347. err = queue.Close()
  348. require.NoError(t, err)
  349. _, err = queue.Next()
  350. assert.Equal(t, errDone, err)
  351. }
  352. func TestChunkQueue_Retry(t *testing.T) {
  353. queue, teardown := setupChunkQueue(t)
  354. defer teardown()
  355. allocateAddChunksToQueue(t, queue)
  356. // Retrying a couple of chunks makes Next() return them, but they are not allocatable
  357. queue.Retry(3)
  358. queue.Retry(1)
  359. _, err := queue.Allocate()
  360. assert.Equal(t, errDone, err)
  361. chunk, err := queue.Next()
  362. require.NoError(t, err)
  363. assert.EqualValues(t, 1, chunk.Index)
  364. chunk, err = queue.Next()
  365. require.NoError(t, err)
  366. assert.EqualValues(t, 3, chunk.Index)
  367. _, err = queue.Next()
  368. assert.Equal(t, errDone, err)
  369. }
  370. func TestChunkQueue_RetryAll(t *testing.T) {
  371. queue, teardown := setupChunkQueue(t)
  372. defer teardown()
  373. allocateAddChunksToQueue(t, queue)
  374. _, err := queue.Next()
  375. assert.Equal(t, errDone, err)
  376. queue.RetryAll()
  377. _, err = queue.Allocate()
  378. assert.Equal(t, errDone, err)
  379. for i := uint32(0); i < queue.Size(); i++ {
  380. chunk, err := queue.Next()
  381. require.NoError(t, err)
  382. assert.EqualValues(t, i, chunk.Index)
  383. }
  384. _, err = queue.Next()
  385. assert.Equal(t, errDone, err)
  386. }
  387. func TestChunkQueue_Size(t *testing.T) {
  388. queue, teardown := setupChunkQueue(t)
  389. defer teardown()
  390. assert.EqualValues(t, 5, queue.Size())
  391. err := queue.Close()
  392. require.NoError(t, err)
  393. assert.EqualValues(t, 0, queue.Size())
  394. }
  395. func TestChunkQueue_WaitFor(t *testing.T) {
  396. queue, teardown := setupChunkQueue(t)
  397. defer teardown()
  398. waitFor1 := queue.WaitFor(1)
  399. waitFor4 := queue.WaitFor(4)
  400. // Adding 0 and 2 should not trigger waiters
  401. _, err := queue.Add(&chunk{Height: 3, Format: 1, Index: 0, Chunk: []byte{3, 1, 0}})
  402. require.NoError(t, err)
  403. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 2, Chunk: []byte{3, 1, 2}})
  404. require.NoError(t, err)
  405. select {
  406. case <-waitFor1:
  407. require.Fail(t, "WaitFor(1) should not trigger on 0 or 2")
  408. case <-waitFor4:
  409. require.Fail(t, "WaitFor(4) should not trigger on 0 or 2")
  410. default:
  411. }
  412. // Adding 1 should trigger WaitFor(1), but not WaitFor(4). The channel should be closed.
  413. _, err = queue.Add(&chunk{Height: 3, Format: 1, Index: 1, Chunk: []byte{3, 1, 1}})
  414. require.NoError(t, err)
  415. assert.EqualValues(t, 1, <-waitFor1)
  416. _, ok := <-waitFor1
  417. assert.False(t, ok)
  418. select {
  419. case <-waitFor4:
  420. require.Fail(t, "WaitFor(4) should not trigger on 0 or 2")
  421. default:
  422. }
  423. // Fetch the first chunk. At this point, waiting for either 0 (retrieved from pool) or 1
  424. // (queued in pool) should immediately return true.
  425. c, err := queue.Next()
  426. require.NoError(t, err)
  427. assert.EqualValues(t, 0, c.Index)
  428. w := queue.WaitFor(0)
  429. assert.EqualValues(t, 0, <-w)
  430. _, ok = <-w
  431. assert.False(t, ok)
  432. w = queue.WaitFor(1)
  433. assert.EqualValues(t, 1, <-w)
  434. _, ok = <-w
  435. assert.False(t, ok)
  436. // Close the queue. This should cause the waiter for 4 to close, and also cause any future
  437. // waiters to get closed channels.
  438. err = queue.Close()
  439. require.NoError(t, err)
  440. _, ok = <-waitFor4
  441. assert.False(t, ok)
  442. w = queue.WaitFor(3)
  443. _, ok = <-w
  444. assert.False(t, ok)
  445. }
  446. func TestNumChunkReturned(t *testing.T) {
  447. queue, teardown := setupChunkQueue(t)
  448. defer teardown()
  449. assert.EqualValues(t, 5, queue.Size())
  450. allocateAddChunksToQueue(t, queue)
  451. assert.EqualValues(t, 5, queue.numChunksReturned())
  452. err := queue.Close()
  453. require.NoError(t, err)
  454. }
  455. // Allocate and add all chunks to the queue
  456. func allocateAddChunksToQueue(t *testing.T, q *chunkQueue) {
  457. t.Helper()
  458. for i := uint32(0); i < q.Size(); i++ {
  459. _, err := q.Allocate()
  460. require.NoError(t, err)
  461. _, err = q.Add(&chunk{Height: 3, Format: 1, Index: i, Chunk: []byte{byte(i)}})
  462. require.NoError(t, err)
  463. _, err = q.Next()
  464. require.NoError(t, err)
  465. }
  466. }