github.com/vipernet-xyz/tm@v0.34.24/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/vipernet-xyz/tm/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  }