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

[Feature] lower oracle feeder cost #463

Merged
merged 14 commits into from
Mar 18, 2021
7 changes: 6 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,12 @@ func NewTerraApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest
// initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetAnteHandler(ante.NewAnteHandler(app.accountKeeper, app.supplyKeeper, app.treasuryKeeper, auth.DefaultSigVerificationGasConsumer))
app.SetAnteHandler(ante.NewAnteHandler(
app.accountKeeper,
app.supplyKeeper,
app.oracleKeeper,
app.treasuryKeeper,
auth.DefaultSigVerificationGasConsumer))
app.SetEndBlocker(app.EndBlocker)

if loadLatest {
Expand Down
13 changes: 9 additions & 4 deletions x/auth/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import (
// NewAnteHandler returns an AnteHandler that checks and increments sequence
// numbers, checks signatures & account numbers, and deducts fees from the first
// signer.
func NewAnteHandler(ak keeper.AccountKeeper, supplyKeeper types.SupplyKeeper, treasuryKeeper TreasuryKeeper, sigGasConsumer cosmosante.SignatureVerificationGasConsumer) sdk.AnteHandler {
func NewAnteHandler(
ak keeper.AccountKeeper,
supplyKeeper types.SupplyKeeper,
oracleKeeper OracleKeeper,
treasuryKeeper TreasuryKeeper,
sigGasConsumer cosmosante.SignatureVerificationGasConsumer) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
cosmosante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
NewSpammingPreventionDecorator(),
NewTaxFeeDecorator(treasuryKeeper), // mempool gas fee validation & record tax proceeds
cosmosante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
NewSpammingPreventionDecorator(oracleKeeper), // spamming prevention
NewTaxFeeDecorator(treasuryKeeper), // mempool gas fee validation & record tax proceeds
cosmosante.NewValidateBasicDecorator(),
cosmosante.NewValidateMemoDecorator(ak),
cosmosante.NewConsumeGasForTxSizeDecorator(ak),
Expand Down
7 changes: 6 additions & 1 deletion x/auth/ante/expected_keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// Treasurykeeper for tax charging & recording
// TreasuryKeeper for tax charging & recording
type TreasuryKeeper interface {
RecordEpochTaxProceeds(ctx sdk.Context, delta sdk.Coins)
GetTaxRate(ctx sdk.Context) (taxRate sdk.Dec)
GetTaxCap(ctx sdk.Context, denom string) (taxCap sdk.Int)
}

// OracleKeeper for feeder validation
type OracleKeeper interface {
ValidateFeeder(ctx sdk.Context, feederAddr sdk.AccAddress, validatorAddr sdk.ValAddress, checkBonded bool) error
}
66 changes: 63 additions & 3 deletions x/auth/ante/spamming_prevention.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package ante

import (
"sync"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/spf13/viper"

oracleexported "github.com/terra-project/core/x/oracle/exported"
)

// FlagTxGasHardLimit defines the hard cap to prevent tx spamming attack
Expand All @@ -12,11 +16,20 @@ const FlagTxGasHardLimit = "tx-gas-hard-limit"
// SpammingPreventionDecorator will check if the transaction's gas is smaller than
// configured hard cap
type SpammingPreventionDecorator struct {
oracleKeeper OracleKeeper
oraclePrevoteMap map[string]int64
oracleVoteMap map[string]int64
mu *sync.Mutex
}

// NewSpammingPreventionDecorator returns new spamming prevention decorator instance
func NewSpammingPreventionDecorator() SpammingPreventionDecorator {
return SpammingPreventionDecorator{}
func NewSpammingPreventionDecorator(oracleKeeper OracleKeeper) SpammingPreventionDecorator {
return SpammingPreventionDecorator{
oracleKeeper: oracleKeeper,
oraclePrevoteMap: make(map[string]int64),
oracleVoteMap: make(map[string]int64),
mu: &sync.Mutex{},
}
}

// AnteHandle handles msg tax fee checking
Expand All @@ -30,9 +43,56 @@ func (spd SpammingPreventionDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, si
gas := feeTx.GetGas()
gasHardLimit := viper.GetUint64(FlagTxGasHardLimit)
if gas > gasHardLimit {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrOutOfGas, "Tx cannot spend more than %d gas", gasHardLimit)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "Tx cannot spend more than %d gas", gasHardLimit)
}

err := spd.CheckOracleSpamming(ctx, feeTx.GetMsgs())
if err != nil {
return ctx, err
}
}

return next(ctx, tx, simulate)
}

// CheckOracleSpamming check whether the msgs are spamming purpose or not
func (spd SpammingPreventionDecorator) CheckOracleSpamming(ctx sdk.Context, msgs []sdk.Msg) error {
spd.mu.Lock()
defer spd.mu.Unlock()

curHeight := ctx.BlockHeight()
for _, msg := range msgs {
switch msg := msg.(type) {
case oracleexported.MsgAggregateExchangeRatePrevote:
err := spd.oracleKeeper.ValidateFeeder(ctx, msg.Feeder, msg.Validator, true)
if err != nil {
return err
}

valAddrStr := msg.Validator.String()
if lastSubmittedHeight, ok := spd.oraclePrevoteMap[valAddrStr]; ok && lastSubmittedHeight == curHeight {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "the validator has already been submitted prevote at the current height")
}

spd.oraclePrevoteMap[valAddrStr] = curHeight
continue
case oracleexported.MsgAggregateExchangeRateVote:
err := spd.oracleKeeper.ValidateFeeder(ctx, msg.Feeder, msg.Validator, true)
if err != nil {
return err
}

valAddrStr := msg.Validator.String()
if lastSubmittedHeight, ok := spd.oracleVoteMap[valAddrStr]; ok && lastSubmittedHeight == curHeight {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "the validator has already been submitted prevote at the current height")
}

spd.oracleVoteMap[valAddrStr] = curHeight
continue
default:
return nil
}
}

return nil
}
80 changes: 80 additions & 0 deletions x/auth/ante/spamming_prevention_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package ante_test

import (
"io/ioutil"
"os"
"testing"

"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/terra-project/core/x/auth/ante"
"github.com/terra-project/core/x/oracle"
)

func TestOracleSpamming(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasmtest")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
viper.Set(flags.FlagHome, tempDir)

_, ctx := createTestApp()
_, _, addr1 := types.KeyTestPubAddr()
_, _, addr2 := types.KeyTestPubAddr()

spd := ante.NewSpammingPreventionDecorator(dummyOracleKeeper{
feeders: map[string]string{
sdk.ValAddress(addr1).String(): addr1.String(),
sdk.ValAddress(addr2).String(): addr2.String(),
},
})

// normal so ok
ctx = ctx.WithBlockHeight(100)
require.NoError(t, spd.CheckOracleSpamming(ctx, []sdk.Msg{
oracle.NewMsgAggregateExchangeRatePrevote(oracle.AggregateVoteHash{}, addr1, sdk.ValAddress(addr1)),
oracle.NewMsgAggregateExchangeRateVote("", "", addr1, sdk.ValAddress(addr1)),
}))

// do it again is blocked
require.Error(t, spd.CheckOracleSpamming(ctx, []sdk.Msg{
oracle.NewMsgAggregateExchangeRatePrevote(oracle.AggregateVoteHash{}, addr1, sdk.ValAddress(addr1)),
oracle.NewMsgAggregateExchangeRateVote("", "", addr1, sdk.ValAddress(addr1)),
}))

// next block; can put oracle again
ctx = ctx.WithBlockHeight(101)
require.NoError(t, spd.CheckOracleSpamming(ctx, []sdk.Msg{
oracle.NewMsgAggregateExchangeRatePrevote(oracle.AggregateVoteHash{}, addr1, sdk.ValAddress(addr1)),
oracle.NewMsgAggregateExchangeRateVote("", "", addr1, sdk.ValAddress(addr1)),
}))

// catch wrong feeder
ctx = ctx.WithBlockHeight(102)
require.Error(t, spd.CheckOracleSpamming(ctx, []sdk.Msg{
oracle.NewMsgAggregateExchangeRatePrevote(oracle.AggregateVoteHash{}, addr2, sdk.ValAddress(addr1)),
oracle.NewMsgAggregateExchangeRateVote("", "", addr1, sdk.ValAddress(addr1)),
}))

// catch wrong feeder
ctx = ctx.WithBlockHeight(103)
require.Error(t, spd.CheckOracleSpamming(ctx, []sdk.Msg{
oracle.NewMsgAggregateExchangeRatePrevote(oracle.AggregateVoteHash{}, addr1, sdk.ValAddress(addr1)),
oracle.NewMsgAggregateExchangeRateVote("", "", addr2, sdk.ValAddress(addr1)),
}))
}

type dummyOracleKeeper struct {
feeders map[string]string
}

func (ok dummyOracleKeeper) ValidateFeeder(ctx sdk.Context, feederAddr sdk.AccAddress, validatorAddr sdk.ValAddress, checkBonded bool) error {
if val, ok := ok.feeders[validatorAddr.String()]; ok && val == feederAddr.String() {
return nil
}

return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "cannot ensure feeder right")
}
18 changes: 17 additions & 1 deletion x/auth/ante/tax.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
core "github.com/terra-project/core/types"
marketexported "github.com/terra-project/core/x/market/exported"
msgauthexported "github.com/terra-project/core/x/msgauth/exported"
oracleexported "github.com/terra-project/core/x/oracle/exported"
wasmexported "github.com/terra-project/core/x/wasm/exported"
)

Expand Down Expand Up @@ -54,7 +55,7 @@ func (tfd TaxFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool,
taxes := FilterMsgAndComputeTax(ctx, tfd.treasuryKeeper, feeTx.GetMsgs())

// Mempool fee validation
if ctx.IsCheckTx() {
if ctx.IsCheckTx() && !isOracleTx(ctx, feeTx.GetMsgs()) {
if err := EnsureSufficientMempoolFees(ctx, gas, feeCoins, taxes); err != nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, err.Error())
}
Expand Down Expand Up @@ -168,3 +169,18 @@ func computeTax(ctx sdk.Context, tk TreasuryKeeper, principal sdk.Coins) sdk.Coi

return taxes
}

func isOracleTx(ctx sdk.Context, msgs []sdk.Msg) bool {
for _, msg := range msgs {
switch msg.(type) {
case oracleexported.MsgAggregateExchangeRatePrevote:
continue
case oracleexported.MsgAggregateExchangeRateVote:
continue
default:
return false
}
}

return true
}
29 changes: 29 additions & 0 deletions x/auth/ante/tax_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/terra-project/core/x/auth/ante"
"github.com/terra-project/core/x/bank"
"github.com/terra-project/core/x/msgauth"
oracleexported "github.com/terra-project/core/x/oracle/exported"
"github.com/terra-project/core/x/treasury"
"github.com/terra-project/core/x/wasm"
wasmconfig "github.com/terra-project/core/x/wasm/config"
Expand Down Expand Up @@ -302,3 +303,31 @@ func TestEnsureMempoolFeesExecAuthorized(t *testing.T) {
_, err = antehandler(ctx, tx, false)
require.Nil(t, err, "Decorator should not have errored on fee higher than local gasPrice + tax")
}

func TestEnsureNoMempoolFeesForOracleMessages(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasmtest")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
viper.Set(flags.FlagHome, tempDir)

// setup
tapp, ctx := createTestApp()

lowGasPrice := []sdk.DecCoin{{Denom: "uusd", Amount: sdk.NewDec(100)}}
ctx = ctx.WithMinGasPrices(lowGasPrice)

tk := tapp.GetTreasuryKeeper()
mtd := ante.NewTaxFeeDecorator(tk)
antehandler := sdk.ChainAnteDecorators(mtd)

// keys and addresses
priv1, _, _ := types.KeyTestPubAddr()
privs, accNums, seqs := []crypto.PrivKey{priv1}, []uint64{0}, []uint64{0}

msgs := []sdk.Msg{oracleexported.MsgAggregateExchangeRatePrevote{}, oracleexported.MsgAggregateExchangeRateVote{}}

fee := auth.NewStdFee(100000, sdk.NewCoins())
tx := types.NewTestTx(ctx, msgs, privs, accNums, seqs, fee)
_, err = antehandler(ctx, tx, false)
require.NoError(t, err)
}
9 changes: 9 additions & 0 deletions x/oracle/exported/alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// nolint:deadcode unused DONTCOVER
package exported

import "github.com/terra-project/core/x/oracle/internal/types"

type (
MsgAggregateExchangeRatePrevote = types.MsgAggregateExchangeRatePrevote
MsgAggregateExchangeRateVote = types.MsgAggregateExchangeRateVote
)
Loading