Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk.Context): Context.KVStore/TransientStore improve performance #14203

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions baseapp/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,8 @@ func TestABCI_CheckTx(t *testing.T) {
require.Empty(t, r.GetEvents())
}

checkStateStore := getCheckStateCtx(suite.baseApp).KVStore(capKey1)
ctx := getCheckStateCtx(suite.baseApp)
checkStateStore := ctx.KVStore(capKey1)
storedCounter := getIntFromStore(t, checkStateStore, counterKey)

// ensure AnteHandler ran
Expand All @@ -625,7 +626,8 @@ func TestABCI_CheckTx(t *testing.T) {
suite.baseApp.EndBlock(abci.RequestEndBlock{})
suite.baseApp.Commit()

checkStateStore = getCheckStateCtx(suite.baseApp).KVStore(capKey1)
ctx = getCheckStateCtx(suite.baseApp)
checkStateStore = ctx.KVStore(capKey1)
storedBytes := checkStateStore.Get(counterKey)
require.Nil(t, storedBytes)
}
Expand Down Expand Up @@ -697,7 +699,8 @@ func TestABCI_DeliverTx_MultiMsg(t *testing.T) {
res := suite.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))

store := getDeliverStateCtx(suite.baseApp).KVStore(capKey1)
ctx := getDeliverStateCtx(suite.baseApp)
store := ctx.KVStore(capKey1)

// tx counter only incremented once
txCounter := getIntFromStore(t, store, anteKey)
Expand Down Expand Up @@ -725,7 +728,8 @@ func TestABCI_DeliverTx_MultiMsg(t *testing.T) {
res = suite.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes})
require.True(t, res.IsOK(), fmt.Sprintf("%v", res))

store = getDeliverStateCtx(suite.baseApp).KVStore(capKey1)
ctx = getDeliverStateCtx(suite.baseApp)
store = ctx.KVStore(capKey1)

// tx counter only incremented once
txCounter = getIntFromStore(t, store, anteKey)
Expand Down
8 changes: 4 additions & 4 deletions store/gaskv/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ var _ types.KVStore = &Store{}
// KVStore interface.
type Store struct {
gasMeter types.GasMeter
gasConfig types.GasConfig
gasConfig *types.GasConfig
parent types.KVStore
}

// NewStore returns a reference to a new GasKVStore.
func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store {
func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig *types.GasConfig) *Store {
kvs := &Store{
gasMeter: gasMeter,
gasConfig: gasConfig,
Expand Down Expand Up @@ -108,11 +108,11 @@ func (gs *Store) iterator(start, end []byte, ascending bool) types.Iterator {

type gasIterator struct {
gasMeter types.GasMeter
gasConfig types.GasConfig
gasConfig *types.GasConfig
parent types.Iterator
}

func newGasIterator(gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.Iterator) types.Iterator {
func newGasIterator(gasMeter types.GasMeter, gasConfig *types.GasConfig, parent types.Iterator) types.Iterator {
return &gasIterator{
gasMeter: gasMeter,
gasConfig: gasConfig,
Expand Down
8 changes: 4 additions & 4 deletions store/types/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ type GasConfig struct {
}

// KVGasConfig returns a default gas config for KVStores.
func KVGasConfig() GasConfig {
return GasConfig{
func KVGasConfig() *GasConfig {
return &GasConfig{
HasCost: 1000,
DeleteCost: 1000,
ReadCostFlat: 1000,
Expand All @@ -241,8 +241,8 @@ func KVGasConfig() GasConfig {
}

// TransientGasConfig returns a default gas config for TransientStores.
func TransientGasConfig() GasConfig {
return GasConfig{
func TransientGasConfig() *GasConfig {
return &GasConfig{
HasCost: 100,
DeleteCost: 100,
ReadCostFlat: 100,
Expand Down
2 changes: 1 addition & 1 deletion store/types/gas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func TestAddUint64Overflow(t *testing.T) {
func TestTransientGasConfig(t *testing.T) {
t.Parallel()
config := TransientGasConfig()
require.Equal(t, config, GasConfig{
require.Equal(t, config, &GasConfig{
HasCost: 100,
DeleteCost: 100,
ReadCostFlat: 100,
Expand Down
25 changes: 14 additions & 11 deletions types/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ type Context struct {
consParams *tmproto.ConsensusParams
eventManager *EventManager
priority int64 // The tx priority, only relevant in CheckTx
kvGasConfig storetypes.GasConfig
transientKVGasConfig storetypes.GasConfig
kvGasConfig *storetypes.GasConfig
transientKVGasConfig *storetypes.GasConfig
}

// Proposed rename, not done to avoid API breakage
Expand All @@ -62,8 +62,8 @@ func (c Context) IsReCheckTx() bool { return c.recheckT
func (c Context) MinGasPrices() DecCoins { return c.minGasPrice }
func (c Context) EventManager() *EventManager { return c.eventManager }
func (c Context) Priority() int64 { return c.priority }
func (c Context) KVGasConfig() storetypes.GasConfig { return c.kvGasConfig }
func (c Context) TransientKVGasConfig() storetypes.GasConfig { return c.transientKVGasConfig }
func (c Context) KVGasConfig() storetypes.GasConfig { return *c.kvGasConfig }
func (c Context) TransientKVGasConfig() storetypes.GasConfig { return *c.transientKVGasConfig }

// clone the header before returning
func (c Context) BlockHeader() tmproto.Header {
Expand Down Expand Up @@ -203,14 +203,14 @@ func (c Context) WithBlockGasMeter(meter GasMeter) Context {
// WithKVGasConfig returns a Context with an updated gas configuration for
// the KVStore
func (c Context) WithKVGasConfig(gasConfig storetypes.GasConfig) Context {
c.kvGasConfig = gasConfig
c.kvGasConfig = &gasConfig
return c
}

// WithTransientKVGasConfig returns a Context with an updated gas configuration for
// the transient KVStore
func (c Context) WithTransientKVGasConfig(gasConfig storetypes.GasConfig) Context {
c.transientKVGasConfig = gasConfig
c.transientKVGasConfig = &gasConfig
return c
}

Expand Down Expand Up @@ -277,21 +277,24 @@ func (c Context) Value(key interface{}) interface{} {
// ----------------------------------------------------------------------------

// KVStore fetches a KVStore from the MultiStore.
func (c Context) KVStore(key storetypes.StoreKey) KVStore {
return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), c.kvGasConfig)
// NOTE: Uses pointer receiver to save on execution time.
func (c *Context) KVStore(key storetypes.StoreKey) KVStore {
kv := c.ms.GetKVStore(key)
return gaskv.NewStore(kv, c.gasMeter, c.kvGasConfig)
}
Comment on lines -280 to 284

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callers who invoke c.KVStore will get a KVStore value with a kvGasConfig that is no longer a copy of the config data, but instead is a pointer to the same storetypes.GasConfig value of the Context from which the KVStore was derived. This is a fundamental change to the semantics of the KVStore method, and of the Context API in general. It's important to establish the safety of large changes like this with comprehensive test coverage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 if we need testing for this but old behaviour is retained


// TransientStore fetches a TransientStore from the MultiStore.
func (c Context) TransientStore(key storetypes.StoreKey) KVStore {
return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), c.transientKVGasConfig)
// NOTE: Uses pointer receiver to save on execution time.
func (c *Context) TransientStore(key storetypes.StoreKey) KVStore {
return gaskv.NewStore(c.ms.GetKVStore(key), c.gasMeter, c.transientKVGasConfig)
}

// CacheContext returns a new Context with the multi-store cached and a new
// EventManager. The cached context is written to the context when writeCache
// is called. Note, events are automatically emitted on the parent context's
// EventManager when the caller executes the write.
func (c Context) CacheContext() (cc Context, writeCache func()) {
cms := c.MultiStore().CacheMultiStore()
cms := c.ms.CacheMultiStore()
cc = c.WithMultiStore(cms).WithEventManager(NewEventManager())

testinginprod marked this conversation as resolved.
Show resolved Hide resolved
writeCache = func() {
Expand Down
18 changes: 18 additions & 0 deletions types/context_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package types_test

import (
"github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/testutil"
"testing"
)

func BenchmarkContext_KVStore(b *testing.B) {
key := types.NewKVStoreKey(b.Name() + "_TestCacheContext")

ctx := testutil.DefaultContext(key, types.NewTransientStoreKey("transient_"+b.Name()))

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ctx.KVStore(key)
}
}