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