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

WIP: Modular AnteHandler #4583

Closed
wants to merge 12 commits into from
4 changes: 4 additions & 0 deletions server/mock/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func (tx kvstoreTx) GetMsgs() []sdk.Msg {
return []sdk.Msg{tx}
}

func (tx kvstoreTx) Gas() uint64 {
return 0
}

func (tx kvstoreTx) GetMemo() string {
return ""
}
Expand Down
2 changes: 1 addition & 1 deletion simapp/sim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ func testAndRunTxs(app *SimApp) []simulation.WeightedOperation {
})
return v
}(nil),
authsim.SimulateDeductFee(app.accountKeeper, app.supplyKeeper),
distrsim.SimulateDeductFee(app.accountKeeper, app.supplyKeeper),
},
{
func(_ *rand.Rand) int {
Expand Down
51 changes: 51 additions & 0 deletions types/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ needlessly defining many placeholder functions
package module

import (
"fmt"
"encoding/json"

"github.com/gorilla/mux"
Expand Down Expand Up @@ -139,6 +140,7 @@ type AppModule interface {
QuerierRoute() string
NewQuerierHandler() sdk.Querier

AnteHandle(sdk.Context, sdk.Tx, bool) (sdk.Context, sdk.Result, bool)
BeginBlock(sdk.Context, abci.RequestBeginBlock)
EndBlock(sdk.Context, abci.RequestEndBlock) []abci.ValidatorUpdate
}
Expand Down Expand Up @@ -171,6 +173,11 @@ func (GenesisOnlyAppModule) QuerierRoute() string { return "" }
// module querier
func (gam GenesisOnlyAppModule) NewQuerierHandler() sdk.Querier { return nil }

// module ante-handle
func (gam GenesisOnlyAppModule) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
return ctx, sdk.Result{}, false
}

// module begin-block
func (gam GenesisOnlyAppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {}

Expand All @@ -186,6 +193,7 @@ type Manager struct {
Modules map[string]AppModule
OrderInitGenesis []string
OrderExportGenesis []string
OrderAnteHandlers []string
OrderBeginBlockers []string
OrderEndBlockers []string
}
Expand All @@ -204,6 +212,7 @@ func NewManager(modules ...AppModule) *Manager {
Modules: moduleMap,
OrderInitGenesis: modulesStr,
OrderExportGenesis: modulesStr,
OrderAnteHandlers: modulesStr,
OrderBeginBlockers: modulesStr,
OrderEndBlockers: modulesStr,
}
Expand All @@ -219,6 +228,11 @@ func (m *Manager) SetOrderExportGenesis(moduleNames ...string) {
m.OrderExportGenesis = moduleNames
}

// set the order of module antehandlers
func (m *Manager) SetOrderAnteHandler(moduleNames ...string) {
m.OrderAnteHandlers = moduleNames
}

// set the order of set begin-blocker calls
func (m *Manager) SetOrderBeginBlockers(moduleNames ...string) {
m.OrderBeginBlockers = moduleNames
Expand Down Expand Up @@ -280,6 +294,43 @@ func (m *Manager) ExportGenesis(ctx sdk.Context) map[string]json.RawMessage {
return genesisData
}

// perform antehandle functionality for modules
func (m *Manager) AnteHandler(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
newCtx = sdk.SetGasMeter(simulate, ctx, tx.Gas())

// 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, tx.Gas(), newCtx.GasMeter().GasConsumed(),
)
res = sdk.ErrOutOfGas(log).Result()

res.GasWanted = tx.Gas()
res.GasUsed = newCtx.GasMeter().GasConsumed()
abort = true
default:
panic(r)
}
}
}()

// must somehow consume gas cost for validating tx (tx size cost/byte)
for _, moduleName := range m.OrderAnteHandlers {
newCtx, res, abort = m.Modules[moduleName].AnteHandle(newCtx, tx, simulate)
if abort {
return newCtx, res, abort
}
}
return newCtx, res, abort
}

// BeginBlock performs begin block functionality for all modules. It creates a
// child context with an event manager to aggregate events emitted from all
// modules.
Expand Down
34 changes: 34 additions & 0 deletions types/module/test_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package module

import (
"fmt"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

// run the tx through the anteHandler and ensure its valid
func CheckValidTx(t *testing.T, m Manager, ctx sdk.Context, tx sdk.Tx, simulate bool) {
_, result, abort := m.AnteHandler(ctx, tx, simulate)
require.Equal(t, "", result.Log)
require.False(t, abort)
require.Equal(t, sdk.CodeOK, result.Code)
require.True(t, result.IsOK())
}

// run the tx through the anteHandler and ensure it fails with the given code
func CheckInvalidTx(t *testing.T, m Manager, ctx sdk.Context, tx sdk.Tx, simulate bool, code sdk.CodeType) {
newCtx, result, abort := m.AnteHandler(ctx, tx, simulate)
require.True(t, abort)

require.Equal(t, code, result.Code, fmt.Sprintf("Expected %v, got %v", code, result))
require.Equal(t, sdk.CodespaceRoot, result.Codespace)

if code == sdk.CodeOutOfGas {
// GasWanted set correctly
require.Equal(t, tx.Gas(), result.GasWanted, "Gas wanted not set correctly")
require.True(t, result.GasUsed > result.GasWanted, "GasUsed not greated than GasWanted")
// Check that context is set correctly
require.Equal(t, result.GasUsed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly")
}
}
11 changes: 11 additions & 0 deletions types/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,14 @@ type (
func NewInfiniteGasMeter() GasMeter {
return types.NewInfiniteGasMeter()
}

// SetGasMeter returns a new context with a gas meter set from a given context.
func SetGasMeter(simulate bool, ctx Context, gasLimit uint64) 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(NewInfiniteGasMeter())
}

return ctx.WithGasMeter(NewGasMeter(gasLimit))
}
5 changes: 5 additions & 0 deletions types/tx_msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ type Tx interface {
// ValidateBasic does a simple and lightweight validation check that doesn't
// require access to any other information.
ValidateBasic() Error

// Gas returns the amount of Gas this tx is allowed to consume
Gas() uint64

FeeCoins() []Coin
}

//__________________________________________________________
Expand Down
1 change: 0 additions & 1 deletion x/auth/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
const (
ModuleName = types.ModuleName
StoreKey = types.StoreKey
FeeCollectorName = types.FeeCollectorName
QuerierRoute = types.QuerierRoute
DefaultParamspace = types.DefaultParamspace
DefaultMaxMemoCharacters = types.DefaultMaxMemoCharacters
Expand Down
Loading