diff --git a/app/ante.go b/app/ante.go index 1f3e85070f..1ae3daafe4 100644 --- a/app/ante.go +++ b/app/ante.go @@ -86,6 +86,7 @@ func NewAnteHandlerAndDepGenerator(options HandlerOptions) (sdk.AnteHandler, sdk sdk.DefaultWrappedAnteDecorator(wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit)), // after setup context to enforce limits early sdk.DefaultWrappedAnteDecorator(ante.NewRejectExtensionOptionsDecorator()), oracle.NewSpammingPreventionDecorator(*options.OracleKeeper), + oracle.NewOracleVoteAloneDecorator(), sdk.DefaultWrappedAnteDecorator(ante.NewValidateBasicDecorator()), sdk.DefaultWrappedAnteDecorator(ante.NewTxTimeoutHeightDecorator()), sdk.DefaultWrappedAnteDecorator(ante.NewValidateMemoDecorator(options.AccountKeeper)), diff --git a/app/antedecorators/gas_test.go b/app/antedecorators/gas_test.go index 9e3b778bb1..d74c25bac1 100644 --- a/app/antedecorators/gas_test.go +++ b/app/antedecorators/gas_test.go @@ -13,30 +13,18 @@ import ( "github.com/tendermint/tendermint/proto/tendermint/types" ) -type TestTx struct { - msgs []sdk.Msg -} - -func (t TestTx) GetMsgs() []sdk.Msg { - return t.msgs -} - -func (t TestTx) ValidateBasic() error { - return nil -} - func TestMultiplierGasSetter(t *testing.T) { - app := app.Setup(false) + testApp := app.Setup(false) contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") require.NoError(t, err) - ctx := app.NewContext(false, types.Header{}).WithBlockHeight(2) + ctx := testApp.NewContext(false, types.Header{}).WithBlockHeight(2) testMsg := wasmtypes.MsgExecuteContract{ Contract: "sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw", Msg: []byte("{}"), } - testTx := TestTx{msgs: []sdk.Msg{&testMsg}} + testTx := app.NewTestTx([]sdk.Msg{&testMsg}) // discounted mapping - app.AccessControlKeeper.SetWasmDependencyMapping(ctx, accesscontrol.WasmDependencyMapping{ + testApp.AccessControlKeeper.SetWasmDependencyMapping(ctx, accesscontrol.WasmDependencyMapping{ Enabled: true, ContractAddress: contractAddr.String(), AccessOps: []accesscontrol.AccessOperationWithSelector{ @@ -52,12 +40,12 @@ func TestMultiplierGasSetter(t *testing.T) { }, }, }) - gasMeterSetter := antedecorators.GetGasMeterSetter(app.AccessControlKeeper) + gasMeterSetter := antedecorators.GetGasMeterSetter(testApp.AccessControlKeeper) ctxWithGasMeter := gasMeterSetter(false, ctx, 1000, testTx) ctxWithGasMeter.GasMeter().ConsumeGas(2, "") require.Equal(t, uint64(1), ctxWithGasMeter.GasMeter().GasConsumed()) // not discounted mapping - app.AccessControlKeeper.SetWasmDependencyMapping(ctx, accesscontrol.WasmDependencyMapping{ + testApp.AccessControlKeeper.SetWasmDependencyMapping(ctx, accesscontrol.WasmDependencyMapping{ Enabled: true, ContractAddress: contractAddr.String(), AccessOps: []accesscontrol.AccessOperationWithSelector{ diff --git a/app/app.go b/app/app.go index 0eab082b94..71a79864bb 100644 --- a/app/app.go +++ b/app/app.go @@ -1175,6 +1175,60 @@ func (app *App) ProcessTxs( return txResults, ctx } +func (app *App) PartitionOracleVoteTxs(ctx sdk.Context, txs [][]byte) (oracleVoteTxs, otherTxs [][]byte) { + for _, tx := range txs { + decodedTx, err := app.txDecoder(tx) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("Error decoding tx for partitioning: %v", err)) + // if theres an issue decoding, add it to `otherTxs` for normal processing and continue + otherTxs = append(otherTxs, tx) + continue + } + oracleVote := false + // if theres an oracle vote msg, we want to add to oracleVoteTxs + msgLoop: + for _, msg := range decodedTx.GetMsgs() { + switch msg.(type) { + case *oracletypes.MsgAggregateExchangeRateVote: + oracleVote = true + default: + oracleVote = false + break msgLoop + } + } + if oracleVote { + oracleVoteTxs = append(oracleVoteTxs, tx) + } else { + otherTxs = append(otherTxs, tx) + } + + } + return oracleVoteTxs, otherTxs +} + +func (app *App) BuildDependenciesAndRunTxs(ctx sdk.Context, txs [][]byte) ([]*abci.ExecTxResult, sdk.Context) { + var txResults []*abci.ExecTxResult + + dependencyDag, err := app.AccessControlKeeper.BuildDependencyDag(ctx, app.txDecoder, app.GetAnteDepGenerator(), txs) + + switch err { + case nil: + // Start with a fresh state for the MemCache + ctx = ctx.WithContextMemCache(sdk.NewContextMemCache()) + txResults, ctx = app.ProcessTxs(ctx, txs, dependencyDag, app.ProcessBlockConcurrent) + case acltypes.ErrGovMsgInBlock: + ctx.Logger().Info(fmt.Sprintf("Gov msg found while building DAG, processing synchronously: %s", err)) + txResults = app.ProcessBlockSynchronous(ctx, txs) + metrics.IncrDagBuildErrorCounter(metrics.GovMsgInBlock) + default: + ctx.Logger().Error(fmt.Sprintf("Error while building DAG, processing synchronously: %s", err)) + txResults = app.ProcessBlockSynchronous(ctx, txs) + metrics.IncrDagBuildErrorCounter(metrics.FailedToBuild) + } + + return txResults, ctx +} + func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequest, lastCommit abci.CommitInfo) ([]abci.Event, []*abci.ExecTxResult, abci.ResponseEndBlock, error) { goCtx := app.decorateContextWithDexMemState(ctx.Context()) ctx = ctx.WithContext(goCtx) @@ -1205,27 +1259,20 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ beginBlockResp := app.BeginBlock(ctx, beginBlockReq) events = append(events, beginBlockResp.Events...) - midBlockEvents := app.MidBlock(ctx, req.GetHeight()) - events = append(events, midBlockEvents...) + var txResults []*abci.ExecTxResult - dependencyDag, err := app.AccessControlKeeper.BuildDependencyDag(ctx, app.txDecoder, app.GetAnteDepGenerator(), txs) + oracleTxs, txs := app.PartitionOracleVoteTxs(ctx, txs) - var txResults []*abci.ExecTxResult + // run the oracle txs + oracleResults, ctx := app.BuildDependenciesAndRunTxs(ctx, oracleTxs) + txResults = append(txResults, oracleResults...) - switch err { - case nil: - // Start with a fresh state for the MemCache - ctx = ctx.WithContextMemCache(sdk.NewContextMemCache()) - txResults, ctx = app.ProcessTxs(ctx, txs, dependencyDag, app.ProcessBlockConcurrent) - case acltypes.ErrGovMsgInBlock: - ctx.Logger().Info(fmt.Sprintf("Gov msg found while building DAG, processing synchronously: %s", err)) - txResults = app.ProcessBlockSynchronous(ctx, txs) - metrics.IncrDagBuildErrorCounter(metrics.GovMsgInBlock) - default: - ctx.Logger().Error(fmt.Sprintf("Error while building DAG, processing synchronously: %s", err)) - txResults = app.ProcessBlockSynchronous(ctx, txs) - metrics.IncrDagBuildErrorCounter(metrics.FailedToBuild) - } + midBlockEvents := app.MidBlock(ctx, req.GetHeight()) + events = append(events, midBlockEvents...) + + // run other txs + otherResults, ctx := app.BuildDependenciesAndRunTxs(ctx, txs) + txResults = append(txResults, otherResults...) // Finalize all Bank Module Transfers here so that events are included lazyWriteEvents := app.BankKeeper.WriteDeferredOperations(ctx) diff --git a/app/app_test.go b/app/app_test.go index 63467cac92..0501aab8fe 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -9,8 +9,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/k0kubun/pp/v3" "github.com/sei-protocol/sei-chain/app" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" ) @@ -155,3 +157,113 @@ func TestProcessTxsClearCacheOnFail(t *testing.T) { require.Equal(t, 0, len(testWrapper.Ctx.ContextMemCache().GetDeferredWithdrawals().GetSortedKeys())) require.Equal(t, 0, len(testWrapper.Ctx.ContextMemCache().GetDeferredSends().GetSortedKeys())) } + +func TestPartitionOracleTxs(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + + testWrapper := app.NewTestWrapper(t, tm, valPub) + + account := sdk.AccAddress(valPub.Address()).String() + validator := sdk.ValAddress(valPub.Address()).String() + + oracleMsg := &oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1.2uatom", + Feeder: account, + Validator: validator, + } + + otherMsg := &stakingtypes.MsgDelegate{ + DelegatorAddress: account, + ValidatorAddress: validator, + Amount: sdk.NewCoin("usei", sdk.NewInt(1)), + } + + txEncoder := app.MakeEncodingConfig().TxConfig.TxEncoder() + oracleTxBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + otherTxBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + mixedTxBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + + err := oracleTxBuilder.SetMsgs(oracleMsg) + require.NoError(t, err) + oracleTx, err := txEncoder(oracleTxBuilder.GetTx()) + require.NoError(t, err) + + err = otherTxBuilder.SetMsgs(otherMsg) + require.NoError(t, err) + otherTx, err := txEncoder(otherTxBuilder.GetTx()) + require.NoError(t, err) + + // this should be treated as non-oracle vote + err = mixedTxBuilder.SetMsgs([]sdk.Msg{oracleMsg, otherMsg}...) + require.NoError(t, err) + mixedTx, err := txEncoder(mixedTxBuilder.GetTx()) + require.NoError(t, err) + + txs := [][]byte{ + oracleTx, + otherTx, + mixedTx, + } + + oracleTxs, otherTxs := testWrapper.App.PartitionOracleVoteTxs(testWrapper.Ctx, txs) + require.Equal(t, oracleTxs, [][]byte{oracleTx}) + require.Equal(t, otherTxs, [][]byte{otherTx, mixedTx}) +} + +func TestProcessOracleAndOtherTxsSuccess(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + + testWrapper := app.NewTestWrapper(t, tm, valPub) + + account := sdk.AccAddress(valPub.Address()).String() + validator := sdk.ValAddress(valPub.Address()).String() + + oracleMsg := &oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1.2uatom", + Feeder: account, + Validator: validator, + } + + otherMsg := &stakingtypes.MsgDelegate{ + DelegatorAddress: account, + ValidatorAddress: validator, + Amount: sdk.NewCoin("usei", sdk.NewInt(1)), + } + + oracleTxBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + otherTxBuilder := app.MakeEncodingConfig().TxConfig.NewTxBuilder() + txEncoder := app.MakeEncodingConfig().TxConfig.TxEncoder() + + err := oracleTxBuilder.SetMsgs(oracleMsg) + require.NoError(t, err) + oracleTx, err := txEncoder(oracleTxBuilder.GetTx()) + require.NoError(t, err) + + err = otherTxBuilder.SetMsgs(otherMsg) + require.NoError(t, err) + otherTx, err := txEncoder(otherTxBuilder.GetTx()) + require.NoError(t, err) + + txs := [][]byte{ + oracleTx, + otherTx, + } + + req := &abci.RequestFinalizeBlock{ + Height: 1, + } + _, txResults, _, _ := testWrapper.App.ProcessBlock( + testWrapper.Ctx.WithBlockHeight( + 1, + ).WithBlockGasMeter( + sdk.NewInfiniteGasMeter(), + ), + txs, + req, + req.DecidedLastCommit, + ) + + require.Equal(t, 2, len(txResults)) +} diff --git a/app/test_helpers.go b/app/test_helpers.go index 406c9744fc..c52c24291c 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -25,6 +25,22 @@ import ( const TestContract = "TEST" +type TestTx struct { + msgs []sdk.Msg +} + +func NewTestTx(msgs []sdk.Msg) TestTx { + return TestTx{msgs: msgs} +} + +func (t TestTx) GetMsgs() []sdk.Msg { + return t.msgs +} + +func (t TestTx) ValidateBasic() error { + return nil +} + type TestWrapper struct { suite.Suite diff --git a/x/oracle/abci.go b/x/oracle/abci.go index e80d7254dd..a97a61fbb4 100755 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -13,12 +13,6 @@ import ( func MidBlocker(ctx sdk.Context, k keeper.Keeper) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyMidBlocker) - ctx.Logger().Info("Running Oracle MidBlocker") - // TODO: this needs to be refactored to perform relevant endblocker logic to finalize oracle prices in a later PR -} - -func EndBlocker(ctx sdk.Context, k keeper.Keeper) { - defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) params := k.GetParams(ctx) if utils.IsPeriodLastBlock(ctx, params.VotePeriod) { @@ -139,6 +133,12 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) { } } +} + +func EndBlocker(ctx sdk.Context, k keeper.Keeper) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + + params := k.GetParams(ctx) // Do slash who did miss voting over threshold and // reset miss counters of all validators at the last block of slash window if utils.IsPeriodLastBlock(ctx, params.SlashWindow) { diff --git a/x/oracle/abci_test.go b/x/oracle/abci_test.go index 85d09a38df..598b2469f1 100755 --- a/x/oracle/abci_test.go +++ b/x/oracle/abci_test.go @@ -28,6 +28,7 @@ func TestOracleThreshold(t *testing.T) { _, err := h(input.Ctx.WithBlockHeight(1), voteMsg) require.NoError(t, err) + oracle.MidBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) oracle.EndBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) _, _, err = input.OracleKeeper.GetBaseExchangeRate(input.Ctx.WithBlockHeight(1), utils.MicroAtomDenom) @@ -49,6 +50,7 @@ func TestOracleThreshold(t *testing.T) { _, err = h(input.Ctx.WithBlockHeight(1), voteMsg) require.NoError(t, err) + oracle.MidBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) oracle.EndBlocker(input.Ctx.WithBlockHeight(1), input.OracleKeeper) rate, lastUpdate, err := input.OracleKeeper.GetBaseExchangeRate(input.Ctx.WithBlockHeight(1), utils.MicroAtomDenom) @@ -69,6 +71,7 @@ func TestOracleThreshold(t *testing.T) { _, err = h(input.Ctx.WithBlockHeight(3), voteMsg) require.NoError(t, err) + oracle.MidBlocker(input.Ctx.WithBlockHeight(3), input.OracleKeeper) oracle.EndBlocker(input.Ctx.WithBlockHeight(3), input.OracleKeeper) rate, lastUpdate, err = input.OracleKeeper.GetBaseExchangeRate(input.Ctx.WithBlockHeight(3), utils.MicroAtomDenom) @@ -87,6 +90,7 @@ func TestOracleDrop(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 0) // Immediately swap halt after an illiquid oracle vote + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) rate, lastUpdate, err := input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroAtomDenom) @@ -188,12 +192,14 @@ func TestOracleTallyTiming(t *testing.T) { input.OracleKeeper.SetParams(input.Ctx, params) require.Equal(t, 0, int(input.Ctx.BlockHeight())) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) _, _, err := input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroAtomDenom) require.Error(t, err) input.Ctx = input.Ctx.WithBlockHeight(int64(params.VotePeriod - 1)) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) _, _, err = input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroAtomDenom) require.NoError(t, err) @@ -222,6 +228,7 @@ func TestInvalidVotesSlashing(t *testing.T) { // Account 3, KRW makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) require.Equal(t, uint64(0), input.OracleKeeper.GetAbstainCount(input.Ctx, keeper.ValAddrs[1])) require.Equal(t, i+1, input.OracleKeeper.GetMissCount(input.Ctx, keeper.ValAddrs[1])) @@ -241,6 +248,7 @@ func TestInvalidVotesSlashing(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) validator = input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[1]) require.Equal(t, sdk.OneDec().Sub(slashFraction).MulInt(stakingAmt).TruncateInt(), validator.GetBondedTokens()) @@ -260,6 +268,7 @@ func TestWhitelistSlashing(t *testing.T) { // Account 3, KRW makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) require.Equal(t, uint64(0), input.OracleKeeper.GetMissCount(input.Ctx, keeper.ValAddrs[0])) require.Equal(t, i+1, input.OracleKeeper.GetAbstainCount(input.Ctx, keeper.ValAddrs[0])) @@ -276,6 +285,7 @@ func TestWhitelistSlashing(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) validator = input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[0]) // because of implicit abstaining there shouldnt be slashing here @@ -296,6 +306,7 @@ func TestNotPassedBallotSlashing(t *testing.T) { // Account 1, KRW makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 0) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) require.Equal(t, uint64(0), input.OracleKeeper.GetMissCount(input.Ctx, keeper.ValAddrs[0])) require.Equal(t, uint64(0), input.OracleKeeper.GetMissCount(input.Ctx, keeper.ValAddrs[1])) @@ -324,6 +335,7 @@ func TestNotPassedBallotSlashingInvalidVotes(t *testing.T) { // Account 3 makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate.Add(sdk.NewDec(100000000000000))}}, 2) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) // 4-7 should be counted as abstained due to not voting @@ -370,6 +382,7 @@ func TestInvalidVoteOnAssetUnderThresholdMisses(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 5) makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 6) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) endBlockerHeight := input.Ctx.BlockHeight() @@ -409,6 +422,7 @@ func TestInvalidVoteOnAssetUnderThresholdMisses(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: anotherRandomExchangeRate}}, 5) makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: anotherRandomExchangeRate}}, 6) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) newEndBlockerHeight := input.Ctx.BlockHeight() @@ -463,12 +477,14 @@ func TestAbstainSlashing(t *testing.T) { // Account 3, KRW makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) require.Equal(t, uint64(i+1%limit), input.OracleKeeper.GetMissCount(input.Ctx, keeper.ValAddrs[1])) require.Equal(t, uint64(0), input.OracleKeeper.GetAbstainCount(input.Ctx, keeper.ValAddrs[1])) } input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) validator := input.StakingKeeper.Validator(input.Ctx, keeper.ValAddrs[1]) // validator got slashed and jailed @@ -490,6 +506,7 @@ func TestVoteTargets(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 1) makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) // no missing current @@ -512,6 +529,7 @@ func TestVoteTargets(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 1) makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) require.Equal(t, uint64(0), input.OracleKeeper.GetMissCount(input.Ctx, keeper.ValAddrs[0])) @@ -532,6 +550,7 @@ func TestVoteTargets(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 1) makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) require.Equal(t, uint64(0), input.OracleKeeper.GetMissCount(input.Ctx, keeper.ValAddrs[0])) @@ -549,6 +568,7 @@ func TestAbstainWithSmallStakingPower(t *testing.T) { input.OracleKeeper.SetVoteTarget(input.Ctx, utils.MicroAtomDenom) makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: sdk.ZeroDec()}}, 0) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) _, _, err := input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroAtomDenom) require.Error(t, err) @@ -567,6 +587,7 @@ func TestOraclePriceSnapshot(t *testing.T) { makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 1) makeAggregateVote(t, input, h, 0, sdk.DecCoins{{Denom: utils.MicroAtomDenom, Amount: randomExchangeRate}}, 2) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) rate, lastUpdate, err := input.OracleKeeper.GetBaseExchangeRate(input.Ctx, utils.MicroAtomDenom) @@ -592,6 +613,7 @@ func TestOraclePriceSnapshot(t *testing.T) { require.Equal(t, expected, snapshot) input.Ctx = input.Ctx.WithBlockTime(time.Unix(200, 0)) + oracle.MidBlocker(input.Ctx, input.OracleKeeper) oracle.EndBlocker(input.Ctx, input.OracleKeeper) expected2 := types.PriceSnapshot{ SnapshotTimestamp: 200, diff --git a/x/oracle/ante.go b/x/oracle/ante.go index 73dbbe157b..38d128488f 100644 --- a/x/oracle/ante.go +++ b/x/oracle/ante.go @@ -121,3 +121,35 @@ func (spd SpammingPreventionDecorator) CheckOracleSpamming(ctx sdk.Context, msgs return nil } + +type VoteAloneDecorator struct{} + +func NewOracleVoteAloneDecorator() VoteAloneDecorator { + return VoteAloneDecorator{} +} + +// AnteHandle handles msg tax fee checking +func (VoteAloneDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + oracleVote := false + otherMsg := false + for _, msg := range tx.GetMsgs() { + switch msg.(type) { + case *types.MsgAggregateExchangeRateVote: + oracleVote = true + + default: + otherMsg = true + } + } + + if oracleVote && otherMsg { + return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "oracle votes cannot be in the same tx as other messages") + } + + return next(ctx, tx, simulate) +} + +func (VoteAloneDecorator) AnteDeps(txDeps []sdkacltypes.AccessOperation, tx sdk.Tx, next sdk.AnteDepGenerator) (newTxDeps []sdkacltypes.AccessOperation, err error) { + // requires no dependencies + return next(txDeps, tx) +} diff --git a/x/oracle/ante_test.go b/x/oracle/ante_test.go new file mode 100644 index 0000000000..ebc6480ba0 --- /dev/null +++ b/x/oracle/ante_test.go @@ -0,0 +1,43 @@ +package oracle_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/x/oracle" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + "github.com/stretchr/testify/require" +) + +func TestOracleVoteAloneAnteHandler(t *testing.T) { + + testOracleMsg := oracletypes.MsgAggregateExchangeRateVote{} + testNonOracleMsg := banktypes.MsgSend{} + testNonOracleMsg2 := banktypes.MsgSend{} + + decorator := oracle.NewOracleVoteAloneDecorator() + anteHandler, _ := sdk.ChainAnteDecorators(decorator) + + testCases := []struct { + name string + expErr bool + tx sdk.Tx + }{ + {"only oracle vote", false, app.NewTestTx([]sdk.Msg{&testOracleMsg})}, + {"only non-oracle msgs", false, app.NewTestTx([]sdk.Msg{&testNonOracleMsg, &testNonOracleMsg2})}, + {"mixed messages", true, app.NewTestTx([]sdk.Msg{&testNonOracleMsg, &testOracleMsg, &testNonOracleMsg2})}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := anteHandler(sdk.Context{}, tc.tx, false) + if tc.expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +}