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(store/v2): gaskv store #18240

Merged
merged 29 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cb7d4b0
init commit
alexanderbez Oct 24, 2023
befd5df
updates
alexanderbez Oct 24, 2023
ef597c6
Merge branch 'main' into bez/18225-store-v2-gaskv-store
alexanderbez Oct 24, 2023
512f8d6
updates
alexanderbez Oct 24, 2023
c4d76ae
updates
alexanderbez Oct 24, 2023
cc268b5
updates
alexanderbez Oct 24, 2023
c2c4e2c
updates
alexanderbez Oct 24, 2023
dba50b4
updates
alexanderbez Oct 24, 2023
c891c47
updates
alexanderbez Oct 24, 2023
b5b6315
updates
alexanderbez Oct 24, 2023
700a7b0
Merge branch 'main' into bez/18225-store-v2-gaskv-store
alexanderbez Oct 24, 2023
b94a997
updates
alexanderbez Oct 24, 2023
cd02d13
updates
alexanderbez Oct 24, 2023
ffe6b61
updates
alexanderbez Oct 24, 2023
7ce1c4c
updates
alexanderbez Oct 25, 2023
04f25b0
updates
alexanderbez Oct 25, 2023
373b643
updates
alexanderbez Oct 25, 2023
3ab1ddc
updates
alexanderbez Oct 25, 2023
1b6c905
updates
alexanderbez Oct 25, 2023
2238f44
Merge branch 'main' into bez/18225-store-v2-gaskv-store
alexanderbez Oct 25, 2023
af99893
lint++
alexanderbez Oct 25, 2023
78bf4e6
Merge branch 'bez/18225-store-v2-gaskv-store' of github.com:cosmos/co…
alexanderbez Oct 25, 2023
f32b30f
Merge branch 'main' into bez/18225-store-v2-gaskv-store
alexanderbez Oct 25, 2023
0145d72
lint++
alexanderbez Oct 25, 2023
0c4f0bd
Merge branch 'bez/18225-store-v2-gaskv-store' of github.com:cosmos/co…
alexanderbez Oct 25, 2023
1b3beef
lint++
alexanderbez Oct 25, 2023
a9be8ea
lint++
alexanderbez Oct 26, 2023
68f8156
Merge branch 'main' into bez/18225-store-v2-gaskv-store
alexanderbez Oct 26, 2023
88d7d0a
godoc++
alexanderbez Oct 26, 2023
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
238 changes: 238 additions & 0 deletions store/gas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package store

import (
"fmt"
"math"
)

// Gas defines type alias of uint64 for gas consumption. Gas is measured by the
// SDK for store operations such as Get and Set calls. In addition, callers have
// the ability to explicitly charge gas for costly operations such as signature
// verification.
type Gas uint64

// Gas consumption descriptors.
const (
GasDescIterNextCostFlat = "IterNextFlat"
GasDescValuePerByte = "ValuePerByte"
GasDescWritePerByte = "WritePerByte"
GasDescReadPerByte = "ReadPerByte"
GasDescWriteCostFlat = "WriteFlat"
GasDescReadCostFlat = "ReadFlat"
GasDescHas = "Has"
GasDescDelete = "Delete"
)

type (
// ErrorNegativeGasConsumed defines an error thrown when the amount of gas refunded
// results in a negative gas consumed amount.
ErrorNegativeGasConsumed struct {
Descriptor string
}

// ErrorOutOfGas defines an error thrown when an action results in out of gas.
ErrorOutOfGas struct {
Descriptor string
}

// ErrorGasOverflow defines an error thrown when an action results gas consumption
// unsigned integer overflow.
ErrorGasOverflow struct {
Descriptor string
}
)

// GasMeter defines an interface for gas consumption tracking.
type GasMeter interface {
// GasConsumed returns the amount of gas consumed so far.
GasConsumed() Gas
// GasConsumedToLimit returns the gas limit if gas consumed is past the limit,
// otherwise it returns the consumed gas so far.
GasConsumedToLimit() Gas
// GasRemaining returns the gas left in the GasMeter.
GasRemaining() Gas
// Limit returns the gas limit (if any).
Limit() Gas
// ConsumeGas adds the given amount of gas to the gas consumed and should panic
// if it overflows the gas limit (if any).
ConsumeGas(amount Gas, descriptor string)
// RefundGas will deduct the given amount from the gas consumed so far. If the
// amount is greater than the gas consumed, the function should panic.
RefundGas(amount Gas, descriptor string)
// IsPastLimit returns <true> if the gas consumed so far is past the limit (if any),
// otherwise it returns <false>.
IsPastLimit() bool
// IsOutOfGas returns <true> if the gas consumed so far is greater than or equal
// to gas limit (if any), otherwise it returns <false>.
IsOutOfGas() bool

fmt.Stringer
}

// GasConfig defines gas cost for each operation on a KVStore.
type GasConfig struct {
HasCost Gas
DeleteCost Gas
ReadCostFlat Gas
ReadCostPerByte Gas
WriteCostFlat Gas
WriteCostPerByte Gas
IterNextCostFlat Gas
}

// DefaultGasConfig returns a default GasConfig for gas metering.
//
// Note, these values are essentially arbitrary. They are not based on any specific
// computation or measurements, but mainly reflect relative costs, i.e. writes
// should be more expensive than reads.
func DefaultGasConfig() GasConfig {
return GasConfig{
HasCost: 1000,
ReadCostFlat: 1000,
ReadCostPerByte: 3,
DeleteCost: 1500,
WriteCostFlat: 2000,
WriteCostPerByte: 30,
IterNextCostFlat: 30,
}
}

// defaultGasMeter defines a default implementation of a GasMeter.
type defaultGasMeter struct {
limit Gas
consumed Gas
}

// NewGasMeter returns a reference to a GasMeter with the provided limit.
func NewGasMeter(limit Gas) GasMeter {
return &defaultGasMeter{
limit: limit,
}
}

func (gm *defaultGasMeter) GasConsumed() Gas {
return gm.consumed
}

// NOTE: This behavior should only be called when recovering from a panic when
// BlockGasMeter consumes gas past the gas limit.
func (gm *defaultGasMeter) GasConsumedToLimit() Gas {
if gm.IsPastLimit() {
return gm.limit
}

return gm.consumed
}

func (gm *defaultGasMeter) GasRemaining() Gas {
if gm.IsPastLimit() {
return 0
}

return gm.limit - gm.consumed
}

func (gm *defaultGasMeter) Limit() Gas {
return gm.limit
}

func (gm *defaultGasMeter) ConsumeGas(amount Gas, descriptor string) {
var overflow bool

gm.consumed, overflow = addGasOverflow(gm.consumed, amount)
if overflow {
gm.consumed = math.MaxUint64
panic(ErrorGasOverflow{descriptor})
}

if gm.consumed > gm.limit {
panic(ErrorOutOfGas{descriptor})
}
}

func (gm *defaultGasMeter) RefundGas(amount Gas, descriptor string) {
if gm.consumed < amount {
panic(ErrorNegativeGasConsumed{Descriptor: descriptor})
}

gm.consumed -= amount
}

func (gm *defaultGasMeter) IsPastLimit() bool {
return gm.consumed > gm.limit
}

func (gm *defaultGasMeter) IsOutOfGas() bool {
return gm.consumed >= gm.limit
}

func (gm *defaultGasMeter) String() string {
return fmt.Sprintf("%T{limit: %d, consumed: %d}", gm, gm.limit, gm.consumed)
}

// infiniteGasMeter defines a GasMeter with an infinite gas limit.
type infiniteGasMeter struct {
consumed Gas
}

// NewInfiniteGasMeter returns a reference to a GasMeter with an infinite gas limit.
func NewInfiniteGasMeter() GasMeter {
return &infiniteGasMeter{
consumed: 0,
}
}

func (gm *infiniteGasMeter) GasConsumed() Gas {
return gm.consumed
}

func (gm *infiniteGasMeter) GasConsumedToLimit() Gas {
return gm.consumed
}

func (_ *infiniteGasMeter) GasRemaining() Gas {
return math.MaxUint64
}

func (_ *infiniteGasMeter) Limit() Gas {
return math.MaxUint64
}

func (gm *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) {
var overflow bool

gm.consumed, overflow = addGasOverflow(gm.consumed, amount)
if overflow {
panic(ErrorGasOverflow{descriptor})
}
}

func (gm *infiniteGasMeter) RefundGas(amount Gas, descriptor string) {
if gm.consumed < amount {
panic(ErrorNegativeGasConsumed{Descriptor: descriptor})
}

gm.consumed -= amount
}

func (_ *infiniteGasMeter) IsPastLimit() bool {
return false
}

func (_ *infiniteGasMeter) IsOutOfGas() bool {
return false
}

func (gm *infiniteGasMeter) String() string {
return fmt.Sprintf("%T{consumed: %d}", gm, gm.consumed)
}

// addGasOverflow performs the addition operation on two uint64 integers and
// returns a boolean on whether or not the result overflows.
func addGasOverflow(a, b Gas) (Gas, bool) {
if math.MaxUint64-a < b {
return 0, true
}

return a + b, false
}
63 changes: 63 additions & 0 deletions store/kv/gas/iterator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package gas

import "cosmossdk.io/store/v2"

var _ store.Iterator = (*iterator)(nil)

type iterator struct {
gasMeter store.GasMeter
gasConfig store.GasConfig
parent store.Iterator
}

func newIterator(parent store.Iterator, gm store.GasMeter, gc store.GasConfig) store.Iterator {
return &iterator{
parent: parent,
gasConfig: gc,
gasMeter: gm,
}
}

func (itr *iterator) Domain() ([]byte, []byte) {
return itr.parent.Domain()
}

func (itr *iterator) Valid() bool {
return itr.parent.Valid()
}

func (itr *iterator) Key() []byte {
return itr.parent.Key()
}

func (itr *iterator) Value() []byte {
return itr.parent.Value()
}

func (itr *iterator) Next() bool {
itr.consumeGasSeek()
return itr.parent.Next()
}

func (itr *iterator) Close() {
itr.parent.Close()
}

func (itr *iterator) Error() error {
return itr.parent.Error()
}

// consumeGasSeek consumes a fixed amount of gas for each iteration step and a
// variable gas cost based on the current key and value's length. This is called
// prior to the iterator's Next() call.
func (itr *iterator) consumeGasSeek() {
if itr.Valid() {
key := itr.Key()
value := itr.Value()

itr.gasMeter.ConsumeGas(itr.gasConfig.ReadCostPerByte*store.Gas(len(key)), store.GasDescValuePerByte)
itr.gasMeter.ConsumeGas(itr.gasConfig.ReadCostPerByte*store.Gas(len(value)), store.GasDescValuePerByte)
}

itr.gasMeter.ConsumeGas(itr.gasConfig.IterNextCostFlat, store.GasDescIterNextCostFlat)
}
89 changes: 89 additions & 0 deletions store/kv/gas/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package gas

import (
"fmt"
"io"

"cosmossdk.io/store/v2"
)

var _ store.BranchedKVStore = (*Store)(nil)

type Store struct {
parent store.KVStore
gasMeter store.GasMeter
gasConfig store.GasConfig
}

func New(p store.KVStore, gm store.GasMeter, gc store.GasConfig) store.BranchedKVStore {
return &Store{
parent: p,
gasMeter: gm,
gasConfig: gc,
}
}

func (s *Store) GetStoreKey() string {
return s.parent.GetStoreKey()
}

func (s *Store) GetStoreType() store.StoreType {
return s.parent.GetStoreType()
}

func (s *Store) Get(key []byte) []byte {
s.gasMeter.ConsumeGas(s.gasConfig.ReadCostFlat, store.GasDescReadCostFlat)

value := s.parent.Get(key)
s.gasMeter.ConsumeGas(s.gasConfig.ReadCostPerByte*store.Gas(len(key)), store.GasDescReadPerByte)
s.gasMeter.ConsumeGas(s.gasConfig.ReadCostPerByte*store.Gas(len(value)), store.GasDescReadPerByte)

return value
}

func (s *Store) Has(key []byte) bool {
s.gasMeter.ConsumeGas(s.gasConfig.HasCost, store.GasDescHas)
return s.parent.Has(key)
}

func (s *Store) Set(key, value []byte) {
s.gasMeter.ConsumeGas(s.gasConfig.WriteCostFlat, store.GasDescWriteCostFlat)
s.gasMeter.ConsumeGas(s.gasConfig.WriteCostPerByte*store.Gas(len(key)), store.GasDescWritePerByte)
s.gasMeter.ConsumeGas(s.gasConfig.WriteCostPerByte*store.Gas(len(value)), store.GasDescWritePerByte)
s.parent.Set(key, value)
}

func (s *Store) Delete(key []byte) {
s.gasMeter.ConsumeGas(s.gasConfig.DeleteCost, store.GasDescDelete)
s.parent.Delete(key)
}

func (s *Store) GetChangeset() *store.Changeset {
return s.parent.GetChangeset()
}

func (s *Store) Reset() error {
return s.parent.Reset()
}

func (s *Store) Write() {
if b, ok := s.parent.(store.BranchedKVStore); ok {
b.Write()
}
}

func (s *Store) Branch() store.BranchedKVStore {
panic(fmt.Sprintf("cannot call Branch() on %T", s))
}

func (s *Store) BranchWithTrace(_ io.Writer, _ store.TraceContext) store.BranchedKVStore {
panic(fmt.Sprintf("cannot call BranchWithTrace() on %T", s))
}

func (s *Store) Iterator(start, end []byte) store.Iterator {
return newIterator(s.parent.Iterator(start, end), s.gasMeter, s.gasConfig)
}

func (s *Store) ReverseIterator(start, end []byte) store.Iterator {
return newIterator(s.parent.ReverseIterator(start, end), s.gasMeter, s.gasConfig)
}
Loading