Co-authored-by: mmsqe <mavis@crypto.com> Co-authored-by: Marko <marko@baricevic.me> Co-authored-by: marbar3778 <marbar3778@yahoo.com>
302 lines
8.7 KiB
Go
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)
|
|
}
|