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 }