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.

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