github.com/Finschia/finschia-sdk@v0.48.1/snapshots/store_test.go (about)

     1  package snapshots_test
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	dbm "github.com/tendermint/tm-db"
    16  
    17  	"github.com/Finschia/finschia-sdk/snapshots"
    18  	"github.com/Finschia/finschia-sdk/snapshots/types"
    19  	"github.com/Finschia/finschia-sdk/testutil"
    20  )
    21  
    22  func setupStore(t *testing.T) *snapshots.Store {
    23  	// os.MkdirTemp() is used instead of testing.T.TempDir()
    24  	// see https://github.com/cosmos/cosmos-sdk/pull/8475 for
    25  	// this change's rationale.
    26  	tempdir, err := os.MkdirTemp("", "")
    27  	require.NoError(t, err)
    28  	t.Cleanup(func() { _ = os.RemoveAll(tempdir) })
    29  
    30  	store, err := snapshots.NewStore(dbm.NewMemDB(), tempdir)
    31  	require.NoError(t, err)
    32  
    33  	_, err = store.Save(1, 1, makeChunks([][]byte{
    34  		{1, 1, 0}, {1, 1, 1},
    35  	}))
    36  	require.NoError(t, err)
    37  	_, err = store.Save(2, 1, makeChunks([][]byte{
    38  		{2, 1, 0}, {2, 1, 1},
    39  	}))
    40  	require.NoError(t, err)
    41  	_, err = store.Save(2, 2, makeChunks([][]byte{
    42  		{2, 2, 0}, {2, 2, 1}, {2, 2, 2},
    43  	}))
    44  	require.NoError(t, err)
    45  	_, err = store.Save(3, 2, makeChunks([][]byte{
    46  		{3, 2, 0}, {3, 2, 1}, {3, 2, 2},
    47  	}))
    48  	require.NoError(t, err)
    49  
    50  	return store
    51  }
    52  
    53  func TestNewStore(t *testing.T) {
    54  	tempdir := t.TempDir()
    55  	_, err := snapshots.NewStore(dbm.NewMemDB(), tempdir)
    56  
    57  	require.NoError(t, err)
    58  }
    59  
    60  func TestNewStore_ErrNoDir(t *testing.T) {
    61  	_, err := snapshots.NewStore(dbm.NewMemDB(), "")
    62  	require.Error(t, err)
    63  }
    64  
    65  func TestNewStore_ErrDirFailure(t *testing.T) {
    66  	notADir := filepath.Join(testutil.TempFile(t).Name(), "subdir")
    67  
    68  	_, err := snapshots.NewStore(dbm.NewMemDB(), notADir)
    69  	require.Error(t, err)
    70  }
    71  
    72  func TestStore_Delete(t *testing.T) {
    73  	store := setupStore(t)
    74  	// Deleting a snapshot should remove it
    75  	err := store.Delete(2, 2)
    76  	require.NoError(t, err)
    77  
    78  	snapshot, err := store.Get(2, 2)
    79  	require.NoError(t, err)
    80  	assert.Nil(t, snapshot)
    81  
    82  	snapshots, err := store.List()
    83  	require.NoError(t, err)
    84  	assert.Len(t, snapshots, 3)
    85  
    86  	// Deleting it again should not error
    87  	err = store.Delete(2, 2)
    88  	require.NoError(t, err)
    89  
    90  	// Deleting a snapshot being saved should error
    91  	ch := make(chan io.ReadCloser)
    92  	go store.Save(9, 1, ch)
    93  
    94  	time.Sleep(10 * time.Millisecond)
    95  	err = store.Delete(9, 1)
    96  	require.Error(t, err)
    97  
    98  	// But after it's saved it should work
    99  	close(ch)
   100  	time.Sleep(10 * time.Millisecond)
   101  	err = store.Delete(9, 1)
   102  	require.NoError(t, err)
   103  }
   104  
   105  func TestStore_Get(t *testing.T) {
   106  	store := setupStore(t)
   107  
   108  	// Loading a missing snapshot should return nil
   109  	snapshot, err := store.Get(9, 9)
   110  	require.NoError(t, err)
   111  	assert.Nil(t, snapshot)
   112  
   113  	// Loading a snapshot should returns its metadata
   114  	snapshot, err = store.Get(2, 1)
   115  	require.NoError(t, err)
   116  	assert.Equal(t, &types.Snapshot{
   117  		Height: 2,
   118  		Format: 1,
   119  		Chunks: 2,
   120  		Hash:   hash([][]byte{{2, 1, 0}, {2, 1, 1}}),
   121  		Metadata: types.Metadata{
   122  			ChunkHashes: checksums([][]byte{
   123  				{2, 1, 0}, {2, 1, 1},
   124  			}),
   125  		},
   126  	}, snapshot)
   127  }
   128  
   129  func TestStore_GetLatest(t *testing.T) {
   130  	store := setupStore(t)
   131  	// Loading a missing snapshot should return nil
   132  	snapshot, err := store.GetLatest()
   133  	require.NoError(t, err)
   134  	assert.Equal(t, &types.Snapshot{
   135  		Height: 3,
   136  		Format: 2,
   137  		Chunks: 3,
   138  		Hash: hash([][]byte{
   139  			{3, 2, 0},
   140  			{3, 2, 1},
   141  			{3, 2, 2},
   142  		}),
   143  		Metadata: types.Metadata{
   144  			ChunkHashes: checksums([][]byte{
   145  				{3, 2, 0},
   146  				{3, 2, 1},
   147  				{3, 2, 2},
   148  			}),
   149  		},
   150  	}, snapshot)
   151  }
   152  
   153  func TestStore_List(t *testing.T) {
   154  	store := setupStore(t)
   155  	snapshots, err := store.List()
   156  	require.NoError(t, err)
   157  
   158  	require.Equal(t, []*types.Snapshot{
   159  		{
   160  			Height: 3, Format: 2, Chunks: 3, Hash: hash([][]byte{{3, 2, 0}, {3, 2, 1}, {3, 2, 2}}),
   161  			Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{3, 2, 0}, {3, 2, 1}, {3, 2, 2}})},
   162  		},
   163  		{
   164  			Height: 2, Format: 2, Chunks: 3, Hash: hash([][]byte{{2, 2, 0}, {2, 2, 1}, {2, 2, 2}}),
   165  			Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{2, 2, 0}, {2, 2, 1}, {2, 2, 2}})},
   166  		},
   167  		{
   168  			Height: 2, Format: 1, Chunks: 2, Hash: hash([][]byte{{2, 1, 0}, {2, 1, 1}}),
   169  			Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{2, 1, 0}, {2, 1, 1}})},
   170  		},
   171  		{
   172  			Height: 1, Format: 1, Chunks: 2, Hash: hash([][]byte{{1, 1, 0}, {1, 1, 1}}),
   173  			Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{1, 1, 0}, {1, 1, 1}})},
   174  		},
   175  	}, snapshots)
   176  }
   177  
   178  func TestStore_Load(t *testing.T) {
   179  	store := setupStore(t)
   180  	// Loading a missing snapshot should return nil
   181  	snapshot, chunks, err := store.Load(9, 9)
   182  	require.NoError(t, err)
   183  	assert.Nil(t, snapshot)
   184  	assert.Nil(t, chunks)
   185  
   186  	// Loading a snapshot should returns its metadata and chunks
   187  	snapshot, chunks, err = store.Load(2, 1)
   188  	require.NoError(t, err)
   189  	assert.Equal(t, &types.Snapshot{
   190  		Height: 2,
   191  		Format: 1,
   192  		Chunks: 2,
   193  		Hash:   hash([][]byte{{2, 1, 0}, {2, 1, 1}}),
   194  		Metadata: types.Metadata{
   195  			ChunkHashes: checksums([][]byte{
   196  				{2, 1, 0}, {2, 1, 1},
   197  			}),
   198  		},
   199  	}, snapshot)
   200  
   201  	for i := uint32(0); i < snapshot.Chunks; i++ {
   202  		reader, ok := <-chunks
   203  		require.True(t, ok)
   204  		chunk, err := io.ReadAll(reader)
   205  		require.NoError(t, err)
   206  		err = reader.Close()
   207  		require.NoError(t, err)
   208  		assert.Equal(t, []byte{2, 1, byte(i)}, chunk)
   209  	}
   210  	assert.Empty(t, chunks)
   211  }
   212  
   213  func TestStore_LoadChunk(t *testing.T) {
   214  	store := setupStore(t)
   215  	// Loading a missing snapshot should return nil
   216  	chunk, err := store.LoadChunk(9, 9, 0)
   217  	require.NoError(t, err)
   218  	assert.Nil(t, chunk)
   219  
   220  	// Loading a missing chunk index should return nil
   221  	chunk, err = store.LoadChunk(2, 1, 2)
   222  	require.NoError(t, err)
   223  	require.Nil(t, chunk)
   224  
   225  	// Loading a chunk should returns a content reader
   226  	chunk, err = store.LoadChunk(2, 1, 0)
   227  	require.NoError(t, err)
   228  	require.NotNil(t, chunk)
   229  	body, err := io.ReadAll(chunk)
   230  	require.NoError(t, err)
   231  	assert.Equal(t, []byte{2, 1, 0}, body)
   232  	err = chunk.Close()
   233  	require.NoError(t, err)
   234  }
   235  
   236  func TestStore_Prune(t *testing.T) {
   237  	store := setupStore(t)
   238  	// Pruning too many snapshots should be fine
   239  	pruned, err := store.Prune(4)
   240  	require.NoError(t, err)
   241  	assert.EqualValues(t, 0, pruned)
   242  
   243  	snapshots, err := store.List()
   244  	require.NoError(t, err)
   245  	assert.Len(t, snapshots, 4)
   246  
   247  	// Pruning until the last two heights should leave three snapshots (for two heights)
   248  	pruned, err = store.Prune(2)
   249  	require.NoError(t, err)
   250  	assert.EqualValues(t, 1, pruned)
   251  
   252  	snapshots, err = store.List()
   253  	require.NoError(t, err)
   254  	require.Equal(t, []*types.Snapshot{
   255  		{
   256  			Height: 3, Format: 2, Chunks: 3, Hash: hash([][]byte{{3, 2, 0}, {3, 2, 1}, {3, 2, 2}}),
   257  			Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{3, 2, 0}, {3, 2, 1}, {3, 2, 2}})},
   258  		},
   259  		{
   260  			Height: 2, Format: 2, Chunks: 3, Hash: hash([][]byte{{2, 2, 0}, {2, 2, 1}, {2, 2, 2}}),
   261  			Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{2, 2, 0}, {2, 2, 1}, {2, 2, 2}})},
   262  		},
   263  		{
   264  			Height: 2, Format: 1, Chunks: 2, Hash: hash([][]byte{{2, 1, 0}, {2, 1, 1}}),
   265  			Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{2, 1, 0}, {2, 1, 1}})},
   266  		},
   267  	}, snapshots)
   268  
   269  	// Pruning all heights should also be fine
   270  	pruned, err = store.Prune(0)
   271  	require.NoError(t, err)
   272  	assert.EqualValues(t, 3, pruned)
   273  
   274  	snapshots, err = store.List()
   275  	require.NoError(t, err)
   276  	assert.Empty(t, snapshots)
   277  }
   278  
   279  func TestStore_Save(t *testing.T) {
   280  	store := setupStore(t)
   281  	// Saving a snapshot should work
   282  	snapshot, err := store.Save(4, 1, makeChunks([][]byte{{1}, {2}}))
   283  	require.NoError(t, err)
   284  	assert.Equal(t, &types.Snapshot{
   285  		Height: 4,
   286  		Format: 1,
   287  		Chunks: 2,
   288  		Hash:   hash([][]byte{{1}, {2}}),
   289  		Metadata: types.Metadata{
   290  			ChunkHashes: checksums([][]byte{{1}, {2}}),
   291  		},
   292  	}, snapshot)
   293  	loaded, err := store.Get(snapshot.Height, snapshot.Format)
   294  	require.NoError(t, err)
   295  	assert.Equal(t, snapshot, loaded)
   296  
   297  	// Saving an existing snapshot should error
   298  	_, err = store.Save(4, 1, makeChunks([][]byte{{1}, {2}}))
   299  	require.Error(t, err)
   300  
   301  	// Saving at height 0 should error
   302  	_, err = store.Save(0, 1, makeChunks([][]byte{{1}, {2}}))
   303  	require.Error(t, err)
   304  
   305  	// Saving at format 0 should be fine
   306  	_, err = store.Save(1, 0, makeChunks([][]byte{{1}, {2}}))
   307  	require.NoError(t, err)
   308  
   309  	// Saving a snapshot with no chunks should be fine, as should loading it
   310  	_, err = store.Save(5, 1, makeChunks([][]byte{}))
   311  	require.NoError(t, err)
   312  	snapshot, chunks, err := store.Load(5, 1)
   313  	require.NoError(t, err)
   314  	assert.Equal(t, &types.Snapshot{Height: 5, Format: 1, Hash: hash([][]byte{}), Metadata: types.Metadata{ChunkHashes: [][]byte{}}}, snapshot)
   315  	assert.Empty(t, chunks)
   316  
   317  	// Saving a snapshot should error if a chunk reader returns an error, and it should empty out
   318  	// the channel
   319  	someErr := errors.New("boom")
   320  	pr, pw := io.Pipe()
   321  	err = pw.CloseWithError(someErr)
   322  	require.NoError(t, err)
   323  
   324  	ch := make(chan io.ReadCloser, 2)
   325  	ch <- pr
   326  	ch <- io.NopCloser(bytes.NewBuffer([]byte{0xff}))
   327  	close(ch)
   328  
   329  	_, err = store.Save(6, 1, ch)
   330  	require.Error(t, err)
   331  	require.True(t, errors.Is(err, someErr))
   332  	assert.Empty(t, ch)
   333  
   334  	// Saving a snapshot should error if a snapshot is already in progress for the same height,
   335  	// regardless of format. However, a different height should succeed.
   336  	ch = make(chan io.ReadCloser)
   337  	go store.Save(7, 1, ch)
   338  	time.Sleep(10 * time.Millisecond)
   339  	_, err = store.Save(7, 2, makeChunks(nil))
   340  	require.Error(t, err)
   341  	_, err = store.Save(8, 1, makeChunks(nil))
   342  	require.NoError(t, err)
   343  	close(ch)
   344  }