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  }