github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/store/rootmulti/store_test.go (about)

     1  package rootmulti
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  
     9  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
    10  	"github.com/gnolang/gno/tm2/pkg/crypto/merkle"
    11  	dbm "github.com/gnolang/gno/tm2/pkg/db"
    12  	"github.com/gnolang/gno/tm2/pkg/db/memdb"
    13  
    14  	"github.com/gnolang/gno/tm2/pkg/store/iavl"
    15  	"github.com/gnolang/gno/tm2/pkg/store/types"
    16  )
    17  
    18  func TestStoreType(t *testing.T) {
    19  	t.Parallel()
    20  
    21  	db := memdb.NewMemDB()
    22  	store := NewMultiStore(db)
    23  	store.MountStoreWithDB(
    24  		types.NewStoreKey("store1"), iavl.StoreConstructor, db)
    25  }
    26  
    27  func TestStoreMount(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	db := memdb.NewMemDB()
    31  	store := NewMultiStore(db)
    32  
    33  	key1 := types.NewStoreKey("store1")
    34  	key2 := types.NewStoreKey("store2")
    35  	dup1 := types.NewStoreKey("store1")
    36  
    37  	require.NotPanics(t, func() { store.MountStoreWithDB(key1, iavl.StoreConstructor, db) })
    38  	require.NotPanics(t, func() { store.MountStoreWithDB(key2, iavl.StoreConstructor, db) })
    39  
    40  	require.Panics(t, func() { store.MountStoreWithDB(key1, iavl.StoreConstructor, db) })
    41  	require.Panics(t, func() { store.MountStoreWithDB(dup1, iavl.StoreConstructor, db) })
    42  }
    43  
    44  func TestCacheMultiStoreWithVersion(t *testing.T) {
    45  	t.Parallel()
    46  
    47  	var db dbm.DB = memdb.NewMemDB()
    48  	ms := newMultiStoreWithMounts(db)
    49  	err := ms.LoadLatestVersion()
    50  	require.Nil(t, err)
    51  
    52  	commitID := types.CommitID{}
    53  	checkStore(t, ms, commitID, commitID)
    54  
    55  	k, v := []byte("wind"), []byte("blows")
    56  
    57  	store1 := ms.getStoreByName("store1").(types.Store)
    58  	store1.Set(k, v)
    59  
    60  	cID := ms.Commit()
    61  	require.Equal(t, int64(1), cID.Version)
    62  
    63  	// require failure when given an invalid or pruned version
    64  	_, err = ms.MultiImmutableCacheWrapWithVersion(cID.Version + 1)
    65  	require.Error(t, err)
    66  
    67  	// require a valid version can be cache-loaded
    68  	cms, err := ms.MultiImmutableCacheWrapWithVersion(cID.Version)
    69  	require.NoError(t, err)
    70  
    71  	// require a valid key lookup yields the correct value
    72  	kvStore := cms.GetStore(ms.keysByName["store1"])
    73  	require.NotNil(t, kvStore)
    74  	require.Equal(t, kvStore.Get(k), v)
    75  
    76  	// require we cannot commit (write) to a cache-versioned multi-store
    77  	require.Panics(t, func() {
    78  		kvStore.Set(k, []byte("newValue"))
    79  		cms.(types.Committer).Commit()
    80  	})
    81  }
    82  
    83  func TestHashStableWithEmptyCommit(t *testing.T) {
    84  	t.Parallel()
    85  
    86  	var db dbm.DB = memdb.NewMemDB()
    87  	ms := newMultiStoreWithMounts(db)
    88  	err := ms.LoadLatestVersion()
    89  	require.Nil(t, err)
    90  
    91  	commitID := types.CommitID{}
    92  	checkStore(t, ms, commitID, commitID)
    93  
    94  	k, v := []byte("wind"), []byte("blows")
    95  
    96  	store1 := ms.getStoreByName("store1").(types.Store)
    97  	store1.Set(k, v)
    98  
    99  	cID := ms.Commit()
   100  	require.Equal(t, int64(1), cID.Version)
   101  	hash := cID.Hash
   102  
   103  	// make an empty commit, it should update version, but not affect hash
   104  	cID = ms.Commit()
   105  	require.Equal(t, int64(2), cID.Version)
   106  	require.Equal(t, hash, cID.Hash)
   107  }
   108  
   109  func TestMultistoreCommitLoad(t *testing.T) {
   110  	t.Parallel()
   111  
   112  	var db dbm.DB = memdb.NewMemDB()
   113  	store := newMultiStoreWithMounts(db)
   114  	err := store.LoadLatestVersion()
   115  	require.Nil(t, err)
   116  
   117  	// New store has empty last commit.
   118  	commitID := types.CommitID{}
   119  	checkStore(t, store, commitID, commitID)
   120  
   121  	// Make sure we can get stores by name.
   122  	s1 := store.getStoreByName("store1")
   123  	require.NotNil(t, s1)
   124  	s3 := store.getStoreByName("store3")
   125  	require.NotNil(t, s3)
   126  	s77 := store.getStoreByName("store77")
   127  	require.Nil(t, s77)
   128  
   129  	// Make a few commits and check them.
   130  	nCommits := int64(3)
   131  	for i := int64(0); i < nCommits; i++ {
   132  		commitID = store.Commit()
   133  		expectedCommitID := getExpectedCommitID(store, i+1)
   134  		checkStore(t, store, expectedCommitID, commitID)
   135  	}
   136  
   137  	// Load the latest multistore again and check version.
   138  	store = newMultiStoreWithMounts(db)
   139  	err = store.LoadLatestVersion()
   140  	require.Nil(t, err)
   141  	commitID = getExpectedCommitID(store, nCommits)
   142  	checkStore(t, store, commitID, commitID)
   143  
   144  	// Commit and check version.
   145  	commitID = store.Commit()
   146  	expectedCommitID := getExpectedCommitID(store, nCommits+1)
   147  	checkStore(t, store, expectedCommitID, commitID)
   148  
   149  	// Load an older multistore and check version.
   150  	ver := nCommits - 1
   151  	store = newMultiStoreWithMounts(db)
   152  	err = store.LoadVersion(ver)
   153  	require.Nil(t, err)
   154  	commitID = getExpectedCommitID(store, ver)
   155  	checkStore(t, store, commitID, commitID)
   156  
   157  	// XXX: commit this older version
   158  	commitID = store.Commit()
   159  	expectedCommitID = getExpectedCommitID(store, ver+1)
   160  	checkStore(t, store, expectedCommitID, commitID)
   161  
   162  	// XXX: confirm old commit is overwritten and we have rolled back
   163  	// LatestVersion
   164  	store = newMultiStoreWithMounts(db)
   165  	err = store.LoadLatestVersion()
   166  	require.Nil(t, err)
   167  	commitID = getExpectedCommitID(store, ver+1)
   168  	checkStore(t, store, commitID, commitID)
   169  }
   170  
   171  func TestParsePath(t *testing.T) {
   172  	t.Parallel()
   173  
   174  	_, _, err := parsePath("foo")
   175  	require.Error(t, err)
   176  
   177  	store, subpath, err := parsePath("/foo")
   178  	require.NoError(t, err)
   179  	require.Equal(t, store, "foo")
   180  	require.Equal(t, subpath, "")
   181  
   182  	store, subpath, err = parsePath("/fizz/bang/baz")
   183  	require.NoError(t, err)
   184  	require.Equal(t, store, "fizz")
   185  	require.Equal(t, subpath, "/bang/baz")
   186  
   187  	substore, subsubpath, err := parsePath(subpath)
   188  	require.NoError(t, err)
   189  	require.Equal(t, substore, "bang")
   190  	require.Equal(t, subsubpath, "/baz")
   191  }
   192  
   193  func TestMultiStoreQuery(t *testing.T) {
   194  	t.Parallel()
   195  
   196  	db := memdb.NewMemDB()
   197  	multi := newMultiStoreWithMounts(db)
   198  	err := multi.LoadLatestVersion()
   199  	require.Nil(t, err)
   200  
   201  	k, v := []byte("wind"), []byte("blows")
   202  	k2, v2 := []byte("water"), []byte("flows")
   203  	// v3 := []byte("is cold")
   204  
   205  	cid := multi.Commit()
   206  
   207  	// Make sure we can get by name.
   208  	garbage := multi.getStoreByName("bad-name")
   209  	require.Nil(t, garbage)
   210  
   211  	// Set and commit data in one store.
   212  	store1 := multi.getStoreByName("store1").(types.Store)
   213  	store1.Set(k, v)
   214  
   215  	// ... and another.
   216  	store2 := multi.getStoreByName("store2").(types.Store)
   217  	store2.Set(k2, v2)
   218  
   219  	// Commit the multistore.
   220  	cid = multi.Commit()
   221  	ver := cid.Version
   222  
   223  	// Reload multistore from database
   224  	multi = newMultiStoreWithMounts(db)
   225  	err = multi.LoadLatestVersion()
   226  	require.Nil(t, err)
   227  
   228  	// Test bad path.
   229  	query := abci.RequestQuery{Path: "/key", Data: k, Height: ver}
   230  	qres := multi.Query(query)
   231  	require.True(t, strings.HasPrefix(qres.Error.Error(), "unknownrequest error:"))
   232  
   233  	query.Path = "h897fy32890rf63296r92"
   234  	qres = multi.Query(query)
   235  	require.True(t, strings.HasPrefix(qres.Error.Error(), "unknownrequest error:"))
   236  
   237  	// Test invalid store name.
   238  	query.Path = "/garbage/key"
   239  	qres = multi.Query(query)
   240  	require.True(t, strings.HasPrefix(qres.Error.Error(), "unknownrequest error:"))
   241  
   242  	// Test valid query with data.
   243  	query.Path = "/store1/key"
   244  	qres = multi.Query(query)
   245  	require.Nil(t, qres.Error)
   246  	require.Equal(t, v, qres.Value)
   247  
   248  	// Test valid but empty query.
   249  	query.Path = "/store2/key"
   250  	query.Prove = true
   251  	qres = multi.Query(query)
   252  	require.Nil(t, qres.Error)
   253  	require.Nil(t, qres.Value)
   254  
   255  	// Test store2 data.
   256  	query.Data = k2
   257  	qres = multi.Query(query)
   258  	require.Nil(t, qres.Error)
   259  	require.Equal(t, v2, qres.Value)
   260  }
   261  
   262  // -----------------------------------------------------------------------
   263  // utils
   264  
   265  func newMultiStoreWithMounts(db dbm.DB) *multiStore {
   266  	store := NewMultiStore(db)
   267  	store.storeOpts = types.StoreOptions{PruningOptions: types.PruneSyncable}
   268  	store.MountStoreWithDB(
   269  		types.NewStoreKey("store1"), iavl.StoreConstructor, nil)
   270  	store.MountStoreWithDB(
   271  		types.NewStoreKey("store2"), iavl.StoreConstructor, nil)
   272  	store.MountStoreWithDB(
   273  		types.NewStoreKey("store3"), iavl.StoreConstructor, nil)
   274  	return store
   275  }
   276  
   277  func checkStore(t *testing.T, store *multiStore, expect, got types.CommitID) {
   278  	t.Helper()
   279  
   280  	require.Equal(t, expect, got)
   281  	require.Equal(t, expect, store.LastCommitID())
   282  }
   283  
   284  func getExpectedCommitID(store *multiStore, ver int64) types.CommitID {
   285  	return types.CommitID{
   286  		Version: ver,
   287  		Hash:    hashStores(store.stores),
   288  	}
   289  }
   290  
   291  func hashStores(stores map[types.StoreKey]types.CommitStore) []byte {
   292  	m := make(map[string][]byte, len(stores))
   293  	for key, store := range stores {
   294  		name := key.Name()
   295  		m[name] = storeInfo{
   296  			Name: name,
   297  			Core: storeCore{
   298  				CommitID: store.LastCommitID(),
   299  				// StoreType: store.GetStoreType(),
   300  			},
   301  		}.Hash()
   302  	}
   303  	return merkle.SimpleHashFromMap(m)
   304  }