diff --git a/x/vm/keeper/hooks.go b/x/vm/keeper/hooks.go new file mode 100644 index 00000000..70151455 --- /dev/null +++ b/x/vm/keeper/hooks.go @@ -0,0 +1,40 @@ +package keeper + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + ethtypes "github.com/ethereum/go-ethereum/core/types" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Event Hooks +// These can be utilized to customize evm transaction processing. + +// EvmHooks event hooks for evm tx processing +type EvmHooks interface { + // Must be called after tx is processed successfully, if an error is returned, the whole transaction is reverted. + PostTxProcessing(ctx sdk.Context, sender common.Address, msg core.Message, receipt *ethtypes.Receipt) error +} + +var _ EvmHooks = MultiEvmHooks{} + +// MultiEvmHooks combine multiple evm hooks, all hook functions are run in array sequence +type MultiEvmHooks []EvmHooks + +// NewMultiEvmHooks combine multiple evm hooks +func NewMultiEvmHooks(hooks ...EvmHooks) MultiEvmHooks { + return hooks +} + +// PostTxProcessing delegate the call to underlying hooks +func (mh MultiEvmHooks) PostTxProcessing(ctx sdk.Context, sender common.Address, msg core.Message, receipt *ethtypes.Receipt) error { + for i := range mh { + if err := mh[i].PostTxProcessing(ctx, sender, msg, receipt); err != nil { + return errorsmod.Wrapf(err, "EVM hook %T failed", mh[i]) + } + } + return nil +} diff --git a/x/vm/keeper/keeper.go b/x/vm/keeper/keeper.go index 1d63e611..4bfa5669 100644 --- a/x/vm/keeper/keeper.go +++ b/x/vm/keeper/keeper.go @@ -58,6 +58,9 @@ type Keeper struct { // Tracer used to collect execution traces from the EVM transaction execution tracer string + // EVM Hooks for tx post-processing + hooks EvmHooks + // Legacy subspace ss paramstypes.Subspace @@ -170,6 +173,30 @@ func (k Keeper) GetTxIndexTransient(ctx sdk.Context) uint64 { return sdk.BigEndianToUint64(store.Get(types.KeyPrefixTransientTxIndex)) } +// ---------------------------------------------------------------------------- +// Hooks +// ---------------------------------------------------------------------------- + +// SetHooks sets the hooks for the EVM module +// Called only once during initialization, panics if called more than once. +func (k *Keeper) SetHooks(eh EvmHooks) *Keeper { + if k.hooks != nil { + panic("cannot set evm hooks twice") + } + + k.hooks = eh + return k +} + +// PostTxProcessing delegates the call to the hooks. +// If no hook has been registered, this function returns with a `nil` error +func (k *Keeper) PostTxProcessing(ctx sdk.Context, sender common.Address, msg core.Message, receipt *ethtypes.Receipt) error { + if k.hooks == nil { + return nil + } + return k.hooks.PostTxProcessing(ctx, sender, msg, receipt) +} + // ---------------------------------------------------------------------------- // Log // ---------------------------------------------------------------------------- diff --git a/x/vm/keeper/state_transition.go b/x/vm/keeper/state_transition.go index 46f2ce7f..ff0e8b24 100644 --- a/x/vm/keeper/state_transition.go +++ b/x/vm/keeper/state_transition.go @@ -1,11 +1,13 @@ package keeper import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" cmttypes "github.com/cometbft/cometbft/types" @@ -145,7 +147,10 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { // // For relevant discussion see: https://github.com/cosmos/cosmos-sdk/discussions/9072 func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) { - var bloom *big.Int + var ( + bloom *big.Int + bloomReceipt ethtypes.Bloom + ) cfg, err := k.EVMConfig(ctx, sdk.ConsAddress(ctx.BlockHeader().ProposerAddress)) if err != nil { @@ -180,10 +185,56 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t if len(logs) > 0 { bloom = k.GetBlockBloomTransient(ctx) bloom.Or(bloom, big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs))) + bloomReceipt = ethtypes.BytesToBloom(bloom.Bytes()) } if !res.Failed() { - commit() + var contractAddr common.Address + if msg.To() == nil { + contractAddr = crypto.CreateAddress(msg.From(), msg.Nonce()) + } + + cumulativeGasUsed := res.GasUsed + if ctx.BlockGasMeter() != nil { + limit := ctx.BlockGasMeter().Limit() + cumulativeGasUsed += ctx.BlockGasMeter().GasConsumed() + if cumulativeGasUsed > limit { + cumulativeGasUsed = limit + } + } + + receipt := ðtypes.Receipt{ + Type: tx.Type(), + PostState: nil, + CumulativeGasUsed: cumulativeGasUsed, + Bloom: bloomReceipt, + Logs: logs, + TxHash: txConfig.TxHash, + ContractAddress: contractAddr, + GasUsed: res.GasUsed, + BlockHash: txConfig.BlockHash, + BlockNumber: big.NewInt(ctx.BlockHeight()), + TransactionIndex: txConfig.TxIndex, + } + + signerAddr, err := signer.Sender(tx) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to extract sender address from ethereum transaction") + } + + if err = k.PostTxProcessing(tmpCtx, signerAddr, msg, receipt); err != nil { + // If hooks returns an error, revert the whole tx. + res.VmError = fmt.Sprintf("failed to execute post transaction processing: %s", err) + k.Logger(ctx).Error("tx post processing failed", "error", err) + // If the tx failed in post processing hooks, we should clear the logs + res.Logs = nil + } else if commit != nil { + commit() + + // Since the post-processing can alter the log, we need to update the result + res.Logs = types.NewLogsFromEth(receipt.Logs) + ctx.EventManager().EmitEvents(tmpCtx.EventManager().Events()) + } } evmDenom := types.GetEVMCoinDenom()