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  }