Skip to content

Commit

Permalink
feat: auto-set block timestamp for historical queries (backport #15448)…
Browse files Browse the repository at this point in the history
… (#15573)

Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Aleksandr Bezobchuk <aleks.bezobchuk@gmail.com>
  • Loading branch information
3 people committed Mar 28, 2023
1 parent 418ca33 commit b979f8b
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 103 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

## Improvements

* [#15448](https://github.com/cosmos/cosmos-sdk/pull/15448) Automatically populate the block timestamp for historical queries. In contexts where the block timestamp is needed for previous states, the timestamp will now be set. Note, when querying against a node it must be re-synced in order to be able to automatically populate the block timestamp. Otherwise, the block timestamp will be populated for heights going forward once upgraded.

## [v0.47.1](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.1) - 2023-03-23

### Features
Expand Down
188 changes: 142 additions & 46 deletions api/cosmos/base/store/v1beta1/commit_info.pulsar.go

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/cosmos/cosmos-sdk/codec"
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
"github.com/cosmos/cosmos-sdk/store/rootmulti"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand Down Expand Up @@ -430,6 +431,11 @@ func (app *BaseApp) Commit() abci.ResponseCommit {
header := app.deliverState.ctx.BlockHeader()
retainHeight := app.GetBlockRetentionHeight(header.Height)

rms, ok := app.cms.(*rootmulti.Store)
if ok {
rms.SetCommitHeader(header)
}

// Write the DeliverTx state into branched storage and commit the MultiStore.
// The write to the DeliverTx state writes all state transitions to the root
// MultiStore (app.cms) so when Commit() is called is persists those values.
Expand Down Expand Up @@ -778,6 +784,16 @@ func (app *BaseApp) CreateQueryContext(height int64, prove bool) (sdk.Context, e
WithMinGasPrices(app.minGasPrices).
WithBlockHeight(height)

if height != lastBlockHeight {
rms, ok := app.cms.(*rootmulti.Store)
if ok {
cInfo, err := rms.GetCommitInfo(height)
if cInfo != nil && err == nil {
ctx = ctx.WithBlockTime(cInfo.Timestamp)
}
}
}

return ctx, nil
}

Expand Down
7 changes: 5 additions & 2 deletions proto/cosmos/base/store/v1beta1/commit_info.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ syntax = "proto3";
package cosmos.base.store.v1beta1;

import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";

option go_package = "github.com/cosmos/cosmos-sdk/store/types";

// CommitInfo defines commit information used by the multi-store when committing
// a version/height.
message CommitInfo {
int64 version = 1;
repeated StoreInfo store_infos = 2 [(gogoproto.nullable) = false];
int64 version = 1;
repeated StoreInfo store_infos = 2 [(gogoproto.nullable) = false];
google.protobuf.Timestamp timestamp = 3
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}

// StoreInfo defines store-specific commit information. It contains a reference
Expand Down
70 changes: 41 additions & 29 deletions store/rootmulti/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
dbm "github.com/cometbft/cometbft-db"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
protoio "github.com/cosmos/gogoproto/io"
gogotypes "github.com/cosmos/gogoproto/types"
iavltree "github.com/cosmos/iavl"
Expand Down Expand Up @@ -66,14 +67,12 @@ type Store struct {
lazyLoading bool
initialVersion int64
removalMap map[types.StoreKey]bool

traceWriter io.Writer
traceContext types.TraceContext
traceContextMutex sync.Mutex

interBlockCache types.MultiStorePersistentCache

listeners map[types.StoreKey][]types.WriteListener
traceWriter io.Writer
traceContext types.TraceContext
traceContextMutex sync.Mutex
interBlockCache types.MultiStorePersistentCache
listeners map[types.StoreKey][]types.WriteListener
commitHeader cmtproto.Header
}

var (
Expand Down Expand Up @@ -208,7 +207,7 @@ func (rs *Store) loadVersion(ver int64, upgrades *types.StoreUpgrades) error {
// load old data if we are not version 0
if ver != 0 {
var err error
cInfo, err = getCommitInfo(rs.db, ver)
cInfo, err = rs.GetCommitInfo(ver)
if err != nil {
return err
}
Expand Down Expand Up @@ -440,7 +439,12 @@ func (rs *Store) Commit() types.CommitID {
version = previousHeight + 1
}

if rs.commitHeader.Height != version {
rs.logger.Debug("commit header and version mismatch", "header_height", rs.commitHeader.Height, "version", version)
}

rs.lastCommitInfo = commitStores(version, rs.stores, rs.removalMap)
rs.lastCommitInfo.Timestamp = rs.commitHeader.Time
defer rs.flushMetadata(rs.db, version, rs.lastCommitInfo)

// remove remnants of removed stores
Expand All @@ -451,6 +455,7 @@ func (rs *Store) Commit() types.CommitID {
delete(rs.keysByName, sk.Name())
}
}

// reset the removalMap
rs.removalMap = make(map[types.StoreKey]bool)

Expand Down Expand Up @@ -676,7 +681,7 @@ func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery {
if res.Height == rs.lastCommitInfo.Version {
commitInfo = rs.lastCommitInfo
} else {
commitInfo, err = getCommitInfo(rs.db, res.Height)
commitInfo, err = rs.GetCommitInfo(res.Height)
if err != nil {
return sdkerrors.QueryResult(err, false)
}
Expand Down Expand Up @@ -1010,6 +1015,32 @@ func (rs *Store) RollbackToVersion(target int64) error {
return rs.LoadLatestVersion()
}

// SetCommitHeader sets the commit block header of the store.
func (rs *Store) SetCommitHeader(h cmtproto.Header) {
rs.commitHeader = h
}

// GetCommitInfo attempts to retrieve CommitInfo for a given version/height. It
// will return an error if no CommitInfo exists, we fail to unmarshal the record
// or if we cannot retrieve the object from the DB.
func (rs *Store) GetCommitInfo(ver int64) (*types.CommitInfo, error) {
cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver)

bz, err := rs.db.Get([]byte(cInfoKey))
if err != nil {
return nil, errors.Wrap(err, "failed to get commit info")
} else if bz == nil {
return nil, errors.New("no commit info found")
}

cInfo := &types.CommitInfo{}
if err = cInfo.Unmarshal(bz); err != nil {
return nil, errors.Wrap(err, "failed unmarshal commit info")
}

return cInfo, nil
}

func (rs *Store) flushMetadata(db dbm.DB, version int64, cInfo *types.CommitInfo) {
rs.logger.Debug("flushing metadata", "height", version)
batch := db.NewBatch()
Expand Down Expand Up @@ -1106,25 +1137,6 @@ func commitStores(version int64, storeMap map[types.StoreKey]types.CommitKVStore
}
}

// Gets commitInfo from disk.
func getCommitInfo(db dbm.DB, ver int64) (*types.CommitInfo, error) {
cInfoKey := fmt.Sprintf(commitInfoKeyFmt, ver)

bz, err := db.Get([]byte(cInfoKey))
if err != nil {
return nil, errors.Wrap(err, "failed to get commit info")
} else if bz == nil {
return nil, errors.New("no commit info found")
}

cInfo := &types.CommitInfo{}
if err = cInfo.Unmarshal(bz); err != nil {
return nil, errors.Wrap(err, "failed unmarshal commit info")
}

return cInfo, nil
}

func flushCommitInfo(batch dbm.Batch, version int64, cInfo *types.CommitInfo) {
bz, err := cInfo.Marshal()
if err != nil {
Expand Down
12 changes: 6 additions & 6 deletions store/rootmulti/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func TestMultistoreLoadWithUpgrade(t *testing.T) {
expectedCommitID := getExpectedCommitID(store, 1)
checkStore(t, store, expectedCommitID, commitID)

ci, err := getCommitInfo(db, 1)
ci, err := store.GetCommitInfo(1)
require.NoError(t, err)
require.Equal(t, int64(1), ci.Version)
require.Equal(t, 3, len(ci.StoreInfos))
Expand Down Expand Up @@ -297,7 +297,7 @@ func TestMultistoreLoadWithUpgrade(t *testing.T) {
require.Equal(t, v4, rl4.Get(k4))

// check commitInfo in storage
ci, err = getCommitInfo(db, 2)
ci, err = reload.GetCommitInfo(2)
require.NoError(t, err)
require.Equal(t, int64(2), ci.Version)
require.Equal(t, 3, len(ci.StoreInfos), ci.StoreInfos)
Expand Down Expand Up @@ -352,7 +352,7 @@ func TestMultiStoreRestart(t *testing.T) {

multi.Commit()

cinfo, err := getCommitInfo(multi.db, int64(i))
cinfo, err := multi.GetCommitInfo(int64(i))
require.NoError(t, err)
require.Equal(t, int64(i), cinfo.Version)
}
Expand All @@ -367,7 +367,7 @@ func TestMultiStoreRestart(t *testing.T) {

multi.Commit()

flushedCinfo, err := getCommitInfo(multi.db, 3)
flushedCinfo, err := multi.GetCommitInfo(3)
require.Nil(t, err)
require.NotEqual(t, initCid, flushedCinfo, "CID is different after flush to disk")

Expand All @@ -377,7 +377,7 @@ func TestMultiStoreRestart(t *testing.T) {

multi.Commit()

postFlushCinfo, err := getCommitInfo(multi.db, 4)
postFlushCinfo, err := multi.GetCommitInfo(4)
require.NoError(t, err)
require.Equal(t, int64(4), postFlushCinfo.Version, "Commit changed after in-memory commit")

Expand Down Expand Up @@ -850,7 +850,7 @@ func TestCommitOrdered(t *testing.T) {
typeID := multi.Commit()
require.Equal(t, int64(1), typeID.Version)

ci, err := getCommitInfo(db, 1)
ci, err := multi.GetCommitInfo(1)
require.NoError(t, err)
require.Equal(t, int64(1), ci.Version)
require.Equal(t, 3, len(ci.StoreInfos))
Expand Down
Loading

0 comments on commit b979f8b

Please sign in to comment.