refactor(store/v2): remove GasKV and Gas types (#18912)

This commit is contained in:
Aleksandr Bezobchuk 2024-01-02 05:26:08 -05:00 committed by GitHub
parent d55985637e
commit bb99dda6d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 0 additions and 524 deletions

View File

@ -1,261 +0,0 @@
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
}
)
func (e ErrorNegativeGasConsumed) Error() string {
return fmt.Sprintf("negative gas consumed: %s", e.Descriptor)
}
func (e ErrorOutOfGas) Error() string {
return fmt.Sprintf("out of gas: %s", e.Descriptor)
}
func (e ErrorGasOverflow) Error() string {
return fmt.Sprintf("gas overflow: %s", e.Descriptor)
}
// 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 should reflect a fixed cost for a Has() call on a store.
HasCost Gas
// DeleteCost should reflect a fixed cost for a Delete() call on a store.
DeleteCost Gas
// ReadCostFlat should reflect a fixed cost for a Get() call on a store.
ReadCostFlat Gas
// ReadCostPerByte should reflect a fixed cost, per-byte on the key and value,
// for a Get() call on a store. Note, this cost can also be used on iteration
// seeks.
ReadCostPerByte Gas
// WriteCostFlat should reflect a fixed cost for a Set() call on a store.
WriteCostFlat Gas
// WriteCostPerByte should reflect a fixed cost, per-byte on the key and value,
// for a Set() call on a store.
WriteCostPerByte Gas
// IterNextCostFlat should reflect a fixed cost for each call to Next() on an
// iterator.
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) {
newConsumed, overflow := addGasOverflow(gm.consumed, amount)
if overflow {
panic(ErrorGasOverflow{descriptor})
}
if newConsumed > gm.limit {
gm.consumed = math.MaxUint64
panic(ErrorOutOfGas{descriptor})
}
gm.consumed = newConsumed
}
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
}

View File

@ -1,63 +0,0 @@
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)
}

View File

@ -1,89 +0,0 @@
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(toVersion uint64) error {
return s.parent.Reset(toVersion)
}
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)
}

View File

@ -1,111 +0,0 @@
package gas_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/suite"
"cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/kv/gas"
"cosmossdk.io/store/v2/kv/mem"
)
const (
storeKey = "storeKey"
gasLimit = store.Gas(1_000_000)
)
type StoreTestSuite struct {
suite.Suite
parent store.KVStore
gasKVStore store.BranchedKVStore
gasMeter store.GasMeter
}
func TestStorageTestSuite(t *testing.T) {
suite.Run(t, &StoreTestSuite{})
}
func (s *StoreTestSuite) SetupTest() {
s.parent = mem.New(storeKey)
s.gasMeter = store.NewGasMeter(gasLimit)
s.gasKVStore = gas.New(s.parent, s.gasMeter, store.DefaultGasConfig())
}
func (s *StoreTestSuite) TearDownTest() {
err := s.gasKVStore.Reset(1)
s.Require().NoError(err)
}
func (s *StoreTestSuite) TestGetStoreKey() {
s.Require().Equal(s.parent.GetStoreKey(), s.gasKVStore.GetStoreKey())
}
func (s *StoreTestSuite) TestGetStoreType() {
s.Require().Equal(s.parent.GetStoreType(), s.gasKVStore.GetStoreType())
}
func (s *StoreTestSuite) TestGet() {
key, value := []byte("key"), []byte("value")
s.parent.Set(key, value)
s.Require().Equal(value, s.gasKVStore.Get(key))
s.Require().Equal(store.Gas(1024), s.gasMeter.GasConsumed())
}
func (s *StoreTestSuite) TestHas() {
key, value := []byte("key"), []byte("value")
s.parent.Set(key, value)
s.Require().True(s.gasKVStore.Has(key))
s.Require().Equal(store.Gas(1000), s.gasMeter.GasConsumed())
}
func (s *StoreTestSuite) TestSet() {
s.gasKVStore.Set([]byte("key"), []byte("value"))
s.Require().Equal(store.Gas(2240), s.gasMeter.GasConsumed())
}
func (s *StoreTestSuite) TestDelete() {
key, value := []byte("key"), []byte("value")
s.parent.Set(key, value)
s.gasKVStore.Delete(key)
s.Require().Equal(store.Gas(1500), s.gasMeter.GasConsumed())
}
func (s *StoreTestSuite) TestIterator() {
for i := 0; i < 100; i++ {
key := fmt.Sprintf("key%03d", i) // key000, key001, ..., key099
val := fmt.Sprintf("val%03d", i) // val000, val001, ..., val099
s.parent.Set([]byte(key), []byte(val))
}
itr := s.gasKVStore.Iterator(nil, nil)
defer itr.Close()
for ; itr.Valid(); itr.Next() {
_ = itr.Key()
_ = itr.Value()
}
s.Require().Equal(store.Gas(6600), s.gasMeter.GasConsumed())
}
func (s *StoreTestSuite) TestReverseIterator() {
for i := 0; i < 100; i++ {
key := fmt.Sprintf("key%03d", i) // key000, key001, ..., key099
val := fmt.Sprintf("val%03d", i) // val000, val001, ..., val099
s.parent.Set([]byte(key), []byte(val))
}
itr := s.gasKVStore.ReverseIterator(nil, nil)
defer itr.Close()
for ; itr.Valid(); itr.Next() {
_ = itr.Key()
_ = itr.Value()
}
s.Require().Equal(store.Gas(6600), s.gasMeter.GasConsumed())
}