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

     1  package rootmulti
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/gnolang/gno/tm2/pkg/amino"
     8  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
     9  	"github.com/gnolang/gno/tm2/pkg/crypto/merkle"
    10  	"github.com/gnolang/gno/tm2/pkg/crypto/tmhash"
    11  	dbm "github.com/gnolang/gno/tm2/pkg/db"
    12  	"github.com/gnolang/gno/tm2/pkg/errors"
    13  
    14  	"github.com/gnolang/gno/tm2/pkg/store/cachemulti"
    15  	serrors "github.com/gnolang/gno/tm2/pkg/store/errors"
    16  	"github.com/gnolang/gno/tm2/pkg/store/immut"
    17  	"github.com/gnolang/gno/tm2/pkg/store/types"
    18  )
    19  
    20  const (
    21  	latestVersionKey = "s/latest"
    22  	commitInfoKeyFmt = "s/%d" // s/<version>
    23  )
    24  
    25  // multiStore is composed of many CommitStores. Name contrasts with
    26  // cacheMultiStore which is for cache-wrapping other MultiStores. It implements
    27  // the CommitMultiStore interface.
    28  type multiStore struct {
    29  	db           dbm.DB
    30  	lastCommitID types.CommitID
    31  	storeOpts    types.StoreOptions
    32  	storesParams map[types.StoreKey]storeParams
    33  	stores       map[types.StoreKey]types.CommitStore
    34  	keysByName   map[string]types.StoreKey
    35  }
    36  
    37  var (
    38  	_ types.CommitMultiStore = (*multiStore)(nil)
    39  	_ types.Queryable        = (*multiStore)(nil)
    40  )
    41  
    42  func NewMultiStore(db dbm.DB) *multiStore {
    43  	return &multiStore{
    44  		db:           db,
    45  		storesParams: make(map[types.StoreKey]storeParams),
    46  		stores:       make(map[types.StoreKey]types.CommitStore),
    47  		keysByName:   make(map[string]types.StoreKey),
    48  	}
    49  }
    50  
    51  // Implements CommitMultiStore
    52  func (ms *multiStore) GetStoreOptions() types.StoreOptions {
    53  	return ms.storeOpts
    54  }
    55  
    56  // Implements CommitMultiStore
    57  func (ms *multiStore) SetStoreOptions(opts types.StoreOptions) {
    58  	ms.storeOpts = opts
    59  	for _, store := range ms.stores {
    60  		store.SetStoreOptions(opts)
    61  	}
    62  }
    63  
    64  // SetLazyLoad sets if the store should be loaded lazily or not
    65  func (ms *multiStore) SetLazyLoad(lazyLoad bool) {
    66  	ms.storeOpts.LazyLoad = lazyLoad
    67  }
    68  
    69  // Implements CommitMultiStore.
    70  func (ms *multiStore) MountStoreWithDB(key types.StoreKey, cons types.CommitStoreConstructor, db dbm.DB) {
    71  	if key == nil {
    72  		panic("MountIAVLStore() key cannot be nil")
    73  	}
    74  	if _, ok := ms.storesParams[key]; ok {
    75  		panic(fmt.Sprintf("Store duplicate store key %v", key))
    76  	}
    77  	if _, ok := ms.keysByName[key.Name()]; ok {
    78  		panic(fmt.Sprintf("Store duplicate store key name %v", key))
    79  	}
    80  	ms.storesParams[key] = storeParams{
    81  		key:         key,
    82  		constructor: cons,
    83  		db:          db,
    84  	}
    85  	ms.keysByName[key.Name()] = key
    86  }
    87  
    88  // Implements CommitMultiStore.
    89  func (ms *multiStore) GetCommitStore(key types.StoreKey) types.CommitStore {
    90  	return ms.stores[key]
    91  }
    92  
    93  // Implements CommitMultiStore.
    94  func (ms *multiStore) LoadLatestVersion() error {
    95  	ver := getLatestVersion(ms.db)
    96  	return ms.LoadVersion(ver)
    97  }
    98  
    99  // Implements CommitMultiStore.
   100  func (ms *multiStore) LoadVersion(ver int64) error {
   101  	if ver == 0 {
   102  		// Special logic for version 0 where there is no need to get commit
   103  		// information.
   104  		for key, storeParams := range ms.storesParams {
   105  			store, err := ms.constructStore(storeParams)
   106  			if err != nil {
   107  				return errors.New("failed to load Store: %v", err)
   108  			}
   109  			store.SetStoreOptions(ms.storeOpts)
   110  			err = store.LoadVersion(ver)
   111  			if err != nil {
   112  				return errors.New("failed to load Store version %d: %v", ver, err)
   113  			}
   114  			if !store.LastCommitID().IsZero() {
   115  				return errors.New("failed to load Store: non-empty CommitID for zero state")
   116  			}
   117  			ms.stores[key] = store
   118  		}
   119  		ms.lastCommitID = types.CommitID{}
   120  		return nil
   121  	}
   122  
   123  	// Load store commit infos @ version ver.
   124  	cInfo, err := getCommitInfo(ms.db, ver)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	// Convert StoreInfos slice to map.
   130  	infos := make(map[types.StoreKey]storeInfo)
   131  	for _, storeInfo := range cInfo.StoreInfos {
   132  		infos[ms.nameToKey(storeInfo.Name)] = storeInfo
   133  	}
   134  
   135  	// Load each Store and check CommitID for each.
   136  	newStores := make(map[types.StoreKey]types.CommitStore)
   137  	for key, storeParams := range ms.storesParams {
   138  		var id types.CommitID
   139  		if info, ok := infos[key]; ok {
   140  			id = info.Core.CommitID
   141  		}
   142  		store, err := ms.constructStore(storeParams)
   143  		if err != nil {
   144  			return fmt.Errorf("failed to load Store: %w", err)
   145  		}
   146  		store.SetStoreOptions(ms.storeOpts)
   147  		err = store.LoadVersion(ver)
   148  		if err != nil {
   149  			return errors.New("failed to load Store version %d: %v", ver, err)
   150  		}
   151  		if !store.LastCommitID().Equals(id) {
   152  			return errors.New("failed to load Store: wrong commit id: %v vs %v",
   153  				store.LastCommitID(),
   154  				id)
   155  		}
   156  		newStores[key] = store
   157  	}
   158  
   159  	ms.lastCommitID = cInfo.CommitID()
   160  	ms.stores = newStores
   161  
   162  	return nil
   163  }
   164  
   165  // ----------------------------------------
   166  // +CommitStore
   167  
   168  // Implements Committer/CommitStore.
   169  func (ms *multiStore) LastCommitID() types.CommitID {
   170  	return ms.lastCommitID
   171  }
   172  
   173  // Implements Committer/CommitStore.
   174  func (ms *multiStore) Commit() types.CommitID {
   175  	// Commit stores.
   176  	version := ms.lastCommitID.Version + 1
   177  	commitInfo := commitStores(version, ms.stores)
   178  
   179  	// Need to update atomically.
   180  	batch := ms.db.NewBatch()
   181  	defer batch.Close()
   182  	setCommitInfo(batch, version, commitInfo)
   183  	setLatestVersion(batch, version)
   184  	batch.Write()
   185  
   186  	// Prepare for next version.
   187  	commitID := types.CommitID{
   188  		Version: version,
   189  		Hash:    commitInfo.Hash(),
   190  	}
   191  	ms.lastCommitID = commitID
   192  	return commitID
   193  }
   194  
   195  // ----------------------------------------
   196  // +MultiStore
   197  
   198  // Implements MultiStore.
   199  func (ms *multiStore) MultiCacheWrap() types.MultiStore {
   200  	stores := make(map[types.StoreKey]types.Store)
   201  	for k, v := range ms.stores {
   202  		stores[k] = v
   203  	}
   204  
   205  	return cachemulti.New(stores, ms.keysByName)
   206  }
   207  
   208  // Implements MultiStore.
   209  func (ms *multiStore) MultiWrite() {
   210  	panic("unexpected .MultiWrite() on rootmulti.Store. Commit()?")
   211  }
   212  
   213  // Implements CommitMultiStore.
   214  func (ms *multiStore) MultiImmutableCacheWrapWithVersion(version int64) (types.MultiStore, error) {
   215  	ims := &multiStore{
   216  		db:           dbm.NewImmutableDB(ms.db),
   217  		storeOpts:    ms.storeOpts,
   218  		storesParams: ms.storesParams,
   219  		keysByName:   ms.keysByName,
   220  	}
   221  	ims.storeOpts.Immutable = true
   222  	err := ims.LoadVersion(version)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	stores := make(map[types.StoreKey]types.Store, len(ims.stores))
   227  	for storeKey, store := range ims.stores {
   228  		stores[storeKey] = immut.New(store)
   229  	}
   230  	return cachemulti.New(stores, ims.keysByName), nil
   231  }
   232  
   233  // Implements MultiStore.
   234  // If the store does not exist, panics.
   235  func (ms *multiStore) GetStore(key types.StoreKey) types.Store {
   236  	store := ms.stores[key]
   237  	if store == nil {
   238  		panic("Could not load store " + key.String())
   239  	}
   240  	return store
   241  }
   242  
   243  // Implements MultiStore
   244  
   245  // getStoreByName will first convert the original name to
   246  // a special key, before looking up the CommitStore.
   247  // This is not exposed to the extensions (which will need the
   248  // StoreKey), but is useful in main, and particularly app.Query,
   249  // in order to convert human strings into CommitStores.
   250  func (ms *multiStore) getStoreByName(name string) types.Store {
   251  	key := ms.keysByName[name]
   252  	if key == nil {
   253  		return nil
   254  	}
   255  	return ms.stores[key]
   256  }
   257  
   258  // ---------------------- Query ------------------
   259  
   260  // Query calls substore.Query with the same `req` where `req.Path` is
   261  // modified to remove the substore prefix.
   262  // Ie. `req.Path` here is `/<substore>/<path>`, and trimmed to `/<path>` for the substore.
   263  // TODO: add proof for `multistore -> substore`.
   264  func (ms *multiStore) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
   265  	// Query just routes this to a substore.
   266  	path := req.Path
   267  	storeName, subpath, err := parsePath(path)
   268  	if err != nil {
   269  		res.Error = err
   270  		return
   271  	}
   272  
   273  	store := ms.getStoreByName(storeName)
   274  	if store == nil {
   275  		msg := fmt.Sprintf("no such store: %s", storeName)
   276  		res.Error = serrors.ErrUnknownRequest(msg)
   277  		return
   278  	}
   279  
   280  	queryable, ok := store.(types.Queryable)
   281  	if !ok {
   282  		msg := fmt.Sprintf("store %s doesn't support queries", storeName)
   283  		res.Error = serrors.ErrUnknownRequest(msg)
   284  		return
   285  	}
   286  
   287  	// trim the path and make the query
   288  	req.Path = subpath
   289  	res = queryable.Query(req)
   290  
   291  	if !req.Prove {
   292  		return res
   293  	} else if res.Proof == nil || len(res.Proof.Ops) == 0 {
   294  		res.Error = serrors.ErrInternal("proof is unexpectedly empty; ensure height has not been pruned")
   295  		return
   296  	}
   297  
   298  	commitInfo, errMsg := getCommitInfo(ms.db, res.Height)
   299  	if errMsg != nil {
   300  		res.Error = serrors.ErrInternal(errMsg.Error())
   301  		return
   302  	}
   303  
   304  	// Restore origin path and append proof op.
   305  	res.Proof.Ops = append(res.Proof.Ops, NewMultiStoreProofOp(
   306  		[]byte(storeName),
   307  		NewMultiStoreProof(commitInfo.StoreInfos),
   308  	).ProofOp())
   309  
   310  	// TODO: handle in another TM v0.26 update PR
   311  	// res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos)
   312  	return res
   313  }
   314  
   315  // parsePath expects a format like /<storeName>[/<subpath>]
   316  // Must start with /, subpath may be empty
   317  // Returns error if it doesn't start with /
   318  func parsePath(path string) (storeName string, subpath string, err serrors.Error) {
   319  	if !strings.HasPrefix(path, "/") {
   320  		err = serrors.ErrUnknownRequest(fmt.Sprintf("invalid path: %s", path))
   321  		return
   322  	}
   323  
   324  	paths := strings.SplitN(path[1:], "/", 2)
   325  	storeName = paths[0]
   326  
   327  	if len(paths) == 2 {
   328  		subpath = "/" + paths[1]
   329  	}
   330  
   331  	return
   332  }
   333  
   334  // ----------------------------------------
   335  
   336  func (ms *multiStore) constructStore(params storeParams) (store types.CommitStore, err error) {
   337  	var db dbm.DB
   338  	if params.db != nil {
   339  		db = dbm.NewPrefixDB(params.db, []byte("s/_/"))
   340  	} else {
   341  		db = dbm.NewPrefixDB(ms.db, []byte("s/k:"+params.key.Name()+"/"))
   342  	}
   343  	opts := ms.storeOpts
   344  
   345  	// XXX: use these:
   346  	// return iavl.LoadStore(db, id, ms.pruningOpts, ms.lazyLoading)
   347  	// return commitDBStoreAdapter{dbadapter.Store{db}}, nil
   348  	store = params.constructor(db, opts)
   349  	return store, nil
   350  }
   351  
   352  func (ms *multiStore) nameToKey(name string) types.StoreKey {
   353  	for key := range ms.storesParams {
   354  		if key.Name() == name {
   355  			return key
   356  		}
   357  	}
   358  	panic("Unknown name " + name)
   359  }
   360  
   361  // ----------------------------------------
   362  // storeParams
   363  
   364  type storeParams struct {
   365  	key         types.StoreKey
   366  	constructor types.CommitStoreConstructor
   367  	db          dbm.DB
   368  }
   369  
   370  // ----------------------------------------
   371  // commitInfo
   372  
   373  // NOTE: Keep commitInfo a simple immutable struct.
   374  type commitInfo struct {
   375  	// Version
   376  	Version int64
   377  
   378  	// Store info for
   379  	StoreInfos []storeInfo
   380  }
   381  
   382  // Hash returns the simple merkle root hash of the stores sorted by name.
   383  func (ci commitInfo) Hash() []byte {
   384  	// TODO: cache to ci.hash []byte
   385  	m := make(map[string][]byte, len(ci.StoreInfos))
   386  	for _, storeInfo := range ci.StoreInfos {
   387  		m[storeInfo.Name] = storeInfo.Hash()
   388  	}
   389  
   390  	return merkle.SimpleHashFromMap(m)
   391  }
   392  
   393  func (ci commitInfo) CommitID() types.CommitID {
   394  	return types.CommitID{
   395  		Version: ci.Version,
   396  		Hash:    ci.Hash(),
   397  	}
   398  }
   399  
   400  // ----------------------------------------
   401  // storeInfo
   402  
   403  // storeInfo contains the name and core reference for an
   404  // underlying store.  It is the leaf of the Stores top
   405  // level simple merkle tree.
   406  type storeInfo struct {
   407  	Name string
   408  	Core storeCore
   409  }
   410  
   411  type storeCore struct {
   412  	// StoreType StoreType
   413  	CommitID types.CommitID
   414  	// ... maybe add more state
   415  }
   416  
   417  // Implements merkle.Hasher.
   418  func (si storeInfo) Hash() []byte {
   419  	// Doesn't write Name, since merkle.SimpleHashFromMap() will
   420  	// include them via the keys.
   421  	bz := si.Core.CommitID.Hash
   422  	hasher := tmhash.New()
   423  
   424  	_, err := hasher.Write(bz)
   425  	if err != nil {
   426  		// TODO: Handle with #870
   427  		panic(err)
   428  	}
   429  
   430  	return hasher.Sum(nil)
   431  }
   432  
   433  // ----------------------------------------
   434  // Misc.
   435  
   436  func getLatestVersion(db dbm.DB) int64 {
   437  	var latest int64
   438  	latestBytes := db.Get([]byte(latestVersionKey))
   439  	if latestBytes == nil {
   440  		return 0
   441  	}
   442  
   443  	err := amino.UnmarshalSized(latestBytes, &latest)
   444  	if err != nil {
   445  		panic(err)
   446  	}
   447  
   448  	return latest
   449  }
   450  
   451  // Set the latest version.
   452  func setLatestVersion(batch dbm.Batch, version int64) {
   453  	latestBytes, _ := amino.MarshalSized(version)
   454  	batch.Set([]byte(latestVersionKey), latestBytes)
   455  }
   456  
   457  // Commits each store and returns a new commitInfo.
   458  func commitStores(version int64, storeMap map[types.StoreKey]types.CommitStore) commitInfo {
   459  	storeInfos := make([]storeInfo, 0, len(storeMap))
   460  
   461  	for key, store := range storeMap {
   462  		// Commit
   463  		commitID := store.Commit()
   464  		/* Print all items.
   465  		itr := store.Iterator(nil, nil)
   466  		for ; itr.Valid(); itr.Next() {
   467  			k, v := itr.Key(), itr.Value()
   468  			fmt.Println("STORE ENTRY",
   469  			colors.ColoredBytes(k, colors.Green, colors.Blue),
   470  			colors.ColoredBytes(v, colors.Cyan, colors.Blue))
   471  		}
   472  		itr.Close()
   473  		*/
   474  		// Record CommitID
   475  		si := storeInfo{}
   476  		si.Name = key.Name()
   477  		si.Core.CommitID = commitID
   478  		// si.Core.StoreType = store.GetStoreType()
   479  		storeInfos = append(storeInfos, si)
   480  	}
   481  
   482  	ci := commitInfo{
   483  		Version:    version,
   484  		StoreInfos: storeInfos,
   485  	}
   486  	return ci
   487  }
   488  
   489  // Gets commitInfo from disk.
   490  func getCommitInfo(db dbm.DB, ver int64) (commitInfo, error) {
   491  	// Get from DB.
   492  	cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver)
   493  	cInfoBytes := db.Get([]byte(cInfoKey))
   494  	if cInfoBytes == nil {
   495  		return commitInfo{}, fmt.Errorf("failed to get Store: no data")
   496  	}
   497  
   498  	var cInfo commitInfo
   499  
   500  	err := amino.UnmarshalSized(cInfoBytes, &cInfo)
   501  	if err != nil {
   502  		return commitInfo{}, fmt.Errorf("failed to get Store: %w", err)
   503  	}
   504  
   505  	return cInfo, nil
   506  }
   507  
   508  // Set a commitInfo for given version.
   509  func setCommitInfo(batch dbm.Batch, version int64, cInfo commitInfo) {
   510  	cInfoBytes := amino.MustMarshalSized(cInfo)
   511  	cInfoKey := fmt.Sprintf(commitInfoKeyFmt, version)
   512  	batch.Set([]byte(cInfoKey), cInfoBytes)
   513  }