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

Strath/tax2gas gasmeter #519

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
60 changes: 60 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
tmos "github.com/cometbft/cometbft/libs/os"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"

sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node"
Expand Down Expand Up @@ -125,6 +126,65 @@ type TerraApp struct {
configurator module.Configurator
}

func (app *TerraApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
res := app.BaseApp.CheckTx(req)

// the ctx here is just for the logger, so it might be okay to remove it
// and the logging in the event parsing
ctx := app.NewContext(true, tmproto.Header{})

// fetch consumed tax gas from events
taxGas := sdkmath.ZeroInt()
for _, event := range res.Events {
if event.Type == "tax2gas" {
for _, attr := range event.Attributes {
if attr.Key == "tax_gas" {
value, ok := sdkmath.NewIntFromString(attr.Value)
if !ok {
ctx.Logger().Error("failed to parse tax gas from events", "value", attr.Value)
continue
}

taxGas = taxGas.Add(value)
}
}
}
}

if taxGas.IsZero() {
return res
}

gasWanted := uint64(res.GasWanted)
gasUsed := uint64(res.GasUsed)
subTaxGas := taxGas.Uint64()

// check how many times the gas used fits into the gas wanted
// if it doesn't fit, we need to adjust the gas wanted
multiple := sdkmath.LegacyNewDec(res.GasWanted).Quo(sdkmath.LegacyNewDec(res.GasUsed).Add(taxGas.ToLegacyDec()))

// we have a multiplier, so we know approximately how often tax gas can be subtracted
// we should assume everything >= 0.9 should be subtracted. Using >= 1 would have issues with approximations
if multiple.GTE(sdkmath.LegacyNewDecWithPrec(9, 1)) {
// adjust gas wanted by the tax gas
subTaxGas = taxGas.ToLegacyDec().Mul(multiple).TruncateInt().Uint64()
}

if gasWanted > subTaxGas {
gasWanted -= subTaxGas
}

// maxGas := app.BaseApp.GetConsensusParams(ctx).Block.MaxGas
ctx.Logger().Info("CheckTx", "GasWanted", res.GasWanted, "GasUsed", res.GasUsed, "TaxGas", taxGas, "GasWantedAdjusted", gasWanted, "Multiple", multiple)

// if the gas wanted is still higher than the gas used, we can adjust the gas wanted
if gasWanted >= gasUsed {
res.GasWanted = int64(gasWanted)
}

return res
}

func init() {
userHomeDir, err := os.UserHomeDir()
if err != nil {
Expand Down
5 changes: 3 additions & 2 deletions custom/auth/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
}

return sdk.ChainAnteDecorators(
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit),
// ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
// wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit),
tax2gaskeeper.NewTax2GasDecorator(options.WasmConfig.SimulationGasLimit),
wasmkeeper.NewCountTXDecorator(options.TXCounterStoreKey),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
Expand Down
9 changes: 8 additions & 1 deletion custom/wasm/keeper/handler_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ func (h SDKMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddr
return nil, nil, err
}

gasMeter, ok := ctx.GasMeter().(*tax2gastypes.Tax2GasMeter)
if !ok {
return nil, nil, errorsmod.Wrap(sdkerrors.ErrInvalidType, "invalid gas meter")
}

gasPrices, ok := ctx.Value(tax2gastypes.FinalGasPrices).(sdk.DecCoins)
if !ok {
gasPrices = h.tax2gaskeeper.GetGasPrices(ctx)
Expand All @@ -96,7 +101,9 @@ func (h SDKMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddr
if err != nil {
return nil, nil, err
}
ctx.TaxGasMeter().ConsumeGas(taxGas, "tax gas")

// Consume tax gas
gasMeter.ConsumeTax(taxGas, "tax gas")

events = eventManager.Events()
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ replace (
// use cometbft
github.com/cometbft/cometbft => github.com/classic-terra/cometbft v0.37.4-terra1
github.com/cometbft/cometbft-db => github.com/cometbft/cometbft-db v0.8.0
github.com/cosmos/cosmos-sdk => github.com/classic-terra/cosmos-sdk v0.47.10-terra.1.0.20240731055430-cf7f52e8ee42
github.com/cosmos/cosmos-sdk => github.com/classic-terra/cosmos-sdk v0.47.10-terra.1
github.com/cosmos/ibc-go/v7 => github.com/classic-terra/ibc-go/v7 v7.4.0-terra
github.com/cosmos/ledger-cosmos-go => github.com/terra-money/ledger-terra-go v0.11.2
// replace goleveldb to optimized one
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/classic-terra/cometbft v0.37.4-terra1 h1:eT5B2n5KKi5WVW+3ZNOVTmtfKKaZrXOLX9G80m9mhZo=
github.com/classic-terra/cometbft v0.37.4-terra1/go.mod h1:vFqj7Qe3uFFJvHZleTJPQDmJ/WscXHi4rKWqiCAaNZk=
github.com/classic-terra/cosmos-sdk v0.47.10-terra.1.0.20240731055430-cf7f52e8ee42 h1:Dr00n/hlWF4biEN/i2MwmNk3iOZ1e+PUraf72gNEqYU=
github.com/classic-terra/cosmos-sdk v0.47.10-terra.1.0.20240731055430-cf7f52e8ee42/go.mod h1:4mBvTB8zevoeTuQufWwTcNnthGG2afXO+9D42BKzlRo=
github.com/classic-terra/cosmos-sdk v0.47.10-terra.1 h1:ek0vQ435fpeP3xGhszDO2yMIRy5XGMj9MCTlvpMUIkw=
github.com/classic-terra/cosmos-sdk v0.47.10-terra.1/go.mod h1:4mBvTB8zevoeTuQufWwTcNnthGG2afXO+9D42BKzlRo=
github.com/classic-terra/goleveldb v0.0.0-20230914223247-2b28f6655121 h1:fjpWDB0hm225wYg9vunyDyTH8ftd5xEUgINJKidj+Tw=
github.com/classic-terra/goleveldb v0.0.0-20230914223247-2b28f6655121/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/classic-terra/ibc-go/v7 v7.4.0-terra h1:hawaq62XKlxyc8xLyIcc6IujDDEbqDBU+2U15SF+hj8=
Expand Down
2 changes: 2 additions & 0 deletions wasmbinding/test/tax_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
core "github.com/classic-terra/core/v3/types"
tax2gastypes "github.com/classic-terra/core/v3/x/tax2gas/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// go test -v -run ^TestWasmTestSuite/TestTax$ github.com/classic-terra/core/v3/wasmbinding/test
func (s *WasmTestSuite) TestTax() {
s.SetupTest()
s.Ctx = s.Ctx.WithGasMeter(tax2gastypes.NewTax2GasMeter(s.Ctx.GasMeter().Limit(), false))
taxRate := sdk.NewDecWithPrec(11, 2) // 11%
s.App.TreasuryKeeper.SetTaxRate(s.Ctx, taxRate) // 11%

Expand Down
10 changes: 6 additions & 4 deletions x/tax2gas/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ func (fd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, nex
// the tax gas that user need to pay
priority = int64(math.MaxInt64)
if !isOracleTx {
if taxGas.IsInt64() {
priority = taxGas.Int64()
}
priority = int64(1)
}
}

Expand All @@ -115,7 +113,11 @@ func (fd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, nex
WithValue(types.TaxGas, taxGas).
WithValue(types.FinalGasPrices, gasPrices)
if !taxGas.IsZero() {
newCtx.TaxGasMeter().ConsumeGas(taxGas, "ante handler taxGas")
gasMeter, ok := ctx.GasMeter().(*types.Tax2GasMeter)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidType, "invalid gas meter")
}
gasMeter.ConsumeTax(taxGas, "ante handler taxGas")
}
newCtx = newCtx.WithValue(types.AnteConsumedGas, gasConsumed)
if paidDenom != "" {
Expand Down
1 change: 1 addition & 0 deletions x/tax2gas/ante/ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func (suite *AnteTestSuite) SetupTest(isCheckTx bool) {
tempDir := suite.T().TempDir()
suite.app, suite.ctx = createTestApp(isCheckTx, tempDir)
suite.ctx = suite.ctx.WithBlockHeight(1)
suite.ctx = suite.ctx.WithGasMeter(tax2gastypes.NewTax2GasMeter(suite.ctx.GasMeter().Limit(), false))

// Set up TxConfig.
encodingConfig := suite.SetupEncoding()
Expand Down
2 changes: 2 additions & 0 deletions x/tax2gas/ante/fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
ibctesting "github.com/cosmos/ibc-go/v7/testing"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
tax2gastypes "github.com/classic-terra/core/v3/x/tax2gas/types"
)

var (
Expand Down Expand Up @@ -436,6 +437,7 @@ func (s *AnteTestSuite) TestDeductFeeDecorator() {
s.Run(tc.name, func() {
tc.mallate()
s.ctx = s.app.BaseApp.NewContext(tc.checkTx, tmproto.Header{})
s.ctx = s.ctx.WithGasMeter(tax2gastypes.NewTax2GasMeter(s.ctx.GasMeter().Limit(), false))

_, err = antehandler(s.ctx, tx, tc.simulation)

Expand Down
122 changes: 122 additions & 0 deletions x/tax2gas/keeper/ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package keeper

import (
"fmt"

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
types "github.com/classic-terra/core/v3/x/tax2gas/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
ante "github.com/cosmos/cosmos-sdk/x/auth/ante"
)

// this keeper has been taken from the wasmd package

// Tax2GasDecorator ante decorator to limit gas in simulation calls
type Tax2GasDecorator struct {
gasLimit *sdk.Gas
}

// NewTax2GasDecorator constructor accepts nil value to fallback to block gas limit.
func NewTax2GasDecorator(gasLimit *sdk.Gas) *Tax2GasDecorator {
if gasLimit != nil && *gasLimit == 0 {
panic("gas limit must not be zero")
}

return &Tax2GasDecorator{gasLimit: gasLimit}
}

// AnteHandle that limits the maximum gas available in simulations only.
// A custom max value can be configured and will be applied when set. The value should not
// exceed the max block gas limit.
// Different values on nodes are not consensus breaking as they affect only
// simulations but may have effect on client user experience.
//
// When no custom value is set then the max block gas is used as default limit.
func (d Tax2GasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
gasTx, ok := tx.(ante.GasTx)
if !ok {
// Set a gas meter with limit 0 as to prevent an infinite gas meter attack
// during runTx.
newCtx = SetGasMeter(simulate, ctx, 0)
return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx")
}

ctx.Logger().Debug("Tax2GasDecorator.AnteHandle", "simulate", simulate, "gaswanted", gasTx.GetGas())

newCtx = ctx // default to old context for defer handling

// gas := gasTx.GetGas()
// newCtx = SetGasMeter(simulate, ctx, gas)

// Decorator will catch an OutOfGasPanic caused in the next antehandler
// AnteHandlers must have their own defer/recover in order for the BaseApp
// to know how much gas was used! This is because the GasMeter is created in
// the AnteHandler, but if it panics the context won't be set properly in
// runTx's recover call.
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
case sdk.ErrorOutOfGas:
log := fmt.Sprintf(
"out of gas in location: %v; gasWanted: %d, gasUsed: %d",
rType.Descriptor, gasTx.GetGas(), newCtx.GasMeter().GasConsumed())

err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log)
default:
panic(r)
}
}
}()

if !simulate {
// Wasm code is not executed in checkTX so that we don't need to limit it further.
// Tendermint rejects the TX afterwards when the tx.gas > max block gas.
// On deliverTX we rely on the tendermint/sdk mechanics that ensure
// tx has gas set and gas < max block gas
if ctx.BlockHeight() == 0 {
return next(ctx.WithGasMeter(types.NewTax2GasMeter(0, true)), tx, simulate)
}

return next(ctx.WithGasMeter(types.NewTax2GasMeter(gasTx.GetGas(), false)), tx, simulate)
}

// apply custom node gas limit
if d.gasLimit != nil {
return next(ctx.WithGasMeter(types.NewTax2GasMeter(*d.gasLimit, false)), tx, simulate)
}

// default to max block gas when set, to be on the safe side
if maxGas := ctx.ConsensusParams().GetBlock().MaxGas; maxGas > 0 {
return next(ctx.WithGasMeter(types.NewTax2GasMeter(sdk.Gas(maxGas), false)), tx, simulate)
}

// if no limit is set anywhere, we make it an infinite gas meter, like in the upstream modules
return next(ctx.WithGasMeter(types.NewTax2GasMeter(0, true)), tx, simulate)
}

// GasRegisterDecorator ante decorator to store gas register in the context
type GasRegisterDecorator struct {
gasRegister wasmtypes.GasRegister
}

// NewGasRegisterDecorator constructor.
func NewGasRegisterDecorator(gr wasmtypes.GasRegister) *GasRegisterDecorator {
return &GasRegisterDecorator{gasRegister: gr}
}

// AnteHandle adds the gas register to the context.
func (g GasRegisterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
return next(wasmtypes.WithGasRegister(ctx, g.gasRegister), tx, simulate)
}

// SetGasMeter returns a new context with a gas meter set from a given context.
func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) sdk.Context {
// In various cases such as simulation and during the genesis block, we do not
// meter any gas utilization.
if simulate || ctx.BlockHeight() == 0 {
return ctx.WithGasMeter(types.NewTax2GasMeter(0, true))
}

return ctx.WithGasMeter(types.NewTax2GasMeter(gasLimit, false))
}
Loading
Loading