github.com/Finschia/finschia-sdk@v0.49.1/store/rootmulti/snapshot_test.go (about) 1 package rootmulti_test 2 3 import ( 4 "crypto/sha256" 5 "encoding/binary" 6 "encoding/hex" 7 "errors" 8 "fmt" 9 "io" 10 "math/rand" 11 "testing" 12 13 "github.com/Finschia/ostracon/libs/log" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 dbm "github.com/tendermint/tm-db" 17 18 "github.com/Finschia/finschia-sdk/snapshots" 19 snapshottypes "github.com/Finschia/finschia-sdk/snapshots/types" 20 "github.com/Finschia/finschia-sdk/store/iavl" 21 "github.com/Finschia/finschia-sdk/store/rootmulti" 22 "github.com/Finschia/finschia-sdk/store/types" 23 ) 24 25 func newMultiStoreWithGeneratedData(db dbm.DB, stores uint8, storeKeys uint64) *rootmulti.Store { 26 multiStore := rootmulti.NewStore(db, log.NewNopLogger()) 27 r := rand.New(rand.NewSource(49872768940)) // Fixed seed for deterministic tests 28 29 keys := []*types.KVStoreKey{} 30 for i := uint8(0); i < stores; i++ { 31 key := types.NewKVStoreKey(fmt.Sprintf("store%v", i)) 32 multiStore.MountStoreWithDB(key, types.StoreTypeIAVL, nil) 33 keys = append(keys, key) 34 } 35 err := multiStore.LoadLatestVersion() 36 if err != nil { 37 panic(err) 38 } 39 40 for _, key := range keys { 41 store := multiStore.GetCommitKVStore(key).(*iavl.Store) 42 for i := uint64(0); i < storeKeys; i++ { 43 k := make([]byte, 8) 44 v := make([]byte, 1024) 45 binary.BigEndian.PutUint64(k, i) 46 _, err := r.Read(v) 47 if err != nil { 48 panic(err) 49 } 50 store.Set(k, v) 51 } 52 } 53 54 multiStore.Commit() 55 err = multiStore.LoadLatestVersion() 56 if err != nil { 57 panic(err) 58 } 59 60 return multiStore 61 } 62 63 func newMultiStoreWithMixedMounts(db dbm.DB) *rootmulti.Store { 64 store := rootmulti.NewStore(db, log.NewNopLogger()) 65 store.MountStoreWithDB(types.NewKVStoreKey("iavl1"), types.StoreTypeIAVL, nil) 66 store.MountStoreWithDB(types.NewKVStoreKey("iavl2"), types.StoreTypeIAVL, nil) 67 store.MountStoreWithDB(types.NewKVStoreKey("iavl3"), types.StoreTypeIAVL, nil) 68 store.MountStoreWithDB(types.NewTransientStoreKey("trans1"), types.StoreTypeTransient, nil) 69 err := store.LoadLatestVersion() 70 if err != nil { 71 panic(err) 72 } 73 74 return store 75 } 76 77 func newMultiStoreWithMixedMountsAndBasicData(db dbm.DB) *rootmulti.Store { 78 store := newMultiStoreWithMixedMounts(db) 79 store1 := store.GetStoreByName("iavl1").(types.CommitKVStore) 80 store2 := store.GetStoreByName("iavl2").(types.CommitKVStore) 81 trans1 := store.GetStoreByName("trans1").(types.KVStore) 82 83 store1.Set([]byte("a"), []byte{1}) 84 store1.Set([]byte("b"), []byte{1}) 85 store2.Set([]byte("X"), []byte{255}) 86 store2.Set([]byte("A"), []byte{101}) 87 trans1.Set([]byte("x1"), []byte{91}) 88 store.Commit() 89 90 store1.Set([]byte("b"), []byte{2}) 91 store1.Set([]byte("c"), []byte{3}) 92 store2.Set([]byte("B"), []byte{102}) 93 store.Commit() 94 95 store2.Set([]byte("C"), []byte{103}) 96 store2.Delete([]byte("X")) 97 trans1.Set([]byte("x2"), []byte{92}) 98 store.Commit() 99 100 return store 101 } 102 103 func assertStoresEqual(t *testing.T, expect, actual types.CommitKVStore, msgAndArgs ...interface{}) { 104 t.Helper() 105 assert.Equal(t, expect.LastCommitID(), actual.LastCommitID()) 106 expectIter := expect.Iterator(nil, nil) 107 expectMap := map[string][]byte{} 108 for ; expectIter.Valid(); expectIter.Next() { 109 expectMap[string(expectIter.Key())] = expectIter.Value() 110 } 111 require.NoError(t, expectIter.Error()) 112 113 actualIter := expect.Iterator(nil, nil) 114 actualMap := map[string][]byte{} 115 for ; actualIter.Valid(); actualIter.Next() { 116 actualMap[string(actualIter.Key())] = actualIter.Value() 117 } 118 require.NoError(t, actualIter.Error()) 119 120 assert.Equal(t, expectMap, actualMap, msgAndArgs...) 121 } 122 123 func TestMultistoreSnapshot_Checksum(t *testing.T) { 124 // Chunks from different nodes must fit together, so all nodes must produce identical chunks. 125 // This checksum test makes sure that the byte stream remains identical. If the test fails 126 // without having changed the data (e.g. because the Protobuf or zlib encoding changes), 127 // snapshottypes.CurrentFormat must be bumped. 128 store := newMultiStoreWithGeneratedData(dbm.NewMemDB(), 5, 10000) 129 version := uint64(store.LastCommitID().Version) 130 131 testcases := []struct { 132 format uint32 133 chunkHashes []string 134 }{ 135 {1, []string{ 136 "503e5b51b657055b77e88169fadae543619368744ad15f1de0736c0a20482f24", 137 "e1a0daaa738eeb43e778aefd2805e3dd720798288a410b06da4b8459c4d8f72e", 138 "aa048b4ee0f484965d7b3b06822cf0772cdcaad02f3b1b9055e69f2cb365ef3c", 139 "7921eaa3ed4921341e504d9308a9877986a879fe216a099c86e8db66fcba4c63", 140 "a4a864e6c02c9fca5837ec80dc84f650b25276ed7e4820cf7516ced9f9901b86", 141 "8ca5b957e36fa13e704c31494649b2a74305148d70d70f0f26dee066b615c1d0", 142 }}, 143 } 144 for _, tc := range testcases { 145 t.Run(fmt.Sprintf("Format %v", tc.format), func(t *testing.T) { 146 ch := make(chan io.ReadCloser) 147 go func() { 148 streamWriter := snapshots.NewStreamWriter(ch) 149 defer streamWriter.Close() 150 require.NotNil(t, streamWriter) 151 err := store.Snapshot(version, streamWriter) 152 require.NoError(t, err) 153 }() 154 hashes := []string{} 155 hasher := sha256.New() 156 for chunk := range ch { 157 hasher.Reset() 158 _, err := io.Copy(hasher, chunk) 159 require.NoError(t, err) 160 hashes = append(hashes, hex.EncodeToString(hasher.Sum(nil))) 161 } 162 assert.Equal(t, tc.chunkHashes, hashes, 163 "Snapshot output for format %v has changed", tc.format) 164 }) 165 } 166 } 167 168 func TestMultistoreSnapshot_Errors(t *testing.T) { 169 store := newMultiStoreWithMixedMountsAndBasicData(dbm.NewMemDB()) 170 171 testcases := map[string]struct { 172 height uint64 173 expectType error 174 }{ 175 "0 height": {0, nil}, 176 "unknown height": {9, nil}, 177 } 178 for name, tc := range testcases { 179 t.Run(name, func(t *testing.T) { 180 err := store.Snapshot(tc.height, nil) 181 require.Error(t, err) 182 if tc.expectType != nil { 183 assert.True(t, errors.Is(err, tc.expectType)) 184 } 185 }) 186 } 187 } 188 189 func TestMultistoreSnapshotRestore(t *testing.T) { 190 source := newMultiStoreWithMixedMountsAndBasicData(dbm.NewMemDB()) 191 target := newMultiStoreWithMixedMounts(dbm.NewMemDB()) 192 version := uint64(source.LastCommitID().Version) 193 require.EqualValues(t, 3, version) 194 dummyExtensionItem := snapshottypes.SnapshotItem{ 195 Item: &snapshottypes.SnapshotItem_Extension{ 196 Extension: &snapshottypes.SnapshotExtensionMeta{ 197 Name: "test", 198 Format: 1, 199 }, 200 }, 201 } 202 203 chunks := make(chan io.ReadCloser, 100) 204 go func() { 205 streamWriter := snapshots.NewStreamWriter(chunks) 206 require.NotNil(t, streamWriter) 207 defer streamWriter.Close() 208 err := source.Snapshot(version, streamWriter) 209 require.NoError(t, err) 210 // write an extension metadata 211 err = streamWriter.WriteMsg(&dummyExtensionItem) 212 require.NoError(t, err) 213 }() 214 215 streamReader, err := snapshots.NewStreamReader(chunks) 216 require.NoError(t, err) 217 nextItem, err := target.Restore(version, snapshottypes.CurrentFormat, streamReader) 218 require.NoError(t, err) 219 require.Equal(t, *dummyExtensionItem.GetExtension(), *nextItem.GetExtension()) 220 221 assert.Equal(t, source.LastCommitID(), target.LastCommitID()) 222 for key, sourceStore := range source.GetStores() { 223 targetStore := target.GetStoreByName(key.Name()).(types.CommitKVStore) 224 switch sourceStore.GetStoreType() { 225 case types.StoreTypeTransient: 226 assert.False(t, targetStore.Iterator(nil, nil).Valid(), 227 "transient store %v not empty", key.Name()) 228 default: 229 assertStoresEqual(t, sourceStore, targetStore, "store %q not equal", key.Name()) 230 } 231 } 232 } 233 234 func benchmarkMultistoreSnapshot(b *testing.B, stores uint8, storeKeys uint64) { 235 b.Helper() 236 b.Skip("Noisy with slow setup time, please see https://github.com/cosmos/cosmos-sdk/issues/8855.") 237 238 b.ReportAllocs() 239 b.StopTimer() 240 source := newMultiStoreWithGeneratedData(dbm.NewMemDB(), stores, storeKeys) 241 version := source.LastCommitID().Version 242 require.EqualValues(b, 1, version) 243 b.StartTimer() 244 245 for i := 0; i < b.N; i++ { 246 target := rootmulti.NewStore(dbm.NewMemDB(), log.NewNopLogger()) 247 for key := range source.GetStores() { 248 target.MountStoreWithDB(key, types.StoreTypeIAVL, nil) 249 } 250 err := target.LoadLatestVersion() 251 require.NoError(b, err) 252 require.EqualValues(b, 0, target.LastCommitID().Version) 253 254 chunks := make(chan io.ReadCloser) 255 go func() { 256 streamWriter := snapshots.NewStreamWriter(chunks) 257 require.NotNil(b, streamWriter) 258 err := source.Snapshot(uint64(version), streamWriter) 259 require.NoError(b, err) 260 }() 261 for reader := range chunks { 262 _, err := io.Copy(io.Discard, reader) 263 require.NoError(b, err) 264 err = reader.Close() 265 require.NoError(b, err) 266 } 267 } 268 } 269 270 func benchmarkMultistoreSnapshotRestore(b *testing.B, stores uint8, storeKeys uint64) { 271 b.Helper() 272 b.Skip("Noisy with slow setup time, please see https://github.com/cosmos/cosmos-sdk/issues/8855.") 273 274 b.ReportAllocs() 275 b.StopTimer() 276 source := newMultiStoreWithGeneratedData(dbm.NewMemDB(), stores, storeKeys) 277 version := uint64(source.LastCommitID().Version) 278 require.EqualValues(b, 1, version) 279 b.StartTimer() 280 281 for i := 0; i < b.N; i++ { 282 target := rootmulti.NewStore(dbm.NewMemDB(), log.NewNopLogger()) 283 for key := range source.GetStores() { 284 target.MountStoreWithDB(key, types.StoreTypeIAVL, nil) 285 } 286 err := target.LoadLatestVersion() 287 require.NoError(b, err) 288 require.EqualValues(b, 0, target.LastCommitID().Version) 289 290 chunks := make(chan io.ReadCloser) 291 go func() { 292 writer := snapshots.NewStreamWriter(chunks) 293 require.NotNil(b, writer) 294 err := source.Snapshot(version, writer) 295 require.NoError(b, err) 296 }() 297 reader, err := snapshots.NewStreamReader(chunks) 298 require.NoError(b, err) 299 _, err = target.Restore(version, snapshottypes.CurrentFormat, reader) 300 require.NoError(b, err) 301 require.Equal(b, source.LastCommitID(), target.LastCommitID()) 302 } 303 } 304 305 func BenchmarkMultistoreSnapshot100K(b *testing.B) { 306 benchmarkMultistoreSnapshot(b, 10, 10000) 307 } 308 309 func BenchmarkMultistoreSnapshot1M(b *testing.B) { 310 benchmarkMultistoreSnapshot(b, 10, 100000) 311 } 312 313 func BenchmarkMultistoreSnapshotRestore100K(b *testing.B) { 314 benchmarkMultistoreSnapshotRestore(b, 10, 10000) 315 } 316 317 func BenchmarkMultistoreSnapshotRestore1M(b *testing.B) { 318 benchmarkMultistoreSnapshotRestore(b, 10, 100000) 319 }