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.

551 lines
15 KiB

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