cosmos-sdk/store/v2/root/store.go
Alexander Peters 9a92843c21
store/v2: remove auto migration and fix restore cmd (#23568)
Co-authored-by: mmsqe <mavis@crypto.com>
Co-authored-by: Marko <marko@baricevic.me>
Co-authored-by: marbar3778 <marbar3778@yahoo.com>
2025-01-31 17:34:44 +00:00

302 lines
8.7 KiB
Go

package root
import (
"crypto/sha256"
"errors"
"fmt"
"io"
"time"
corelog "cosmossdk.io/core/log"
corestore "cosmossdk.io/core/store"
"cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/metrics"
"cosmossdk.io/store/v2/proof"
"cosmossdk.io/store/v2/pruning"
)
var (
_ store.RootStore = (*Store)(nil)
_ store.UpgradeableStore = (*Store)(nil)
)
// Store defines the SDK's default RootStore implementation. It contains a single
// State Storage (SS) backend and a single State Commitment (SC) backend. The SC
// backend may or may not support multiple store keys and is implementation
// dependent.
type Store struct {
logger corelog.Logger
// holds the db instance for closing it
dbCloser io.Closer
// stateCommitment reflects the state commitment (SC) backend
stateCommitment store.Committer
// lastCommitInfo reflects the last version/hash that has been committed
lastCommitInfo *proof.CommitInfo
// telemetry reflects a telemetry agent responsible for emitting metrics (if any)
telemetry metrics.StoreMetrics
// pruningManager reflects the pruning manager used to prune state of the SS and SC backends
pruningManager *pruning.Manager
}
// New creates a new root Store instance.
//
// NOTE: The migration manager is optional and can be nil if no migration is required.
func New(
dbCloser io.Closer,
logger corelog.Logger,
sc store.Committer,
pm *pruning.Manager,
m metrics.StoreMetrics,
) (store.RootStore, error) {
return &Store{
dbCloser: dbCloser,
logger: logger,
stateCommitment: sc,
pruningManager: pm,
telemetry: m,
}, nil
}
// Close closes the store and resets all internal fields. Note, Close() is NOT
// idempotent and should only be called once.
func (s *Store) Close() (err error) {
err = errors.Join(err, s.stateCommitment.Close())
err = errors.Join(err, s.dbCloser.Close())
s.stateCommitment = nil
s.lastCommitInfo = nil
return err
}
func (s *Store) SetMetrics(m metrics.Metrics) {
s.telemetry = m
}
func (s *Store) SetInitialVersion(v uint64) error {
return s.stateCommitment.SetInitialVersion(v)
}
// getVersionedReader returns a VersionedReader based on the given version. If the
// version exists in the state storage, it returns the state storage.
// If not, it checks if the state commitment implements the VersionedReader interface
// and the version exists in the state commitment, since the state storage will be
// synced during migration.
func (s *Store) getVersionedReader(version uint64) (store.VersionedReader, error) {
isExist, err := s.stateCommitment.VersionExists(version)
if err != nil {
return nil, err
}
if isExist {
return s.stateCommitment, nil
}
return nil, fmt.Errorf("version %d does not exist", version)
}
// StateAt returns a read-only view of the state at a given version.
func (s *Store) StateAt(v uint64) (corestore.ReaderMap, error) {
vReader, err := s.getVersionedReader(v)
return NewReaderMap(v, vReader), err
}
func (s *Store) StateLatest() (uint64, corestore.ReaderMap, error) {
v, err := s.GetLatestVersion()
if err != nil {
return 0, nil, err
}
vReader, err := s.getVersionedReader(v)
if err != nil {
return 0, nil, err
}
return v, NewReaderMap(v, vReader), nil
}
func (s *Store) GetStateCommitment() store.Committer {
return s.stateCommitment
}
// LastCommitID returns a CommitID based off of the latest internal CommitInfo.
// If an internal CommitInfo is not set, a new one will be returned with only the
// latest version set, which is based off of the SC view.
func (s *Store) LastCommitID() (proof.CommitID, error) {
if s.lastCommitInfo != nil {
return *s.lastCommitInfo.CommitID(), nil
}
latestVersion, err := s.stateCommitment.GetLatestVersion()
if err != nil {
return proof.CommitID{}, err
}
// if the latest version is 0, we return a CommitID with version 0 and a hash of an empty byte slice
bz := sha256.Sum256([]byte{})
return proof.CommitID{Version: int64(latestVersion), Hash: bz[:]}, nil
}
// GetLatestVersion returns the latest version based on the latest internal
// CommitInfo. An error is returned if the latest CommitInfo or version cannot
// be retrieved.
func (s *Store) GetLatestVersion() (uint64, error) {
lastCommitID, err := s.LastCommitID()
if err != nil {
return 0, err
}
return uint64(lastCommitID.Version), nil
}
func (s *Store) Query(storeKey []byte, version uint64, key []byte, prove bool) (store.QueryResult, error) {
if s.telemetry != nil {
defer s.telemetry.MeasureSince(time.Now(), "root_store", "query")
}
val, err := s.stateCommitment.Get(storeKey, version, key)
if err != nil {
return store.QueryResult{}, fmt.Errorf("failed to query SC store: %w", err)
}
result := store.QueryResult{
Key: key,
Value: val,
Version: version,
}
if prove {
result.ProofOps, err = s.stateCommitment.GetProof(storeKey, version, key)
if err != nil {
return store.QueryResult{}, fmt.Errorf("failed to get SC store proof: %w", err)
}
}
return result, nil
}
func (s *Store) LoadLatestVersion() error {
if s.telemetry != nil {
defer s.telemetry.MeasureSince(time.Now(), "root_store", "load_latest_version")
}
lv, err := s.GetLatestVersion()
if err != nil {
return err
}
return s.loadVersion(lv, nil, false)
}
func (s *Store) LoadVersion(version uint64) error {
if s.telemetry != nil {
defer s.telemetry.MeasureSince(time.Now(), "root_store", "load_version")
}
return s.loadVersion(version, nil, false)
}
func (s *Store) LoadVersionForOverwriting(version uint64) error {
if s.telemetry != nil {
defer s.telemetry.MeasureSince(time.Now(), "root_store", "load_version_for_overwriting")
}
return s.loadVersion(version, nil, true)
}
// LoadVersionAndUpgrade implements the UpgradeableStore interface.
//
// NOTE: It cannot be called while the store is migrating.
func (s *Store) LoadVersionAndUpgrade(version uint64, upgrades *corestore.StoreUpgrades) error {
if upgrades == nil {
return errors.New("upgrades cannot be nil")
}
if s.telemetry != nil {
defer s.telemetry.MeasureSince(time.Now(), "root_store", "load_version_and_upgrade")
}
if err := s.loadVersion(version, upgrades, true); err != nil {
return err
}
return nil
}
func (s *Store) loadVersion(v uint64, upgrades *corestore.StoreUpgrades, overrideAfter bool) error {
s.logger.Debug("loading version", "version", v)
if upgrades == nil {
if !overrideAfter {
if err := s.stateCommitment.LoadVersion(v); err != nil {
return fmt.Errorf("failed to load SC version %d: %w", v, err)
}
} else {
if err := s.stateCommitment.LoadVersionForOverwriting(v); err != nil {
return fmt.Errorf("failed to load SC version %d: %w", v, err)
}
}
} else {
// if upgrades are provided, we need to load the version and apply the upgrades
if err := s.stateCommitment.LoadVersionAndUpgrade(v, upgrades); err != nil {
return fmt.Errorf("failed to load SS version with upgrades %d: %w", v, err)
}
}
// set lastCommitInfo explicitly s.t. Commit commits the correct version, i.e. v+1
var err error
s.lastCommitInfo, err = s.stateCommitment.GetCommitInfo(v)
if err != nil {
return fmt.Errorf("failed to get commit info for version %d: %w", v, err)
}
return nil
}
// Commit commits all state changes to the underlying SS and SC backends. It
// writes a batch of the changeset to the SC tree, and retrieves the CommitInfo
// from the SC tree. Finally, it commits the SC tree and returns the hash of
// the CommitInfo.
func (s *Store) Commit(cs *corestore.Changeset) ([]byte, error) {
if s.telemetry != nil {
now := time.Now()
defer func() {
s.telemetry.MeasureSince(now, "root_store", "commit")
}()
}
// signal to the pruning manager that a new version is about to be committed
// this may be required if the SS and SC backends implementation have the
// background pruning process (iavl v1 for example) which must be paused during the commit
s.pruningManager.PausePruning()
st := time.Now()
if err := s.stateCommitment.WriteChangeset(cs); err != nil {
return nil, fmt.Errorf("failed to write batch to SC store: %w", err)
}
writeDur := time.Since(st)
st = time.Now()
cInfo, err := s.stateCommitment.Commit(cs.Version)
if err != nil {
return nil, fmt.Errorf("failed to commit SC store: %w", err)
}
s.logger.Warn(fmt.Sprintf("commit version %d write=%s commit=%s", cs.Version, writeDur, time.Since(st)))
if cInfo.Version != int64(cs.Version) {
return nil, fmt.Errorf("commit version mismatch: got %d, expected %d", cInfo.Version, cs.Version)
}
s.lastCommitInfo = cInfo
// signal to the pruning manager that the commit is done
if err := s.pruningManager.ResumePruning(uint64(s.lastCommitInfo.Version)); err != nil {
s.logger.Error("failed to signal commit done to pruning manager", "err", err)
}
return s.lastCommitInfo.Hash(), nil
}
func (s *Store) Prune(version uint64) error {
return s.pruningManager.Prune(version)
}