diff --git a/PENDING.md b/PENDING.md index ec7941b86fa9..109ed95ba238 100644 --- a/PENDING.md +++ b/PENDING.md @@ -35,6 +35,7 @@ FEATURES * [cosmos-sdk-cli] Added support for cosmos-sdk-cli tool under cosmos-sdk/cmd * This allows SDK users to initialize a new project repository. * [tests] Remotenet commands for AWS (awsnet) +* [store] Add transient store IMPROVEMENTS * [baseapp] Allow any alphanumeric character in route diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 837ce5a1064f..50b96f8e3815 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -47,6 +47,7 @@ type GaiaApp struct { keyGov *sdk.KVStoreKey keyFeeCollection *sdk.KVStoreKey keyParams *sdk.KVStoreKey + tkeyParams *sdk.TransientStoreKey // Manage getting and setting accounts accountMapper auth.AccountMapper @@ -77,6 +78,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio keyGov: sdk.NewKVStoreKey("gov"), keyFeeCollection: sdk.NewKVStoreKey("fee"), keyParams: sdk.NewKVStoreKey("params"), + tkeyParams: sdk.NewTransientStoreKey("params"), } // define the accountMapper @@ -109,6 +111,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) + app.MountStore(app.tkeyParams, sdk.StoreTypeTransient) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) diff --git a/cmd/gaia/cmd/gaiadebug/gaiadebug b/cmd/gaia/cmd/gaiadebug/gaiadebug new file mode 100755 index 000000000000..642291fd3e65 Binary files /dev/null and b/cmd/gaia/cmd/gaiadebug/gaiadebug differ diff --git a/examples/democoin/x/assoc/validator_set.go b/examples/democoin/x/assoc/validator_set.go index 94a0ef63060e..03ce506ea0f1 100644 --- a/examples/democoin/x/assoc/validator_set.go +++ b/examples/democoin/x/assoc/validator_set.go @@ -11,8 +11,8 @@ import ( type ValidatorSet struct { sdk.ValidatorSet - key sdk.KVStoreGetter - cdc *wire.Codec + store sdk.KVStore + cdc *wire.Codec maxAssoc int addrLen int @@ -21,15 +21,15 @@ type ValidatorSet struct { var _ sdk.ValidatorSet = ValidatorSet{} // NewValidatorSet returns new ValidatorSet with underlying ValidatorSet -func NewValidatorSet(cdc *wire.Codec, key sdk.KVStoreGetter, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet { +func NewValidatorSet(cdc *wire.Codec, store sdk.KVStore, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet { if maxAssoc < 0 || addrLen < 0 { panic("Cannot use negative integer for NewValidatorSet") } return ValidatorSet{ ValidatorSet: valset, - key: key, - cdc: cdc, + store: store, + cdc: cdc, maxAssoc: maxAssoc, addrLen: addrLen, @@ -38,8 +38,7 @@ func NewValidatorSet(cdc *wire.Codec, key sdk.KVStoreGetter, valset sdk.Validato // Implements sdk.ValidatorSet func (valset ValidatorSet) Validator(ctx sdk.Context, addr sdk.AccAddress) (res sdk.Validator) { - store := valset.key.KVStore(ctx) - base := store.Get(GetBaseKey(addr)) + base := valset.store.Get(GetBaseKey(addr)) res = valset.ValidatorSet.Validator(ctx, base) if res == nil { res = valset.ValidatorSet.Validator(ctx, addr) @@ -67,13 +66,12 @@ func (valset ValidatorSet) Associate(ctx sdk.Context, base sdk.AccAddress, assoc if len(base) != valset.addrLen || len(assoc) != valset.addrLen { return false } - store := valset.key.KVStore(ctx) // If someone already owns the associated address - if store.Get(GetBaseKey(assoc)) != nil { + if valset.store.Get(GetBaseKey(assoc)) != nil { return false } - store.Set(GetBaseKey(assoc), base) - store.Set(GetAssocKey(base, assoc), []byte{0x00}) + valset.store.Set(GetBaseKey(assoc), base) + valset.store.Set(GetAssocKey(base, assoc), []byte{0x00}) return true } @@ -82,21 +80,19 @@ func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.AccAddress, asso if len(base) != valset.addrLen || len(assoc) != valset.addrLen { return false } - store := valset.key.KVStore(ctx) // No associated address found for given validator - if !bytes.Equal(store.Get(GetBaseKey(assoc)), base) { + if !bytes.Equal(valset.store.Get(GetBaseKey(assoc)), base) { return false } - store.Delete(GetBaseKey(assoc)) - store.Delete(GetAssocKey(base, assoc)) + valset.store.Delete(GetBaseKey(assoc)) + valset.store.Delete(GetAssocKey(base, assoc)) return true } // Associations returns all associated addresses with a validator func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.AccAddress) (res []sdk.AccAddress) { - store := valset.key.KVStore(ctx) res = make([]sdk.AccAddress, valset.maxAssoc) - iter := sdk.KVStorePrefixIterator(store, GetAssocPrefix(base)) + iter := sdk.KVStorePrefixIterator(valset.store, GetAssocPrefix(base)) i := 0 for ; iter.Valid(); iter.Next() { key := iter.Key() diff --git a/examples/democoin/x/assoc/validator_set_test.go b/examples/democoin/x/assoc/validator_set_test.go index e5932c14b360..2fead3ad2660 100644 --- a/examples/democoin/x/assoc/validator_set_test.go +++ b/examples/democoin/x/assoc/validator_set_test.go @@ -36,7 +36,7 @@ func TestValidatorSet(t *testing.T) { {addr2, sdk.NewRat(2)}, }} - valset := NewValidatorSet(wire.NewCodec(), sdk.NewPrefixStoreGetter(key, []byte("assoc")), base, 1, 5) + valset := NewValidatorSet(wire.NewCodec(), ctx.KVStore(key).Prefix([]byte("assoc")), base, 1, 5) require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1)) require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2)) diff --git a/examples/democoin/x/oracle/handler.go b/examples/democoin/x/oracle/handler.go index 8b94a18940f4..079f7680f4cd 100644 --- a/examples/democoin/x/oracle/handler.go +++ b/examples/democoin/x/oracle/handler.go @@ -26,7 +26,7 @@ func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.Valid info.Power = sdk.ZeroRat() info.Hash = hash prefix := GetSignPrefix(p, keeper.cdc) - store := keeper.key.KVStore(ctx) + store := ctx.KVStore(keeper.key) iter := sdk.KVStorePrefixIterator(store, prefix) for ; iter.Valid(); iter.Next() { if valset.Validator(ctx, iter.Value()) != nil { diff --git a/examples/democoin/x/oracle/keeper.go b/examples/democoin/x/oracle/keeper.go index 97c68251b1f7..25554faa814b 100644 --- a/examples/democoin/x/oracle/keeper.go +++ b/examples/democoin/x/oracle/keeper.go @@ -8,7 +8,7 @@ import ( // Keeper of the oracle store type Keeper struct { - key sdk.KVStoreGetter + key sdk.StoreKey cdc *wire.Codec valset sdk.ValidatorSet @@ -18,7 +18,7 @@ type Keeper struct { } // NewKeeper constructs a new keeper -func NewKeeper(key sdk.KVStoreGetter, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper { +func NewKeeper(key sdk.StoreKey, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper { if timeout < 0 { panic("Timeout should not be negative") } @@ -64,7 +64,7 @@ func EmptyInfo(ctx sdk.Context) Info { // Info returns the information about a payload func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) { - store := keeper.key.KVStore(ctx) + store := ctx.KVStore(keeper.key) key := GetInfoKey(p, keeper.cdc) bz := store.Get(key) @@ -77,7 +77,7 @@ func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) { } func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) { - store := keeper.key.KVStore(ctx) + store := ctx.KVStore(keeper.key) key := GetInfoKey(p, keeper.cdc) bz := keeper.cdc.MustMarshalBinary(info) @@ -85,21 +85,21 @@ func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) { } func (keeper Keeper) sign(ctx sdk.Context, p Payload, signer sdk.AccAddress) { - store := keeper.key.KVStore(ctx) + store := ctx.KVStore(keeper.key) key := GetSignKey(p, signer, keeper.cdc) store.Set(key, signer) } func (keeper Keeper) signed(ctx sdk.Context, p Payload, signer sdk.AccAddress) bool { - store := keeper.key.KVStore(ctx) + store := ctx.KVStore(keeper.key) key := GetSignKey(p, signer, keeper.cdc) return store.Has(key) } func (keeper Keeper) clearSigns(ctx sdk.Context, p Payload) { - store := keeper.key.KVStore(ctx) + store := ctx.KVStore(keeper.key) prefix := GetSignPrefix(p, keeper.cdc) diff --git a/examples/democoin/x/oracle/oracle_test.go b/examples/democoin/x/oracle/oracle_test.go index 467035897d08..c476290f8adc 100644 --- a/examples/democoin/x/oracle/oracle_test.go +++ b/examples/democoin/x/oracle/oracle_test.go @@ -119,7 +119,7 @@ func TestOracle(t *testing.T) { require.Nil(t, err) ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz}) - ork := NewKeeper(sdk.NewPrefixStoreGetter(key, []byte("oracle")), cdc, valset, sdk.NewRat(2, 3), 100) + ork := NewKeeper(key, cdc, valset, sdk.NewRat(2, 3), 100) h := seqHandler(ork, key, sdk.CodespaceRoot) // Nonmock.Validator signed, transaction failed diff --git a/server/mock/store.go b/server/mock/store.go index 5f598621ff15..ec963a1bc22c 100644 --- a/server/mock/store.go +++ b/server/mock/store.go @@ -8,6 +8,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +var _ sdk.MultiStore = multiStore{} + type multiStore struct { kv map[sdk.StoreKey]kvStore } @@ -76,10 +78,6 @@ func (ms multiStore) GetKVStore(key sdk.StoreKey) sdk.KVStore { return ms.kv[key] } -func (ms multiStore) GetKVStoreWithGas(meter sdk.GasMeter, key sdk.StoreKey) sdk.KVStore { - panic("not implemented") -} - func (ms multiStore) GetStore(key sdk.StoreKey) sdk.Store { panic("not implemented") } @@ -88,6 +86,8 @@ func (ms multiStore) GetStoreType() sdk.StoreType { panic("not implemented") } +var _ sdk.KVStore = kvStore{} + type kvStore struct { store map[string][]byte } @@ -129,6 +129,10 @@ func (kv kvStore) Prefix(prefix []byte) sdk.KVStore { panic("not implemented") } +func (kv kvStore) Gas(meter sdk.GasMeter, config sdk.GasConfig) sdk.KVStore { + panic("not implmeneted") +} + func (kv kvStore) Iterator(start, end []byte) sdk.Iterator { panic("not implemented") } diff --git a/store/cachekvstore.go b/store/cachekvstore.go index efc4e89113e5..9eb4ae9324db 100644 --- a/store/cachekvstore.go +++ b/store/cachekvstore.go @@ -85,6 +85,11 @@ func (ci *cacheKVStore) Prefix(prefix []byte) KVStore { return prefixStore{ci, prefix} } +// Implements KVStore +func (ci *cacheKVStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, ci) +} + // Implements CacheKVStore. func (ci *cacheKVStore) Write() { ci.mtx.Lock() diff --git a/store/cachemultistore.go b/store/cachemultistore.go index af89bde6924b..f69ad42aacd1 100644 --- a/store/cachemultistore.go +++ b/store/cachemultistore.go @@ -134,6 +134,6 @@ func (cms cacheMultiStore) GetKVStore(key StoreKey) KVStore { } // Implements MultiStore. -func (cms cacheMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, key StoreKey) KVStore { - return NewGasKVStore(meter, cms.GetKVStore(key)) +func (cms cacheMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, config sdk.GasConfig, key StoreKey) KVStore { + return NewGasKVStore(meter, config, cms.GetKVStore(key)) } diff --git a/store/dbstoreadapter.go b/store/dbstoreadapter.go index cd384c892c10..739e30596b05 100644 --- a/store/dbstoreadapter.go +++ b/store/dbstoreadapter.go @@ -7,6 +7,7 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" ) +// Wrapper type for dbm.Db with implementation of KVStore type dbStoreAdapter struct { dbm.DB } @@ -31,5 +32,10 @@ func (dsa dbStoreAdapter) Prefix(prefix []byte) KVStore { return prefixStore{dsa, prefix} } +// Implements KVStore +func (dsa dbStoreAdapter) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, dsa) +} + // dbm.DB implements KVStore so we can CacheKVStore it. -var _ KVStore = dbStoreAdapter{dbm.DB(nil)} +var _ KVStore = dbStoreAdapter{} diff --git a/store/gaskvstore.go b/store/gaskvstore.go index 22f89e63151a..53ac340bd7ad 100644 --- a/store/gaskvstore.go +++ b/store/gaskvstore.go @@ -6,29 +6,21 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// nolint -const ( - HasCost = 10 - ReadCostFlat = 10 - ReadCostPerByte = 1 - WriteCostFlat = 10 - WriteCostPerByte = 10 - KeyCostFlat = 5 - ValueCostFlat = 10 - ValueCostPerByte = 1 -) +var _ KVStore = &gasKVStore{} // gasKVStore applies gas tracking to an underlying kvstore type gasKVStore struct { - gasMeter sdk.GasMeter - parent sdk.KVStore + gasMeter sdk.GasMeter + gasConfig sdk.GasConfig + parent sdk.KVStore } // nolint -func NewGasKVStore(gasMeter sdk.GasMeter, parent sdk.KVStore) *gasKVStore { +func NewGasKVStore(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.KVStore) *gasKVStore { kvs := &gasKVStore{ - gasMeter: gasMeter, - parent: parent, + gasMeter: gasMeter, + gasConfig: gasConfig, + parent: parent, } return kvs } @@ -40,24 +32,25 @@ func (gi *gasKVStore) GetStoreType() sdk.StoreType { // Implements KVStore. func (gi *gasKVStore) Get(key []byte) (value []byte) { - gi.gasMeter.ConsumeGas(ReadCostFlat, "GetFlat") + gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostFlat, "ReadFlat") value = gi.parent.Get(key) // TODO overflow-safe math? - gi.gasMeter.ConsumeGas(ReadCostPerByte*sdk.Gas(len(value)), "ReadPerByte") + gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*sdk.Gas(len(value)), "ReadPerByte") + return value } // Implements KVStore. func (gi *gasKVStore) Set(key []byte, value []byte) { - gi.gasMeter.ConsumeGas(WriteCostFlat, "SetFlat") + gi.gasMeter.ConsumeGas(gi.gasConfig.WriteCostFlat, "WriteFlat") // TODO overflow-safe math? - gi.gasMeter.ConsumeGas(WriteCostPerByte*sdk.Gas(len(value)), "SetPerByte") + gi.gasMeter.ConsumeGas(gi.gasConfig.WriteCostPerByte*sdk.Gas(len(value)), "WritePerByte") gi.parent.Set(key, value) } // Implements KVStore. func (gi *gasKVStore) Has(key []byte) bool { - gi.gasMeter.ConsumeGas(HasCost, "Has") + gi.gasMeter.ConsumeGas(gi.gasConfig.HasCost, "Has") return gi.parent.Has(key) } @@ -69,7 +62,17 @@ func (gi *gasKVStore) Delete(key []byte) { // Implements KVStore func (gi *gasKVStore) Prefix(prefix []byte) KVStore { - return prefixStore{gi, prefix} + // Keep gasstore layer at the top + return &gasKVStore{ + gasMeter: gi.gasMeter, + gasConfig: gi.gasConfig, + parent: prefixStore{gi.parent, prefix}, + } +} + +// Implements KVStore +func (gi *gasKVStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, gi) } // Implements KVStore. @@ -99,18 +102,20 @@ func (gi *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { } else { parent = gi.parent.ReverseIterator(start, end) } - return newGasIterator(gi.gasMeter, parent) + return newGasIterator(gi.gasMeter, gi.gasConfig, parent) } type gasIterator struct { - gasMeter sdk.GasMeter - parent sdk.Iterator + gasMeter sdk.GasMeter + gasConfig sdk.GasConfig + parent sdk.Iterator } -func newGasIterator(gasMeter sdk.GasMeter, parent sdk.Iterator) sdk.Iterator { +func newGasIterator(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.Iterator) sdk.Iterator { return &gasIterator{ - gasMeter: gasMeter, - parent: parent, + gasMeter: gasMeter, + gasConfig: gasConfig, + parent: parent, } } @@ -131,7 +136,7 @@ func (g *gasIterator) Next() { // Implements Iterator. func (g *gasIterator) Key() (key []byte) { - g.gasMeter.ConsumeGas(KeyCostFlat, "KeyFlat") + g.gasMeter.ConsumeGas(g.gasConfig.KeyCostFlat, "KeyFlat") key = g.parent.Key() return key } @@ -139,8 +144,8 @@ func (g *gasIterator) Key() (key []byte) { // Implements Iterator. func (g *gasIterator) Value() (value []byte) { value = g.parent.Value() - g.gasMeter.ConsumeGas(ValueCostFlat, "ValueFlat") - g.gasMeter.ConsumeGas(ValueCostPerByte*sdk.Gas(len(value)), "ValuePerByte") + g.gasMeter.ConsumeGas(g.gasConfig.ValueCostFlat, "ValueFlat") + g.gasMeter.ConsumeGas(g.gasConfig.ValueCostPerByte*sdk.Gas(len(value)), "ValuePerByte") return value } diff --git a/store/gaskvstore_test.go b/store/gaskvstore_test.go index fe84affa2889..b2c23c955331 100644 --- a/store/gaskvstore_test.go +++ b/store/gaskvstore_test.go @@ -3,21 +3,23 @@ package store import ( "testing" + dbm "github.com/tendermint/tendermint/libs/db" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" - dbm "github.com/tendermint/tendermint/libs/db" ) func newGasKVStore() KVStore { meter := sdk.NewGasMeter(1000) mem := dbStoreAdapter{dbm.NewMemDB()} - return NewGasKVStore(meter, mem) + return NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) } func TestGasKVStoreBasic(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(1000) - st := NewGasKVStore(meter, mem) + st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") st.Set(keyFmt(1), valFmt(1)) require.Equal(t, valFmt(1), st.Get(keyFmt(1))) @@ -29,7 +31,7 @@ func TestGasKVStoreBasic(t *testing.T) { func TestGasKVStoreIterator(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(1000) - st := NewGasKVStore(meter, mem) + st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") require.Empty(t, st.Get(keyFmt(2)), "Expected `key2` to be empty") st.Set(keyFmt(1), valFmt(1)) @@ -53,16 +55,51 @@ func TestGasKVStoreIterator(t *testing.T) { func TestGasKVStoreOutOfGasSet(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(0) - st := NewGasKVStore(meter, mem) + st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) require.Panics(t, func() { st.Set(keyFmt(1), valFmt(1)) }, "Expected out-of-gas") } func TestGasKVStoreOutOfGasIterator(t *testing.T) { mem := dbStoreAdapter{dbm.NewMemDB()} meter := sdk.NewGasMeter(200) - st := NewGasKVStore(meter, mem) + st := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) st.Set(keyFmt(1), valFmt(1)) iterator := st.Iterator(nil, nil) iterator.Next() require.Panics(t, func() { iterator.Value() }, "Expected out-of-gas") } + +func testGasKVStoreWrap(t *testing.T, store KVStore) { + meter := sdk.NewGasMeter(10000) + + store = store.Gas(meter, sdk.GasConfig{HasCost: 10}) + require.Equal(t, int64(0), meter.GasConsumed()) + + store.Has([]byte("key")) + require.Equal(t, int64(10), meter.GasConsumed()) + + store = store.Gas(meter, sdk.GasConfig{HasCost: 20}) + + store.Has([]byte("key")) + require.Equal(t, int64(40), meter.GasConsumed()) +} + +func TestGasKVStoreWrap(t *testing.T) { + db := dbm.NewMemDB() + tree, _ := newTree(t, db) + iavl := newIAVLStore(tree, numRecent, storeEvery) + testGasKVStoreWrap(t, iavl) + + st := NewCacheKVStore(iavl) + testGasKVStoreWrap(t, st) + + pref := st.Prefix([]byte("prefix")) + testGasKVStoreWrap(t, pref) + + dsa := dbStoreAdapter{dbm.NewMemDB()} + testGasKVStoreWrap(t, dsa) + + ts := newTransientStore() + testGasKVStoreWrap(t, ts) + +} diff --git a/store/iavlstore.go b/store/iavlstore.go index 80c693288c18..6ab50dfc960d 100644 --- a/store/iavlstore.go +++ b/store/iavlstore.go @@ -67,7 +67,6 @@ func newIAVLStore(tree *iavl.VersionedTree, numRecent int64, storeEvery int64) * // Implements Committer. func (st *iavlStore) Commit() CommitID { - // Save a new version. hash, version, err := st.tree.SaveVersion() if err != nil { @@ -161,6 +160,11 @@ func (st *iavlStore) Prefix(prefix []byte) KVStore { return prefixStore{st, prefix} } +// Implements KVStore +func (st *iavlStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, st) +} + // Implements KVStore. func (st *iavlStore) Iterator(start, end []byte) Iterator { return newIAVLIterator(st.tree.Tree(), start, end, true) diff --git a/store/iavlstore_test.go b/store/iavlstore_test.go index ab117252fb94..d081b4576fde 100644 --- a/store/iavlstore_test.go +++ b/store/iavlstore_test.go @@ -446,6 +446,7 @@ func TestIAVLStoreQuery(t *testing.T) { require.Equal(t, uint32(sdk.CodeOK), qres.Code) require.Equal(t, v3, qres.Value) query2 := abci.RequestQuery{Path: "/key", Data: k2, Height: cid.Version} + qres = iavlStore.Query(query2) require.Equal(t, uint32(sdk.CodeOK), qres.Code) require.Equal(t, v2, qres.Value) diff --git a/store/prefixstore.go b/store/prefixstore.go index 7e7f8d49313f..835a210385c4 100644 --- a/store/prefixstore.go +++ b/store/prefixstore.go @@ -6,14 +6,16 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +var _ KVStore = prefixStore{} + type prefixStore struct { - store KVStore + parent KVStore prefix []byte } // Implements Store func (s prefixStore) GetStoreType() StoreType { - return sdk.StoreTypePrefix + return s.parent.GetStoreType() } // Implements CacheWrap @@ -28,22 +30,22 @@ func (s prefixStore) CacheWrapWithTrace(w io.Writer, tc TraceContext) CacheWrap // Implements KVStore func (s prefixStore) Get(key []byte) []byte { - return s.store.Get(append(s.prefix, key...)) + return s.parent.Get(append(s.prefix, key...)) } // Implements KVStore func (s prefixStore) Has(key []byte) bool { - return s.store.Has(append(s.prefix, key...)) + return s.parent.Has(append(s.prefix, key...)) } // Implements KVStore func (s prefixStore) Set(key, value []byte) { - s.store.Set(append(s.prefix, key...), value) + s.parent.Set(append(s.prefix, key...), value) } // Implements KVStore func (s prefixStore) Delete(key []byte) { - s.store.Delete(append(s.prefix, key...)) + s.parent.Delete(append(s.prefix, key...)) } // Implements KVStore @@ -51,6 +53,11 @@ func (s prefixStore) Prefix(prefix []byte) KVStore { return prefixStore{s, prefix} } +// Implements KVStore +func (s prefixStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, s) +} + // Implements KVStore func (s prefixStore) Iterator(start, end []byte) Iterator { if end == nil { @@ -60,7 +67,7 @@ func (s prefixStore) Iterator(start, end []byte) Iterator { } return prefixIterator{ prefix: s.prefix, - iter: s.store.Iterator(append(s.prefix, start...), end), + iter: s.parent.Iterator(append(s.prefix, start...), end), } } @@ -73,7 +80,7 @@ func (s prefixStore) ReverseIterator(start, end []byte) Iterator { } return prefixIterator{ prefix: s.prefix, - iter: s.store.ReverseIterator(start, end), + iter: s.parent.ReverseIterator(start, end), } } diff --git a/store/prefixstore_test.go b/store/prefixstore_test.go index 1961bb4bb07b..ff37b27d4719 100644 --- a/store/prefixstore_test.go +++ b/store/prefixstore_test.go @@ -4,7 +4,6 @@ import ( "math/rand" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/iavl" @@ -35,32 +34,34 @@ func setRandomKVPairs(t *testing.T, store KVStore) []kvpair { func testPrefixStore(t *testing.T, baseStore KVStore, prefix []byte) { prefixStore := baseStore.Prefix(prefix) + prefixPrefixStore := prefixStore.Prefix([]byte("prefix")) - kvps := setRandomKVPairs(t, prefixStore) - - buf := make([]byte, 32) - for i := 0; i < 20; i++ { - rand.Read(buf) - assert.False(t, prefixStore.Has(buf)) - assert.Nil(t, prefixStore.Get(buf)) - assert.False(t, baseStore.Has(append(prefix, buf...))) - assert.Nil(t, baseStore.Get(append(prefix, buf...))) - } + kvps := setRandomKVPairs(t, prefixPrefixStore) for i := 0; i < 20; i++ { key := kvps[i].key - assert.True(t, prefixStore.Has(key)) - assert.Equal(t, kvps[i].value, prefixStore.Get(key)) - assert.True(t, baseStore.Has(append(prefix, key...))) - assert.Equal(t, kvps[i].value, baseStore.Get(append(prefix, key...))) - - prefixStore.Delete(key) - assert.False(t, prefixStore.Has(key)) - assert.Nil(t, prefixStore.Get(key)) - assert.False(t, baseStore.Has(append(prefix, buf...))) - assert.Nil(t, baseStore.Get(append(prefix, buf...))) + value := kvps[i].value + require.True(t, prefixPrefixStore.Has(key)) + require.Equal(t, value, prefixPrefixStore.Get(key)) + + key = append([]byte("prefix"), key...) + require.True(t, prefixStore.Has(key)) + require.Equal(t, value, prefixStore.Get(key)) + key = append(prefix, key...) + require.True(t, baseStore.Has(key)) + require.Equal(t, value, baseStore.Get(key)) + + key = kvps[i].key + prefixPrefixStore.Delete(key) + require.False(t, prefixPrefixStore.Has(key)) + require.Nil(t, prefixPrefixStore.Get(key)) + key = append([]byte("prefix"), key...) + require.False(t, prefixStore.Has(key)) + require.Nil(t, prefixStore.Get(key)) + key = append(prefix, key...) + require.False(t, baseStore.Has(key)) + require.Nil(t, baseStore.Get(key)) } - } func TestIAVLStorePrefix(t *testing.T) { @@ -80,7 +81,7 @@ func TestCacheKVStorePrefix(t *testing.T) { func TestGasKVStorePrefix(t *testing.T) { meter := sdk.NewGasMeter(100000000) mem := dbStoreAdapter{dbm.NewMemDB()} - gasStore := NewGasKVStore(meter, mem) + gasStore := NewGasKVStore(meter, sdk.DefaultGasConfig(), mem) testPrefixStore(t, gasStore, []byte("test")) } diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 255afffbd5a1..8eb1c33dd55f 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -99,7 +99,7 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { if ver == 0 { for key, storeParams := range rs.storesParams { id := CommitID{} - store, err := rs.loadCommitStoreFromParams(id, storeParams) + store, err := rs.loadCommitStoreFromParams(key, id, storeParams) if err != nil { return fmt.Errorf("failed to load rootMultiStore: %v", err) } @@ -122,17 +122,20 @@ func (rs *rootMultiStore) LoadVersion(ver int64) error { for _, storeInfo := range cInfo.StoreInfos { key, commitID := rs.nameToKey(storeInfo.Name), storeInfo.Core.CommitID storeParams := rs.storesParams[key] - store, err := rs.loadCommitStoreFromParams(commitID, storeParams) + store, err := rs.loadCommitStoreFromParams(key, commitID, storeParams) if err != nil { return fmt.Errorf("failed to load rootMultiStore: %v", err) } newStores[key] = store } - // If any CommitStoreLoaders were not used, return error. - for key := range rs.storesParams { - if _, ok := newStores[key]; !ok { - return fmt.Errorf("unused CommitStoreLoader: %v", key) + // TODO: detecting transient is quite adhoc + // If any nontransient CommitStoreLoaders were not used, return error. + for key, param := range rs.storesParams { + if param.typ != sdk.StoreTypeTransient { + if _, ok := newStores[key]; !ok { + return fmt.Errorf("unused CommitStoreLoader: %v", key) + } } } @@ -242,10 +245,7 @@ func (rs *rootMultiStore) GetKVStore(key StoreKey) KVStore { return store } -// Implements MultiStore. -func (rs *rootMultiStore) GetKVStoreWithGas(meter sdk.GasMeter, key StoreKey) KVStore { - return NewGasKVStore(meter, rs.GetKVStore(key)) -} +// Implements MultiStore // getStoreByName will first convert the original name to // a special key, before looking up the CommitStore. @@ -309,7 +309,7 @@ func parsePath(path string) (storeName string, subpath string, err sdk.Error) { //---------------------------------------- -func (rs *rootMultiStore) loadCommitStoreFromParams(id CommitID, params storeParams) (store CommitStore, err error) { +func (rs *rootMultiStore) loadCommitStoreFromParams(key sdk.StoreKey, id CommitID, params storeParams) (store CommitStore, err error) { var db dbm.DB if params.db != nil { db = dbm.NewPrefixDB(params.db, []byte("s/_/")) @@ -326,6 +326,14 @@ func (rs *rootMultiStore) loadCommitStoreFromParams(id CommitID, params storePar return case sdk.StoreTypeDB: panic("dbm.DB is not a CommitStore") + case sdk.StoreTypeTransient: + _, ok := key.(*sdk.TransientStoreKey) + if !ok { + err = fmt.Errorf("invalid StoreKey for StoreTypeTransient: %s", key.String()) + return + } + store = newTransientStore() + return default: panic(fmt.Sprintf("unrecognized store type %v", params.typ)) } @@ -440,6 +448,10 @@ func commitStores(version int64, storeMap map[StoreKey]CommitStore) commitInfo { // Commit commitID := store.Commit() + if store.GetStoreType() == sdk.StoreTypeTransient { + continue + } + // Record CommitID si := storeInfo{} si.Name = key.Name() diff --git a/store/rootmultistore_test.go b/store/rootmultistore_test.go index f564114626aa..a9cac2975ae7 100644 --- a/store/rootmultistore_test.go +++ b/store/rootmultistore_test.go @@ -13,6 +13,14 @@ import ( const useDebugDB = false +func TestStoreType(t *testing.T) { + db := dbm.NewMemDB() + store := NewCommitMultiStore(db) + store.MountStoreWithDB( + sdk.NewKVStoreKey("store1"), sdk.StoreTypeIAVL, db) + +} + func TestMultistoreCommitLoad(t *testing.T) { var db dbm.DB = dbm.NewMemDB() if useDebugDB { diff --git a/store/tracekvstore.go b/store/tracekvstore.go index f769c6690ab4..0224e8c123db 100644 --- a/store/tracekvstore.go +++ b/store/tracekvstore.go @@ -82,6 +82,11 @@ func (tkv *TraceKVStore) Prefix(prefix []byte) KVStore { return prefixStore{tkv, prefix} } +// Gas implements the KVStore interface. +func (tkv *TraceKVStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, tkv.parent) +} + // Iterator implements the KVStore interface. It delegates the Iterator call // the to the parent KVStore. func (tkv *TraceKVStore) Iterator(start, end []byte) sdk.Iterator { diff --git a/store/transientstore.go b/store/transientstore.go new file mode 100644 index 000000000000..1c099fa0ddea --- /dev/null +++ b/store/transientstore.go @@ -0,0 +1,43 @@ +package store + +import ( + dbm "github.com/tendermint/tendermint/libs/db" +) + +var _ KVStore = (*transientStore)(nil) + +// transientStore is a wrapper for a MemDB with Commiter implementation +type transientStore struct { + dbStoreAdapter +} + +// Constructs new MemDB adapter +func newTransientStore() *transientStore { + return &transientStore{dbStoreAdapter{dbm.NewMemDB()}} +} + +// Implements CommitStore +// Commit cleans up transientStore. +func (ts *transientStore) Commit() (id CommitID) { + ts.dbStoreAdapter = dbStoreAdapter{dbm.NewMemDB()} + return +} + +// Implements CommitStore +func (ts *transientStore) SetPruning(pruning PruningStrategy) { +} + +// Implements CommitStore +func (ts *transientStore) LastCommitID() (id CommitID) { + return +} + +// Implements KVStore +func (ts *transientStore) Prefix(prefix []byte) KVStore { + return prefixStore{ts, prefix} +} + +// Implements KVStore +func (ts *transientStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, ts) +} diff --git a/store/transientstore_test.go b/store/transientstore_test.go new file mode 100644 index 000000000000..1c9e98cfaaa1 --- /dev/null +++ b/store/transientstore_test.go @@ -0,0 +1,23 @@ +package store + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +var k, v = []byte("hello"), []byte("world") + +func TestTransientStore(t *testing.T) { + tstore := newTransientStore() + + require.Nil(t, tstore.Get(k)) + + tstore.Set(k, v) + + require.Equal(t, v, tstore.Get(k)) + + tstore.Commit() + + require.Nil(t, tstore.Get(k)) +} diff --git a/store/types.go b/store/types.go index 4c36a004b340..353cd2e3cd77 100644 --- a/store/types.go +++ b/store/types.go @@ -7,6 +7,7 @@ import ( // Import cosmos-sdk/types/store.go for convenience. // nolint type ( + PruningStrategy = types.PruningStrategy Store = types.Store Committer = types.Committer CommitStore = types.CommitStore @@ -25,4 +26,7 @@ type ( StoreType = types.StoreType Queryable = types.Queryable TraceContext = types.TraceContext + Gas = types.Gas + GasMeter = types.GasMeter + GasConfig = types.GasConfig ) diff --git a/types/context.go b/types/context.go index 064625028edf..44b0474e8b6b 100644 --- a/types/context.go +++ b/types/context.go @@ -31,7 +31,6 @@ type Context struct { // create a new context func NewContext(ms MultiStore, header abci.Header, isCheckTx bool, logger log.Logger) Context { - c := Context{ Context: context.Background(), pst: newThePast(), @@ -70,7 +69,12 @@ func (c Context) Value(key interface{}) interface{} { // KVStore fetches a KVStore from the MultiStore. func (c Context) KVStore(key StoreKey) KVStore { - return c.multiStore().GetKVStoreWithGas(c.GasMeter(), key) + return c.multiStore().GetKVStore(key).Gas(c.GasMeter(), cachedDefaultGasConfig) +} + +// TransientStore fetches a TransientStore from the MultiStore. +func (c Context) TransientStore(key StoreKey) KVStore { + return c.multiStore().GetKVStore(key).Gas(c.GasMeter(), cachedTransientGasConfig) } //---------------------------------------- @@ -125,6 +129,7 @@ const ( contextKeyMultiStore contextKey = iota contextKeyBlockHeader contextKeyBlockHeight + contextKeyConsensusParams contextKeyChainID contextKeyTxBytes contextKeyLogger @@ -146,6 +151,9 @@ func (c Context) BlockHeader() abci.Header { func (c Context) BlockHeight() int64 { return c.Value(contextKeyBlockHeight).(int64) } +func (c Context) ConsensusParams() abci.ConsensusParams { + return c.Value(contextKeyConsensusParams).(abci.ConsensusParams) +} func (c Context) ChainID() string { return c.Value(contextKeyChainID).(string) } @@ -171,6 +179,13 @@ func (c Context) WithBlockHeader(header abci.Header) Context { func (c Context) WithBlockHeight(height int64) Context { return c.withValue(contextKeyBlockHeight, height) } +func (c Context) WithConsensusParams(params *abci.ConsensusParams) Context { + if params == nil { + return c + } + return c.withValue(contextKeyConsensusParams, params). + WithGasMeter(NewGasMeter(params.TxSize.MaxGas)) +} func (c Context) WithChainID(chainID string) Context { return c.withValue(contextKeyChainID, chainID) } diff --git a/types/gas.go b/types/gas.go index 18d3aa570205..e8486c81361e 100644 --- a/types/gas.go +++ b/types/gas.go @@ -54,3 +54,40 @@ func (g *infiniteGasMeter) GasConsumed() Gas { func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) { g.consumed += amount } + +// GasConfig defines gas cost for each operation on KVStores +type GasConfig struct { + HasCost Gas + ReadCostFlat Gas + ReadCostPerByte Gas + WriteCostFlat Gas + WriteCostPerByte Gas + KeyCostFlat Gas + ValueCostFlat Gas + ValueCostPerByte Gas +} + +var ( + cachedDefaultGasConfig = DefaultGasConfig() + cachedTransientGasConfig = TransientGasConfig() +) + +// Default gas config for KVStores +func DefaultGasConfig() GasConfig { + return GasConfig{ + HasCost: 10, + ReadCostFlat: 10, + ReadCostPerByte: 1, + WriteCostFlat: 10, + WriteCostPerByte: 10, + KeyCostFlat: 5, + ValueCostFlat: 10, + ValueCostPerByte: 1, + } +} + +// Default gas config for TransientStores +func TransientGasConfig() GasConfig { + // TODO: define gasconfig for transient stores + return DefaultGasConfig() +} diff --git a/types/store.go b/types/store.go index e8fe9067a634..e895b24c94d8 100644 --- a/types/store.go +++ b/types/store.go @@ -65,7 +65,6 @@ type MultiStore interface { //nolint // Convenience for fetching substores. GetStore(StoreKey) Store GetKVStore(StoreKey) KVStore - GetKVStoreWithGas(GasMeter, StoreKey) KVStore // TracingEnabled returns if tracing is enabled for the MultiStore. TracingEnabled() bool @@ -134,9 +133,6 @@ type KVStore interface { // Delete deletes the key. Panics on nil key. Delete(key []byte) - // Prefix applied keys with the argument - Prefix(prefix []byte) KVStore - // Iterator over a domain of keys in ascending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. // Iterator must be closed by caller. @@ -155,6 +151,16 @@ type KVStore interface { // TODO Not yet implemented. // GetSubKVStore(key *storeKey) KVStore + + // Prefix applied keys with the argument + // CONTRACT: when Prefix is called on a KVStore more than once, + // the concatanation of the prefixes is applied + Prefix(prefix []byte) KVStore + + // Gas consuming store + // CONTRACT: when Gas is called on a KVStore more than once, + // the concatanation of the meters/configs is applied + Gas(GasMeter, GasConfig) KVStore } // Alias iterator to db's Iterator for convenience. @@ -186,11 +192,6 @@ type CommitKVStore interface { KVStore } -// Wrapper for StoreKeys to get KVStores -type KVStoreGetter interface { - KVStore(Context) KVStore -} - //---------------------------------------- // CacheWrap @@ -245,7 +246,7 @@ const ( StoreTypeMulti StoreType = iota StoreTypeDB StoreTypeIAVL - StoreTypePrefix + StoreTypeTransient ) //---------------------------------------- @@ -279,11 +280,6 @@ func (key *KVStoreKey) String() string { return fmt.Sprintf("KVStoreKey{%p, %s}", key, key.name) } -// Implements KVStoreGetter -func (key *KVStoreKey) KVStore(ctx Context) KVStore { - return ctx.KVStore(key) -} - // PrefixEndBytes returns the []byte that would end a // range query for all []byte with a certain prefix // Deals with last byte of prefix being FF without overflowing @@ -310,19 +306,27 @@ func PrefixEndBytes(prefix []byte) []byte { return end } -// Getter struct for prefixed stores -type PrefixStoreGetter struct { - key StoreKey - prefix []byte +// TransientStoreKey is used for indexing transient stores in a MultiStore +type TransientStoreKey struct { + name string +} + +// Constructs new TransientStoreKey +// Must return a pointer according to the ocap principle +func NewTransientStoreKey(name string) *TransientStoreKey { + return &TransientStoreKey{ + name: name, + } } -func NewPrefixStoreGetter(key StoreKey, prefix []byte) PrefixStoreGetter { - return PrefixStoreGetter{key, prefix} +// Implements StoreKey +func (key *TransientStoreKey) Name() string { + return key.name } -// Implements sdk.KVStoreGetter -func (getter PrefixStoreGetter) KVStore(ctx Context) KVStore { - return ctx.KVStore(getter.key).Prefix(getter.prefix) +// Implements StoreKey +func (key *TransientStoreKey) String() string { + return fmt.Sprintf("TransientStoreKey{%p, %s}", key, key.name) } //----------------------------------------