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