|
| 1 | +package app |
| 2 | + |
| 3 | +import ( |
| 4 | + "math" |
| 5 | + "math/big" |
| 6 | + |
| 7 | + sdk "github.com/cosmos/cosmos-sdk/types" |
| 8 | + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" |
| 9 | + cosmosante "github.com/cosmos/cosmos-sdk/x/auth/ante" |
| 10 | + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" |
| 11 | + upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" |
| 12 | + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" |
| 13 | + ethtypes "github.com/ethereum/go-ethereum/core/types" |
| 14 | + "github.com/sei-protocol/sei-chain/app/antedecorators" |
| 15 | + "github.com/sei-protocol/sei-chain/utils" |
| 16 | + evmante "github.com/sei-protocol/sei-chain/x/evm/ante" |
| 17 | + "github.com/sei-protocol/sei-chain/x/evm/derived" |
| 18 | + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" |
| 19 | + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" |
| 20 | + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" |
| 21 | + "github.com/tendermint/tendermint/libs/log" |
| 22 | +) |
| 23 | + |
| 24 | +var _ sdk.TxPrioritizer = (*SeiTxPrioritizer)(nil).GetTxPriorityHint |
| 25 | + |
| 26 | +type SeiTxPrioritizer struct { |
| 27 | + evmKeeper *evmkeeper.Keeper |
| 28 | + upgradeKeeper *upgradekeeper.Keeper |
| 29 | + paramsKeeper *paramskeeper.Keeper |
| 30 | + logger log.Logger |
| 31 | +} |
| 32 | + |
| 33 | +func NewSeiTxPrioritizer(logger log.Logger, ek *evmkeeper.Keeper, uk *upgradekeeper.Keeper, pk *paramskeeper.Keeper) *SeiTxPrioritizer { |
| 34 | + return &SeiTxPrioritizer{ |
| 35 | + logger: logger, |
| 36 | + evmKeeper: ek, |
| 37 | + upgradeKeeper: uk, |
| 38 | + paramsKeeper: pk, |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +func (s *SeiTxPrioritizer) GetTxPriorityHint(ctx sdk.Context, tx sdk.Tx) (_priorityHint int64, _err error) { |
| 43 | + defer func() { |
| 44 | + if r := recover(); r != nil { |
| 45 | + // Fall back to no-op priority if we panic for any reason. This is to avoid DoS |
| 46 | + // vectors where a malicious actor crafts a transaction that panics the |
| 47 | + // prioritizer. Since the prioritizer is used as a hint only, it's safe to fall |
| 48 | + // back to zero priority in this case and log the panic for monitoring purposes. |
| 49 | + s.logger.Error("tx prioritizer panicked. Falling back on no priority", "error", r) |
| 50 | + _priorityHint = 0 |
| 51 | + _err = nil |
| 52 | + } |
| 53 | + }() |
| 54 | + if ctx.HasPriority() { |
| 55 | + // The context already has a priority set, return it. |
| 56 | + return ctx.Priority(), nil |
| 57 | + } |
| 58 | + |
| 59 | + if ok, err := evmante.IsEVMMessage(tx); err != nil { |
| 60 | + return 0, err |
| 61 | + } else if ok { |
| 62 | + evmTx := evmtypes.GetEVMTransactionMessage(tx) |
| 63 | + if evmTx != nil { |
| 64 | + return s.getEvmTxPriority(ctx, evmTx) |
| 65 | + } |
| 66 | + // This should never happen since IsEVMMessage returned true. But we defensively |
| 67 | + // return zero priority to be safe. |
| 68 | + return 0, nil |
| 69 | + } |
| 70 | + if feeTx, ok := tx.(sdk.FeeTx); ok { |
| 71 | + return s.getCosmosTxPriority(ctx, feeTx) |
| 72 | + } |
| 73 | + return 0, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must either be EVM or Fee") |
| 74 | +} |
| 75 | + |
| 76 | +func (s *SeiTxPrioritizer) getEvmTxPriority(ctx sdk.Context, evmTx *evmtypes.MsgEVMTransaction) (int64, error) { |
| 77 | + |
| 78 | + // Unpack the transaction data first to avoid double unpacking as part of preprocessing. |
| 79 | + txData, err := evmtypes.UnpackTxData(evmTx.Data) |
| 80 | + if err != nil { |
| 81 | + return 0, err |
| 82 | + } |
| 83 | + |
| 84 | + if err := evmante.PreprocessUnpacked(ctx, evmTx, s.evmKeeper.ChainID(ctx), s.evmKeeper.EthBlockTestConfig.Enabled, txData); err != nil { |
| 85 | + return 0, err |
| 86 | + } |
| 87 | + if evmTx.Derived.IsAssociate { |
| 88 | + _, isAssociated := s.evmKeeper.GetEVMAddress( |
| 89 | + ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)), |
| 90 | + evmTx.Derived.SenderSeiAddr) |
| 91 | + if !isAssociated { |
| 92 | + // Unassociated associate transactions have the second-highest priority. |
| 93 | + // This is to ensure that associate transactions are processed before |
| 94 | + // regular transactions, but after oracle transactions. |
| 95 | + // |
| 96 | + // Note that we are not checking if sufficient funds are present here to keep the |
| 97 | + // priority calculation fast. CheckTx should fully check the transaction. |
| 98 | + return antedecorators.EVMAssociatePriority, nil |
| 99 | + } |
| 100 | + return 0, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "account already has association set") |
| 101 | + } |
| 102 | + |
| 103 | + // Check txData for sanity. |
| 104 | + feeCap := txData.GetGasFeeCap() |
| 105 | + fee := s.getEvmBaseFee(ctx) |
| 106 | + if feeCap.Cmp(fee) < 0 { |
| 107 | + return 0, sdkerrors.ErrInsufficientFee |
| 108 | + } |
| 109 | + minimumFee := s.evmKeeper.GetMinimumFeePerGas(ctx).TruncateInt().BigInt() |
| 110 | + if feeCap.Cmp(minimumFee) < 0 { |
| 111 | + return 0, sdkerrors.ErrInsufficientFee |
| 112 | + } |
| 113 | + if txData.GetGasTipCap().Sign() < 0 { |
| 114 | + return 0, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "gas fee cap cannot be negative") |
| 115 | + } |
| 116 | + // Check blob hashes for sanity. If EVM version is Cancun or later, and the |
| 117 | + // transaction contains at least one blob, we need to make sure the transaction |
| 118 | + // carries a non-zero blob fee cap. |
| 119 | + if evmTx.Derived != nil && evmTx.Derived.Version >= derived.Cancun && len(txData.GetBlobHashes()) > 0 { |
| 120 | + // For now we are simply assuming excessive blob gas is 0. In the future we might change it to be |
| 121 | + // dynamic based on prior block usage. |
| 122 | + chainConfig := evmtypes.DefaultChainConfig().EthereumConfig(s.evmKeeper.ChainID(ctx)) |
| 123 | + if txData.GetBlobFeeCap().Cmp(eip4844.CalcBlobFee(chainConfig, ðtypes.Header{Time: uint64(ctx.BlockTime().Unix())})) < 0 { //nolint:gosec |
| 124 | + return 0, sdkerrors.ErrInsufficientFee |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + gp := txData.EffectiveGasPrice(utils.Big0) |
| 129 | + priority := sdk.NewDecFromBigInt(gp).Quo(s.evmKeeper.GetPriorityNormalizer(ctx)).TruncateInt().BigInt() |
| 130 | + if priority.Cmp(big.NewInt(antedecorators.MaxPriority)) > 0 { |
| 131 | + priority = big.NewInt(antedecorators.MaxPriority) |
| 132 | + } |
| 133 | + return priority.Int64(), nil |
| 134 | +} |
| 135 | + |
| 136 | +func (s *SeiTxPrioritizer) getEvmBaseFee(ctx sdk.Context) *big.Int { |
| 137 | + const ( |
| 138 | + pacific1 = "pacific-1" |
| 139 | + historicalBlockHeight = 114945913 |
| 140 | + doneHeightName = "6.2.0" |
| 141 | + ) |
| 142 | + if ctx.ChainID() == pacific1 { |
| 143 | + height := ctx.BlockHeight() |
| 144 | + if height < historicalBlockHeight { |
| 145 | + return s.evmKeeper.GetBaseFeePerGas(ctx).TruncateInt().BigInt() |
| 146 | + } |
| 147 | + |
| 148 | + doneHeight := s.upgradeKeeper.GetDoneHeight( |
| 149 | + ctx.WithGasMeter(sdk.NewInfiniteGasMeter(1, 1)), doneHeightName) |
| 150 | + if height < doneHeight { |
| 151 | + return s.evmKeeper.GetCurrBaseFeePerGas(ctx).TruncateInt().BigInt() |
| 152 | + } |
| 153 | + } |
| 154 | + return s.evmKeeper.GetNextBaseFeePerGas(ctx).TruncateInt().BigInt() |
| 155 | +} |
| 156 | + |
| 157 | +func (s *SeiTxPrioritizer) getCosmosTxPriority(ctx sdk.Context, feeTx sdk.FeeTx) (int64, error) { |
| 158 | + if isOracleTx(feeTx) { |
| 159 | + return antedecorators.OraclePriority, nil |
| 160 | + } |
| 161 | + |
| 162 | + gas := feeTx.GetGas() |
| 163 | + if gas <= 0 { |
| 164 | + return 0, nil |
| 165 | + } |
| 166 | + var igas int64 |
| 167 | + if gas > math.MaxInt64 { |
| 168 | + igas = math.MaxInt64 |
| 169 | + } else { |
| 170 | + igas = int64(gas) //nolint:gosec |
| 171 | + } |
| 172 | + |
| 173 | + feeParams := s.paramsKeeper.GetFeesParams(ctx) |
| 174 | + allowedDenoms := feeParams.GetAllowedFeeDenoms() |
| 175 | + denoms := make([]string, 0, len(allowedDenoms)+1) |
| 176 | + denoms = append(denoms, sdk.DefaultBondDenom) |
| 177 | + denoms = append(denoms, allowedDenoms...) |
| 178 | + feeCoins := feeTx.GetFee().NonZeroAmountsOf(denoms) |
| 179 | + priority := cosmosante.GetTxPriority(feeCoins, igas) |
| 180 | + return min(antedecorators.MaxPriority, priority), nil |
| 181 | +} |
| 182 | + |
| 183 | +func isOracleTx(tx sdk.FeeTx) bool { |
| 184 | + if len(tx.GetMsgs()) == 0 { |
| 185 | + return false |
| 186 | + } |
| 187 | + for _, msg := range tx.GetMsgs() { |
| 188 | + switch msg.(type) { |
| 189 | + case *oracletypes.MsgAggregateExchangeRateVote: |
| 190 | + continue |
| 191 | + default: |
| 192 | + return false |
| 193 | + } |
| 194 | + } |
| 195 | + return true |
| 196 | +} |
0 commit comments