diff --git a/app/abci.go b/app/abci.go index 6443dda874..ce41f979b2 100644 --- a/app/abci.go +++ b/app/abci.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "time" + "github.com/cosmos/cosmos-sdk/tasks" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -116,7 +117,52 @@ func (app *App) DeliverTx(ctx sdk.Context, req abci.RequestDeliverTxV2, tx sdk.T defer span.End() // update context with trace span new context ctx = ctx.WithTraceSpanContext(spanCtx) - return app.BaseApp.DeliverTx(ctx, req, tx, checksum) + defer telemetry.MeasureSince(time.Now(), "abci", "deliver_tx") + + gInfo := sdk.GasInfo{} + resultStr := "successful" + + defer func() { + telemetry.IncrCounter(1, "tx", "count") + telemetry.IncrCounter(1, "tx", resultStr) + telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used") + telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") + }() + gInfo, result, anteEvents, resCtx, err := legacyabci.DeliverTx(ctx.WithTxBytes(req.Tx).WithTxSum(checksum).WithVoteInfos(app.UnsafeGetVoteInfos()), tx, app.GetTxConfig(), &app.DeliverTxKeepers, checksum, func(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + return app.CacheTxContext(ctx, checksum) + }, app.RunMsgs, app.TracingInfo, app.AddCosmosEventsToEVMReceiptIfApplicable) + if err != nil { + resultStr = "failed" + // if we have a result, use those events instead of just the anteEvents + if result != nil { + return sdkerrors.ResponseDeliverTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, sdk.MarkEventsToIndex(result.Events, app.IndexEvents), false) + } + return sdkerrors.ResponseDeliverTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, sdk.MarkEventsToIndex(anteEvents, app.IndexEvents), false) + } + + res := abci.ResponseDeliverTx{ + GasWanted: int64(gInfo.GasWanted), //nolint:gosec + GasUsed: int64(gInfo.GasUsed), //nolint:gosec + Log: result.Log, + Data: result.Data, + Events: sdk.MarkEventsToIndex(result.Events, app.IndexEvents), + } + if resCtx.IsEVM() { + res.EvmTxInfo = &abci.EvmTxInfo{ + SenderAddress: resCtx.EVMSenderAddress(), + Nonce: resCtx.EVMNonce(), + TxHash: resCtx.EVMTxHash(), + VmError: result.EvmError, + } + // TODO: populate error data for EVM err + if result.EvmError != "" { + evmErr := sdkerrors.Wrap(sdkerrors.ErrEVMVMError, result.EvmError) + res.Codespace, res.Code, res.Log = sdkerrors.ABCIInfo(evmErr, false) + resultStr = "failed" + return res + } + } + return res } // DeliverTxBatch is not part of the ABCI specification, but this is here for code convention @@ -126,7 +172,24 @@ func (app *App) DeliverTxBatch(ctx sdk.Context, req sdk.DeliverTxBatchRequest) ( defer span.End() // update context with trace span new context ctx = ctx.WithTraceSpanContext(spanCtx) - return app.BaseApp.DeliverTxBatch(ctx, req) + responses := make([]*sdk.DeliverTxResult, 0, len(req.TxEntries)) + + if len(req.TxEntries) == 0 { + return sdk.DeliverTxBatchResponse{Results: responses} + } + + // avoid overhead for empty batches + scheduler := tasks.NewScheduler(app.ConcurrencyWorkers(), app.TracingInfo, app.DeliverTx) + txRes, err := scheduler.ProcessAll(ctx, req.TxEntries) + if err != nil { + ctx.Logger().Error("error while processing scheduler", "err", err) + panic(err) + } + for _, tx := range txRes { + responses = append(responses, &sdk.DeliverTxResult{Response: tx}) + } + + return sdk.DeliverTxBatchResponse{Results: responses} } func (app *App) Commit(ctx context.Context) (res *abci.ResponseCommit, err error) { diff --git a/app/ante/cosmos_checktx.go b/app/ante/cosmos_checktx.go index e5cb6251cc..f92d8ca7fb 100644 --- a/app/ante/cosmos_checktx.go +++ b/app/ante/cosmos_checktx.go @@ -2,6 +2,7 @@ package ante import ( "bytes" + "encoding/base64" "errors" "fmt" @@ -99,11 +100,11 @@ func CosmosCheckTxAnte( return ctx, err } - if err := CheckSignatures(ctx, txConfig, tx, signerAccounts, authParams); err != nil { + if _, err := CheckSignatures(ctx, txConfig, tx, signerAccounts, authParams); err != nil { return ctx, err } - if err := UpdateSigners(ctx, tx, accountKeeper, ek); err != nil { + if _, err := UpdateSigners(ctx, tx, accountKeeper, ek); err != nil { return ctx, err } @@ -284,6 +285,18 @@ func CheckAndChargeFees(ctx sdk.Context, tx sdk.Tx, accountKeeper authkeeper.Acc return priority, fmt.Errorf("fee collector module account (%s) has not been set", authtypes.FeeCollectorName) } + if _, err := chargeFees(ctx, tx, feeCoins, accountKeeper, bankKeeper, feegrantKeeper); err != nil { + return priority, err + } + return priority, nil +} + +func chargeFees(ctx sdk.Context, tx sdk.Tx, feeCoins sdk.Coins, accountKeeper authkeeper.AccountKeeper, bankKeeper bankkeeper.Keeper, feegrantKeeper *feegrantkeeper.Keeper) (sdk.AccAddress, error) { + if addr := accountKeeper.GetModuleAddress(authtypes.FeeCollectorName); addr == nil { + return nil, fmt.Errorf("fee collector module account (%s) has not been set", authtypes.FeeCollectorName) + } + + feeTx := tx.(sdk.FeeTx) feePayer := feeTx.FeePayer() feeGranter := feeTx.FeeGranter() deductFeesFrom := feePayer @@ -292,11 +305,11 @@ func CheckAndChargeFees(ctx sdk.Context, tx sdk.Tx, accountKeeper authkeeper.Acc // this works with only when feegrant enabled. if feeGranter != nil { if feegrantKeeper == nil { - return priority, sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled") + return nil, sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled") } else if !feeGranter.Equals(feePayer) { err := feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, feeCoins, tx.GetMsgs()) if err != nil { - return priority, sdkerrors.Wrapf(err, "%s does not not allow to pay fees for %s", feeGranter, feePayer) + return nil, sdkerrors.Wrapf(err, "%s does not not allow to pay fees for %s", feeGranter, feePayer) } } @@ -305,22 +318,22 @@ func CheckAndChargeFees(ctx sdk.Context, tx sdk.Tx, accountKeeper authkeeper.Acc deductFeesFromAcc := accountKeeper.GetAccount(ctx, deductFeesFrom) if deductFeesFromAcc == nil { - return priority, sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) + return nil, sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) } // deduct the fees if !feeCoins.IsZero() { if !feeCoins.IsValid() { - return priority, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", feeCoins) + return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", feeCoins) } err := bankKeeper.DeferredSendCoinsFromAccountToModule(ctx, deductFeesFromAcc.GetAddress(), authtypes.FeeCollectorName, feeCoins) if err != nil { - return priority, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "%s", err.Error()) + return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "%s", err.Error()) } } - return + return deductFeesFrom, nil } func DecoratePriority(ctx sdk.Context, priority int64, oracleVote bool) sdk.Context { @@ -376,11 +389,11 @@ func CheckPubKeys(ctx sdk.Context, tx sdk.Tx, accountKeeper authkeeper.AccountKe return signerAcounts, nil } -func CheckSignatures(ctx sdk.Context, txConfig client.TxConfig, tx sdk.Tx, signerAccounts []authtypes.AccountI, authParams authtypes.Params) error { +func CheckSignatures(ctx sdk.Context, txConfig client.TxConfig, tx sdk.Tx, signerAccounts []authtypes.AccountI, authParams authtypes.Params) (sdk.Events, error) { sigTx := tx.(authsigning.SigVerifiableTx) sigs, err := sigTx.GetSignaturesV2() if err != nil { - return err + return nil, err } // stdSigs contains the sequence number, account number, and signatures. @@ -388,12 +401,21 @@ func CheckSignatures(ctx sdk.Context, txConfig client.TxConfig, tx sdk.Tx, signe signerAddrs := sigTx.GetSigners() // check that signer length and signature length are the same if len(sigs) != len(signerAddrs) { - return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs)) + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs)) } + var events sdk.Events for i, sig := range sigs { - - if _, err := authante.SignatureDataToBz(sig.Data); err != nil { - return err + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerAddrs[i], sig.Sequence)), + )) + if sigBzs, err := authante.SignatureDataToBz(sig.Data); err != nil { + return nil, err + } else { + for _, sigBz := range sigBzs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)), + )) + } } signerAcc := signerAccounts[i] @@ -409,13 +431,13 @@ func CheckSignatures(ctx sdk.Context, txConfig client.TxConfig, tx sdk.Tx, signe err = authante.DefaultSigVerificationGasConsumer(ctx.GasMeter(), sig, authParams) if err != nil { - return err + return nil, err } // Check account sequence number. if sig.Sequence != signerAcc.GetSequence() { if !authParams.GetDisableSeqnoCheck() { - return sdkerrors.Wrapf( + return nil, sdkerrors.Wrapf( sdkerrors.ErrWrongSequence, "account sequence mismatch, expected %d, got %d", signerAcc.GetSequence(), sig.Sequence, ) @@ -449,15 +471,16 @@ func CheckSignatures(ctx sdk.Context, txConfig client.TxConfig, tx sdk.Tx, signe } else { errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID) } - return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg) } } - return nil + return events, nil } -func UpdateSigners(ctx sdk.Context, tx sdk.Tx, accountKeeper authkeeper.AccountKeeper, evmKeeper *evmkeeper.Keeper) error { +func UpdateSigners(ctx sdk.Context, tx sdk.Tx, accountKeeper authkeeper.AccountKeeper, evmKeeper *evmkeeper.Keeper) (sdk.Events, error) { signers := tx.(authsigning.SigVerifiableTx).GetSigners() + var events sdk.Events for _, signer := range signers { acc := accountKeeper.GetAccount(ctx, signer) if err := acc.SetSequence(acc.GetSequence() + 1); err != nil { @@ -465,37 +488,49 @@ func UpdateSigners(ctx sdk.Context, tx sdk.Tx, accountKeeper authkeeper.AccountK } accountKeeper.SetAccount(ctx, acc) - if _, associated := evmKeeper.GetEVMAddress(ctx, signer); associated { + if evmAddr, associated := evmKeeper.GetEVMAddress(ctx, signer); associated { + events = append(events, sdk.NewEvent(evmtypes.EventTypeSigner, + sdk.NewAttribute(evmtypes.AttributeKeyEvmAddress, evmAddr.Hex()), + sdk.NewAttribute(evmtypes.AttributeKeySeiAddress, signer.String()))) continue } if acc.GetPubKey() == nil { ctx.Logger().Error(fmt.Sprintf("missing pubkey for %s", signer.String())) + events = append(events, sdk.NewEvent(evmtypes.EventTypeSigner, + sdk.NewAttribute(evmtypes.AttributeKeySeiAddress, signer.String()))) continue } pk, err := btcec.ParsePubKey(acc.GetPubKey().Bytes()) if err != nil { ctx.Logger().Debug(fmt.Sprintf("failed to parse pubkey for %s, likely due to the fact that it isn't on secp256k1 curve", acc.GetPubKey()), "err", err) + events = append(events, sdk.NewEvent(evmtypes.EventTypeSigner, + sdk.NewAttribute(evmtypes.AttributeKeySeiAddress, signer.String()))) continue } evmAddr, err := helpers.PubkeyToEVMAddress(pk.SerializeUncompressed()) if err != nil { ctx.Logger().Error(fmt.Sprintf("failed to get EVM address from pubkey due to %s", err)) + events = append(events, sdk.NewEvent(evmtypes.EventTypeSigner, + sdk.NewAttribute(evmtypes.AttributeKeySeiAddress, signer.String()))) continue } + events = append(events, sdk.NewEvent(evmtypes.EventTypeSigner, + sdk.NewAttribute(evmtypes.AttributeKeyEvmAddress, evmAddr.Hex()), + sdk.NewAttribute(evmtypes.AttributeKeySeiAddress, signer.String()))) evmKeeper.SetAddressMapping(ctx, signer, evmAddr) associationHelper := helpers.NewAssociationHelper(evmKeeper, evmKeeper.BankKeeper(), accountKeeper) if err := associationHelper.MigrateBalance(ctx, evmAddr, signer); err != nil { ctx.Logger().Error(fmt.Sprintf("failed to migrate EVM address balance (%s) %s", evmAddr.Hex(), err)) - return err + return nil, err } if evmtypes.IsTxMsgAssociate(tx) { // check if there is non-zero balance if !evmKeeper.BankKeeper().GetBalance(ctx, signer, sdk.MustGetBaseDenom()).IsPositive() && !evmKeeper.BankKeeper().GetWeiBalance(ctx, signer).IsPositive() { - return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "account needs to have at least 1 wei to force association") + return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "account needs to have at least 1 wei to force association") } } } - return nil + return events, nil } func CheckMessage(ctx sdk.Context, tx sdk.Tx, ibcKeeper *ibckeeper.Keeper, oracleKeeper oraclekeeper.Keeper) error { diff --git a/app/ante/cosmos_delivertx.go b/app/ante/cosmos_delivertx.go new file mode 100644 index 0000000000..5f0b0074b7 --- /dev/null +++ b/app/ante/cosmos_delivertx.go @@ -0,0 +1,91 @@ +package ante + +import ( + "github.com/cosmos/cosmos-sdk/client" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" + oraclekeeper "github.com/sei-protocol/sei-chain/x/oracle/keeper" +) + +func CosmosDeliverTxAnte( + ctx sdk.Context, + txConfig client.TxConfig, + tx sdk.Tx, + pk paramskeeper.Keeper, + oraclek oraclekeeper.Keeper, + ek *evmkeeper.Keeper, + accountKeeper authkeeper.AccountKeeper, + bankKeeper bankkeeper.Keeper, + feegrantKeeper *feegrantkeeper.Keeper, +) (returnCtx sdk.Context, returnErr error) { + if _, err := CosmosStatelessChecks(tx, ctx.BlockHeight(), ctx.ConsensusParams()); err != nil { + return ctx, err + } + + defer func() { + if r := recover(); r != nil { + returnErr = HandleOutofGas(r, tx.(GasTx).GetGas(), ctx.GasMeter().GasConsumed()) + } + }() + gasMeter, _, err := GetGasMeter(ctx.WithGasMeter(storetypes.NewNoConsumptionInfiniteGasMeter()), tx, oraclek, ek, pk) + if err != nil { + return ctx, err + } + ctx = ctx.WithGasMeter(gasMeter) + + authParams := accountKeeper.GetParams(ctx) + + if err := CheckMemoLength(tx, authParams); err != nil { + return ctx, err + } + + ctx.GasMeter().ConsumeGas(authParams.TxSizeCostPerByte*sdk.Gas(len(ctx.TxBytes())), "txSize") + + signerAccounts, err := CheckPubKeys(ctx, tx, accountKeeper, authParams) + if err != nil { + return ctx, err + } + + sigEvents, err := CheckSignatures(ctx, txConfig, tx, signerAccounts, authParams) + if err != nil { + return ctx, err + } + ctx.EventManager().EmitEvents(sigEvents) + + signerEvents, err := UpdateSigners(ctx, tx, accountKeeper, ek) + if err != nil { + return ctx, err + } + ctx.EventManager().EmitEvents(signerEvents) + + if err := ChargeFees(ctx, tx, accountKeeper, bankKeeper, feegrantKeeper, pk); err != nil { + return ctx, err + } + + return ctx, nil +} + +func ChargeFees(ctx sdk.Context, tx sdk.Tx, accountKeeper authkeeper.AccountKeeper, bankKeeper bankkeeper.Keeper, feegrantKeeper *feegrantkeeper.Keeper, paramsKeeper paramskeeper.Keeper) error { + feeTx := tx.(sdk.FeeTx) + feeCoins := feeTx.GetFee() + feeParams := paramsKeeper.GetFeesParams(ctx) + feeCoins = feeCoins.NonZeroAmountsOf(append([]string{sdk.DefaultBondDenom}, feeParams.GetAllowedFeeDenoms()...)) + deductFeesFrom, err := chargeFees(ctx, tx, feeCoins, accountKeeper, bankKeeper, feegrantKeeper) + if err != nil { + return err + } + events := sdk.Events{ + sdk.NewEvent( + sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyFee, feeCoins.String()), + sdk.NewAttribute(sdk.AttributeKeyFeePayer, deductFeesFrom.String()), + ), + } + ctx.EventManager().EmitEvents(events) + return nil +} diff --git a/app/ante/evm_checktx.go b/app/ante/evm_checktx.go index b1f036df8a..9d4cbc68c3 100644 --- a/app/ante/evm_checktx.go +++ b/app/ante/evm_checktx.go @@ -49,17 +49,17 @@ func EvmCheckTxAnte( txData, _ := evmtypes.UnpackTxData(msg.Data) // cached and validated ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)) if atx, ok := txData.(*ethtx.AssociateTx); ok { - return HandleAssociateTx(ctx, ek, atx) + return HandleAssociateTx(ctx, ek, atx, true) } etx := ethtypes.NewTx(txData.AsEthereumData()) - evmAddr, seiAddr, seiPubkey, version, err := CheckAndDecodeSignature(ctx, txData, chainID) + evmAddr, seiAddr, seiPubkey, version, err := CheckAndDecodeSignature(ctx, txData, chainID, false) if err != nil { return ctx, err } if err := AssociateAddress(ctx, ek, evmAddr, seiAddr, seiPubkey); err != nil { return ctx, err } - if err := EvmCheckAndChargeFees(ctx, evmAddr, ek, upgradeKeeper, txData, etx, msg, version); err != nil { + if _, err := EvmCheckAndChargeFees(ctx, evmAddr, ek, upgradeKeeper, txData, etx, msg, version, false); err != nil { return ctx, err } @@ -121,7 +121,11 @@ func EvmStatelessChecks(ctx sdk.Context, tx sdk.Tx, chainID *big.Int) error { return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "not EVM message") } if err := msg.ValidateBasic(); err != nil { - return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, err.Error()) + return err + } + if msg.Derived != nil && msg.Derived.PubKey == nil { + // this means the message has `Derived` set from the outside, in which case we should reject + return sdkerrors.ErrInvalidPubKey } txData, err := evmtypes.UnpackTxData(msg.Data) if err != nil { @@ -207,12 +211,12 @@ func DecorateContext(ctx sdk.Context, ek *evmkeeper.Keeper, tx sdk.Tx, txData et return ctx } -func HandleAssociateTx(ctx sdk.Context, ek *evmkeeper.Keeper, atx *ethtx.AssociateTx) (sdk.Context, error) { +func HandleAssociateTx(ctx sdk.Context, ek *evmkeeper.Keeper, atx *ethtx.AssociateTx, readOnly bool) (sdk.Context, error) { V, R, S := atx.GetRawSignatureValues() V = new(big.Int).Add(V, utils.Big27) // Hash custom message passed in customMessageHash := crypto.Keccak256Hash([]byte(atx.CustomMessage)) - evmAddr, seiAddr, _, err := helpers.GetAddresses(V, R, S, customMessageHash) + evmAddr, seiAddr, seiPubkey, err := helpers.GetAddresses(V, R, S, customMessageHash) if err != nil { return ctx, err } @@ -223,10 +227,15 @@ func HandleAssociateTx(ctx sdk.Context, ek *evmkeeper.Keeper, atx *ethtx.Associa if !IsAccountBalancePositive(ctx, ek, seiAddr, evmAddr) { return ctx, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "account needs to have at least 1 wei to force association") } + if !readOnly { + if err := AssociateAddress(ctx, ek, evmAddr, seiAddr, seiPubkey); err != nil { + return ctx, err + } + } return ctx.WithPriority(antedecorators.EVMAssociatePriority), nil } -func CheckAndDecodeSignature(ctx sdk.Context, txData ethtx.TxData, chainID *big.Int) (common.Address, sdk.AccAddress, cryptotypes.PubKey, derived.SignerVersion, error) { +func CheckAndDecodeSignature(ctx sdk.Context, txData ethtx.TxData, chainID *big.Int, isBlockTest bool) (common.Address, sdk.AccAddress, cryptotypes.PubKey, derived.SignerVersion, error) { ethTx := ethtypes.NewTx(txData.AsEthereumData()) if ethTx.Type() != ethtypes.LegacyTxType { chainID = ethTx.ChainId() @@ -245,7 +254,13 @@ func CheckAndDecodeSignature(ctx sdk.Context, txData ethtx.TxData, chainID *big. V = evmante.AdjustV(V, ethTx.Type(), ethCfg.ChainID) txHash = signer.Hash(ethTx) } else { - return common.Address{}, sdk.AccAddress{}, nil, 0, errors.New("unsupported tx type: unsafe legacy tx") + if isBlockTest { + // need to allow unprotected legacy txs in blocktest + // to not lose coverage for other parts of the code + txHash = ethtypes.FrontierSigner{}.Hash(ethTx) + } else { + return common.Address{}, sdk.AccAddress{}, nil, 0, errors.New("unsupported tx type: unsafe legacy tx") + } } evmAddr, seiAddr, seiPubkey, err := helpers.GetAddresses(V, R, S, txHash) if err != nil { @@ -265,19 +280,19 @@ func AssociateAddress(ctx sdk.Context, ek *evmkeeper.Keeper, evmAddr common.Addr return nil } -func EvmCheckAndChargeFees(ctx sdk.Context, sender common.Address, ek *evmkeeper.Keeper, upgradeKeeper *upgradekeeper.Keeper, txData ethtx.TxData, etx *ethtypes.Transaction, msg *evmtypes.MsgEVMTransaction, version derived.SignerVersion) error { +func EvmCheckAndChargeFees(ctx sdk.Context, sender common.Address, ek *evmkeeper.Keeper, upgradeKeeper *upgradekeeper.Keeper, txData ethtx.TxData, etx *ethtypes.Transaction, msg *evmtypes.MsgEVMTransaction, version derived.SignerVersion, statelessChecks bool) (*state.DBImpl, error) { if txData.GetGasFeeCap().Cmp(GetBaseFee(ctx, ek, upgradeKeeper)) < 0 { - return sdkerrors.ErrInsufficientFee + return nil, sdkerrors.ErrInsufficientFee } if txData.GetGasFeeCap().Cmp(GetMinimumFee(ctx, ek)) < 0 { - return sdkerrors.ErrInsufficientFee + return nil, sdkerrors.ErrInsufficientFee } if version >= derived.Cancun && len(txData.GetBlobHashes()) > 0 { // For now we are simply assuming excessive blob gas is 0. In the future we might change it to be // dynamic based on prior block usage. chainConfig := evmtypes.DefaultChainConfig().EthereumConfig(ek.ChainID(ctx)) if txData.GetBlobFeeCap().Cmp(eip4844.CalcBlobFee(chainConfig, ðtypes.Header{Time: uint64(ctx.BlockTime().Unix())})) < 0 { // nolint:gosec - return sdkerrors.ErrInsufficientFee + return nil, sdkerrors.ErrInsufficientFee } } emsg := ek.GetEVMMessage(ctx, etx, sender) @@ -285,17 +300,22 @@ func EvmCheckAndChargeFees(ctx sdk.Context, sender common.Address, ek *evmkeeper gp := ek.GetGasPool() blockCtx, err := ek.GetVMBlockContext(ctx, gp) if err != nil { - return err + return nil, err } cfg := evmtypes.DefaultChainConfig().EthereumConfig(ek.ChainID(ctx)) txCtx := core.NewEVMTxContext(emsg) evmInstance := vm.NewEVM(*blockCtx, stateDB, cfg, vm.Config{}, ek.CustomPrecompiles(ctx)) evmInstance.SetTxContext(txCtx) st := core.NewStateTransition(evmInstance, emsg, &gp, true, false) + if statelessChecks { + if err := st.StatelessChecks(); err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrWrongSequence, err.Error()) + } + } if err := st.BuyGas(); err != nil { - return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, err.Error()) + return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, err.Error()) } - return nil + return stateDB, nil } func CheckNonce(ctx sdk.Context, latestCtxGetter func() sdk.Context, ek *evmkeeper.Keeper, etx *ethtypes.Transaction, evmAddr common.Address, seiAddr sdk.AccAddress) (sdk.Context, error) { diff --git a/app/ante/evm_delivertx.go b/app/ante/evm_delivertx.go new file mode 100644 index 0000000000..ade1cd426a --- /dev/null +++ b/app/ante/evm_delivertx.go @@ -0,0 +1,95 @@ +package ante + +import ( + "math/big" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/sei-protocol/sei-chain/x/evm/derived" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" +) + +func EvmDeliverTxAnte( + ctx sdk.Context, + txConfig client.TxConfig, + tx sdk.Tx, + upgradeKeeper *upgradekeeper.Keeper, + ek *evmkeeper.Keeper, +) (returnCtx sdk.Context, returnErr error) { + ctx = ctx.WithDeliverTxCallback(func(sdk.Context) {}) + chainID := ek.ChainID(ctx) + if err := EvmStatelessChecks(ctx, tx, chainID); err != nil { + return ctx, err + } + msg := tx.GetMsgs()[0].(*evmtypes.MsgEVMTransaction) + txData, _ := evmtypes.UnpackTxData(msg.Data) // cached and validated + ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx)) + if atx, ok := txData.(*ethtx.AssociateTx); ok { + return HandleAssociateTx(ctx, ek, atx, false) + } + etx := ethtypes.NewTx(txData.AsEthereumData()) + evmAddr, version, err := EvmDeliverHandleSignatures(ctx, ek, txData, chainID, msg) + if err != nil { + return ctx, err + } + ctx = DecorateNonceCallback(ctx, ek, evmAddr, etx.Nonce()) + if err := EvmDeliverChargeFees(ctx, ek, upgradeKeeper, txData, etx, msg, version, evmAddr); err != nil { + return ctx, err + } + return DecorateContext(ctx, ek, tx, txData, etx, evmAddr), nil +} + +func EvmDeliverHandleSignatures(ctx sdk.Context, ek *evmkeeper.Keeper, txData ethtx.TxData, chainID *big.Int, msg *evmtypes.MsgEVMTransaction) (common.Address, derived.SignerVersion, error) { + evmAddr, seiAddr, seiPubkey, version, err := CheckAndDecodeSignature(ctx, txData, chainID, ek.EthBlockTestConfig.Enabled) + if err != nil { + return evmAddr, version, err + } + if err := AssociateAddress(ctx, ek, evmAddr, seiAddr, seiPubkey); err != nil { + return evmAddr, version, err + } + if ek.EthReplayConfig.Enabled { + ek.PrepareReplayedAddr(ctx, evmAddr) + } + msg.Derived = &derived.Derived{ + SenderEVMAddr: evmAddr, + SenderSeiAddr: seiAddr, + PubKey: &secp256k1.PubKey{Key: seiPubkey.Bytes()}, + Version: version, + IsAssociate: false, + } + return evmAddr, version, nil +} + +func EvmDeliverChargeFees(ctx sdk.Context, ek *evmkeeper.Keeper, upgradeKeeper *upgradekeeper.Keeper, txData ethtx.TxData, etx *ethtypes.Transaction, msg *evmtypes.MsgEVMTransaction, version derived.SignerVersion, evmAddr common.Address) error { + stateDB, err := EvmCheckAndChargeFees(ctx, evmAddr, ek, upgradeKeeper, txData, etx, msg, version, true) + if err != nil { + return err + } + surplus, err := stateDB.Finalize() + if err != nil { + return err + } + return ek.AddAnteSurplus(ctx, etx.Hash(), surplus) +} + +func DecorateNonceCallback(ctx sdk.Context, ek *evmkeeper.Keeper, evmAddr common.Address, txNonce uint64) sdk.Context { + if ek.EthReplayConfig.Enabled || ek.EthBlockTestConfig.Enabled { + return ctx + } + startingNonce := ek.GetNonce(ctx, evmAddr) + if startingNonce != txNonce { + return ctx + } + return ctx.WithDeliverTxCallback(func(callCtx sdk.Context) { + // bump nonce if it is for some reason not incremented (e.g. ante failure) + if ek.GetNonce(callCtx, evmAddr) == startingNonce { + ek.SetNonce(callCtx, evmAddr, startingNonce+1) + } + }) +} diff --git a/app/app.go b/app/app.go index 25a79badd9..5c06e6405b 100644 --- a/app/app.go +++ b/app/app.go @@ -341,6 +341,7 @@ type App struct { BeginBlockKeepers legacyabci.BeginBlockKeepers EndBlockKeepers legacyabci.EndBlockKeepers CheckTxKeepers legacyabci.CheckTxKeepers + DeliverTxKeepers legacyabci.DeliverTxKeepers // mm is the module manager mm *module.Manager @@ -768,6 +769,15 @@ func New( ParamsKeeper: app.ParamsKeeper, UpgradeKeeper: &app.UpgradeKeeper, } + app.DeliverTxKeepers = legacyabci.DeliverTxKeepers{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + FeeGrantKeeper: &app.FeeGrantKeeper, + OracleKeeper: app.OracleKeeper, + EvmKeeper: &app.EvmKeeper, + ParamsKeeper: app.ParamsKeeper, + UpgradeKeeper: &app.UpgradeKeeper, + } app.mm.SetOrderMidBlockers( oracletypes.ModuleName, diff --git a/app/app_test.go b/app/app_test.go index e5980c1d18..303cd5b552 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -196,8 +196,8 @@ func TestProcessOracleAndOtherTxsSuccess(t *testing.T) { fmt.Println("txResults1", txResults) require.Equal(t, 2, len(txResults)) - require.Equal(t, uint32(3), txResults[0].Code) - require.Equal(t, uint32(5), txResults[1].Code) + require.Equal(t, uint32(15), txResults[0].Code) + require.Equal(t, uint32(15), txResults[1].Code) diffOrderTxs := [][]byte{ otherTx, @@ -220,8 +220,8 @@ func TestProcessOracleAndOtherTxsSuccess(t *testing.T) { require.Equal(t, 2, len(txResults2)) // opposite ordering due to true index ordering - require.Equal(t, uint32(5), txResults2[0].Code) - require.Equal(t, uint32(3), txResults2[1].Code) + require.Equal(t, uint32(15), txResults2[0].Code) + require.Equal(t, uint32(15), txResults2[1].Code) } func TestInvalidProposalWithExcessiveGasWanted(t *testing.T) { diff --git a/app/legacyabci/deliver_tx.go b/app/legacyabci/deliver_tx.go new file mode 100644 index 0000000000..f222191fe6 --- /dev/null +++ b/app/legacyabci/deliver_tx.go @@ -0,0 +1,145 @@ +package legacyabci + +import ( + "fmt" + "time" + + "github.com/armon/go-metrics" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/utils/tracing" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" + "github.com/sei-protocol/sei-chain/app/ante" + evmante "github.com/sei-protocol/sei-chain/x/evm/ante" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" + oraclekeeper "github.com/sei-protocol/sei-chain/x/oracle/keeper" + abci "github.com/tendermint/tendermint/abci/types" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +type DeliverTxKeepers struct { + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + FeeGrantKeeper *feegrantkeeper.Keeper + OracleKeeper oraclekeeper.Keeper + EvmKeeper *evmkeeper.Keeper + ParamsKeeper paramskeeper.Keeper + UpgradeKeeper *upgradekeeper.Keeper +} + +func DeliverTx( + ctx sdk.Context, + tx sdk.Tx, + txConfig client.TxConfig, + keepers *DeliverTxKeepers, + checksum [32]byte, + contextCacher func(sdk.Context) (sdk.Context, sdk.CacheMultiStore), + msgRunner func(ctx sdk.Context, msgs []sdk.Msg) (*sdk.Result, error), //TODO: remove + tracingInfo *tracing.Info, + evmHook func(ctx sdk.Context, tx sdk.Tx, checksum [32]byte, response sdk.DeliverTxHookInput), +) ( + gInfo sdk.GasInfo, + result *sdk.Result, + anteEvents []abci.Event, + txCtx sdk.Context, + err error, +) { + defer telemetry.MeasureThroughputSinceWithLabels( + telemetry.TxCount, + []metrics.Label{ + telemetry.NewLabel("mode", "deliver"), + }, + time.Now(), + ) + // check for existing parent tracer, and if applicable, use it + spanCtx, span := tracingInfo.StartWithContext("DeliverTx", ctx.TraceSpanContext()) + defer span.End() + ctx = ctx.WithTraceSpanContext(spanCtx) + span.SetAttributes(attribute.String("txHash", fmt.Sprintf("%X", checksum))) + var gasWanted uint64 + ms := ctx.MultiStore() + defer func() { + if r := recover(); r != nil { + recoveryMW := newOutOfGasRecoveryMiddleware(gasWanted, ctx, defaultRecoveryMiddleware) + recoveryMW = newOCCAbortRecoveryMiddleware(recoveryMW) // TODO: do we have to wrap with occ enabled check? + err, result = processRecovery(r, recoveryMW), nil + } + gInfo = sdk.GasInfo{GasWanted: gasWanted, GasUsed: ctx.GasMeter().GasConsumed()} + }() + + if tx == nil { + return sdk.GasInfo{}, nil, nil, ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "tx decode error") + } + var anteSpan trace.Span + // trace AnteHandler + _, anteSpan = tracingInfo.StartWithContext("AnteHandler", ctx.TraceSpanContext()) + defer anteSpan.End() + var ( + anteCtx sdk.Context + msCache sdk.CacheMultiStore + ) + anteCtx, msCache = contextCacher(ctx) + anteCtx = anteCtx.WithEventManager(sdk.NewEventManager()) + var newCtx sdk.Context + if isEVM, evmerr := evmante.IsEVMMessage(tx); evmerr != nil { + err = evmerr + } else if isEVM { + newCtx, err = ante.EvmDeliverTxAnte(anteCtx, txConfig, tx, keepers.UpgradeKeeper, keepers.EvmKeeper) + defer func() { + if newCtx.DeliverTxCallback() != nil { + newCtx.DeliverTxCallback()(ctx.WithGasMeter(sdk.NewInfiniteGasMeterWithMultiplier(ctx))) + } + }() + } else { + newCtx, err = ante.CosmosDeliverTxAnte(anteCtx, txConfig, tx, keepers.ParamsKeeper, keepers.OracleKeeper, keepers.EvmKeeper, keepers.AccountKeeper, keepers.BankKeeper, keepers.FeeGrantKeeper) + } + if !newCtx.IsZero() { + ctx = newCtx.WithMultiStore(ms) + } + + events := ctx.EventManager().Events() + if err != nil { + return gInfo, nil, nil, ctx, err + } + gasWanted = ctx.GasMeter().Limit() + msCache.Write() + anteEvents = events.ToABCIEvents() + anteSpan.End() + + runMsgCtx, msCache := contextCacher(ctx) + // TODO: simplify + result, err = msgRunner(runMsgCtx, tx.GetMsgs()) + + if err == nil { + msCache.Write() + } + // we do this since we will only be looking at result in DeliverTx + if result != nil && len(anteEvents) > 0 { + // append the events in the order of occurrence + result.Events = append(anteEvents, result.Events...) + } + // only apply hooks if no error + if err == nil && (!ctx.IsEVM() || result.EvmError == "") { + var evmTxInfo *abci.EvmTxInfo + if ctx.IsEVM() { + evmTxInfo = &abci.EvmTxInfo{ + SenderAddress: ctx.EVMSenderAddress(), + Nonce: ctx.EVMNonce(), + TxHash: ctx.EVMTxHash(), + VmError: result.EvmError, + } + } + evmHook(ctx, tx, checksum, sdk.DeliverTxHookInput{ + EvmTxInfo: evmTxInfo, + Events: result.Events, + }) + } + return gInfo, result, anteEvents, ctx, err +} diff --git a/sei-cosmos/baseapp/abci.go b/sei-cosmos/baseapp/abci.go index 43fe7642bc..df6dc9e93f 100644 --- a/sei-cosmos/baseapp/abci.go +++ b/sei-cosmos/baseapp/abci.go @@ -754,6 +754,17 @@ func (app *BaseApp) GetBlockRetentionHeight(commitHeight int64) int64 { return retentionHeight } +func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { + ctx := app.checkState.ctx.WithTxBytes(txBytes).WithVoteInfos(app.voteInfos).WithConsensusParams(app.GetConsensusParams(app.checkState.ctx)) + ctx, _ = ctx.CacheContext() + tx, err := app.txDecoder(txBytes) + if err != nil { + return sdk.GasInfo{}, nil, err + } + gasInfo, result, _, _, _, _, _, _, err := app.runTx(ctx, runTxModeSimulate, tx, sha256.Sum256(txBytes)) + return gasInfo, result, err +} + func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery { if len(path) >= 2 { switch path[1] { diff --git a/sei-cosmos/baseapp/baseapp.go b/sei-cosmos/baseapp/baseapp.go index a44df6fac1..89fb69d5b6 100644 --- a/sei-cosmos/baseapp/baseapp.go +++ b/sei-cosmos/baseapp/baseapp.go @@ -658,6 +658,10 @@ func (app *BaseApp) setVotesInfo(votes []abci.VoteInfo) { app.voteInfos = votes } +func (app *BaseApp) UnsafeGetVoteInfos() []abci.VoteInfo { + return app.voteInfos +} + // GetConsensusParams returns the current consensus parameters from the BaseApp's // ParamStore. If the BaseApp has no ParamStore defined, nil is returned. func (app *BaseApp) GetConsensusParams(ctx sdk.Context) *tmproto.ConsensusParams { @@ -975,9 +979,9 @@ func (app *BaseApp) runTx(ctx sdk.Context, mode runTxMode, tx sdk.Tx, checksum [ runMsgCtx, msCache := app.CacheTxContext(ctx, checksum) // Attempt to execute all messages and only update state if all messages pass - // and we're in DeliverTx. Note, runMsgs will never return a reference to a + // and we're in DeliverTx. Note, RunMsgs will never return a reference to a // Result if any single message fails or does not have a registered Handler. - result, err = app.runMsgs(runMsgCtx, msgs, mode) + result, err = app.RunMsgs(runMsgCtx, msgs) if err == nil { msCache.Write() @@ -1012,17 +1016,17 @@ func (app *BaseApp) runTx(ctx sdk.Context, mode runTxMode, tx sdk.Tx, checksum [ return gInfo, result, anteEvents, priority, pendingTxChecker, expireHandler, checkTxCallback, ctx, err } -// runMsgs iterates through a list of messages and executes them with the provided +// RunMsgs iterates through a list of messages and executes them with the provided // Context and execution mode. Messages will only be executed during simulation // and DeliverTx. An error is returned if any single message fails or if a // Handler does not exist for a given message route. Otherwise, a reference to a // Result is returned. The caller must not commit state if an error is returned. -func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*sdk.Result, error) { +func (app *BaseApp) RunMsgs(ctx sdk.Context, msgs []sdk.Msg) (*sdk.Result, error) { defer telemetry.MeasureThroughputSinceWithLabels( telemetry.MessageCount, []metrics.Label{ - telemetry.NewLabel("mode", modeKeyToString[mode]), + telemetry.NewLabel("mode", "deliver"), }, time.Now(), ) diff --git a/sei-cosmos/baseapp/test_helpers.go b/sei-cosmos/baseapp/test_helpers.go index 2d539bbc50..18d8bae319 100644 --- a/sei-cosmos/baseapp/test_helpers.go +++ b/sei-cosmos/baseapp/test_helpers.go @@ -21,17 +21,6 @@ func (app *BaseApp) Check(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk return gasInfo, result, err } -func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { - ctx := app.checkState.ctx.WithTxBytes(txBytes).WithVoteInfos(app.voteInfos).WithConsensusParams(app.GetConsensusParams(app.checkState.ctx)) - ctx, _ = ctx.CacheContext() - tx, err := app.txDecoder(txBytes) - if err != nil { - return sdk.GasInfo{}, nil, err - } - gasInfo, result, _, _, _, _, _, _, err := app.runTx(ctx, runTxModeSimulate, tx, sha256.Sum256(txBytes)) - return gasInfo, result, err -} - func (app *BaseApp) Deliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error) { // See comment for Check(). bz, err := txEncoder(tx)