Skip to content

feat: post tx hooks #54

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions x/vm/keeper/hooks.go
Original file line number Diff line number Diff line change
@@ -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
}
27 changes: 27 additions & 0 deletions x/vm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
// ----------------------------------------------------------------------------
Expand Down
55 changes: 53 additions & 2 deletions x/vm/keeper/state_transition.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Comment on lines +200 to +203
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not too familiar with this code path at this point, but is this the expected behavior? Possibly we should be stopping execution if we've gone over the gas limit and returning a out of gas error?

}

receipt := &ethtypes.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()
Expand Down
Loading