evm: implement ADR-002 EVM Hooks (#417)
* Allow evm to call native modules through logs Closes #416 comment add txHash parameter review suggestions add hooks test * Update x/evm/keeper/hooks.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update x/evm/keeper/hooks_test.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update x/evm/keeper/keeper.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update x/evm/keeper/keeper.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update x/evm/keeper/keeper.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * use table tests * update adr comment * update adr * changelog * Update CHANGELOG.md Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
parent
b76d024225
commit
089afe41a8
@ -48,6 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
|||||||
### Features
|
### Features
|
||||||
|
|
||||||
* (evm) [tharsis#469](https://github.com/tharsis/ethermint/pull/469) Support [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)
|
* (evm) [tharsis#469](https://github.com/tharsis/ethermint/pull/469) Support [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)
|
||||||
|
* (evm) [tharsis#417](https://github.com/tharsis/ethermint/pull/417) Add `EvmHooks` for tx post-processing
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ func (k *EvmKeeper) SetHooks(eh types.EvmHooks) *Keeper;
|
|||||||
The EVM state transition method `ApplyTransaction` should be changed like this:
|
The EVM state transition method `ApplyTransaction` should be changed like this:
|
||||||
|
|
||||||
```golang
|
```golang
|
||||||
// Create cached context which convers both the tx processing and post processing
|
// Need to create a snapshot explicitly to cover both tx processing and post processing logic
|
||||||
revision := k.Snapshot()
|
revision := k.Snapshot()
|
||||||
|
|
||||||
res, err := k.ApplyMessage(evm, msg, ethCfg, false)
|
res, err := k.ApplyMessage(evm, msg, ethCfg, false)
|
||||||
@ -186,12 +186,12 @@ The proposed ADR is backward compatible.
|
|||||||
|
|
||||||
### Negative
|
### Negative
|
||||||
|
|
||||||
- It's possible that some contracts accidentally define a log with the same signature and cause an unintentional result.
|
- On the use case of native call: It's possible that some contracts accidentally define a log with the same signature and cause an unintentional result.
|
||||||
To mitigate this, the implementor could whitelist contracts that are allowed to invoke native calls.
|
To mitigate this, the implementor could whitelist contracts that are allowed to invoke native calls.
|
||||||
|
|
||||||
### Neutral
|
### Neutral
|
||||||
|
|
||||||
- The contract can only call native modules asynchronously, which means it can neither get the result nor handle the error.
|
- On the use case of native call: The contract can only call native modules asynchronously, which means it can neither get the result nor handle the error.
|
||||||
|
|
||||||
## Further Discussions
|
## Further Discussions
|
||||||
|
|
||||||
|
31
x/evm/keeper/hooks.go
Normal file
31
x/evm/keeper/hooks.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/tharsis/ethermint/x/evm/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ types.EvmHooks = MultiEvmHooks{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// MultiEvmHooks combine multiple evm hooks, all hook functions are run in array sequence
|
||||||
|
type MultiEvmHooks []types.EvmHooks
|
||||||
|
|
||||||
|
// NewMultiEvmHooks combine multiple evm hooks
|
||||||
|
func NewMultiEvmHooks(hooks ...types.EvmHooks) MultiEvmHooks {
|
||||||
|
return hooks
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostTxProcessing delegate the call to underlying hooks
|
||||||
|
func (mh MultiEvmHooks) PostTxProcessing(ctx sdk.Context, txHash ethcmn.Hash, logs []*ethtypes.Log) error {
|
||||||
|
for i := range mh {
|
||||||
|
if err := mh[i].PostTxProcessing(ctx, txHash, logs); err != nil {
|
||||||
|
return sdkerrors.Wrapf(err, "EVM hook %T failed", mh[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
76
x/evm/keeper/hooks_test.go
Normal file
76
x/evm/keeper/hooks_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
||||||
|
"github.com/tharsis/ethermint/x/evm/keeper"
|
||||||
|
"github.com/tharsis/ethermint/x/evm/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogRecordHook records all the logs
|
||||||
|
type LogRecordHook struct {
|
||||||
|
Logs []*ethtypes.Log
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dh *LogRecordHook) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error {
|
||||||
|
dh.Logs = logs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailureHook always fail
|
||||||
|
type FailureHook struct{}
|
||||||
|
|
||||||
|
func (dh FailureHook) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error {
|
||||||
|
return errors.New("post tx processing failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *KeeperTestSuite) TestEvmHooks() {
|
||||||
|
testCases := []struct {
|
||||||
|
msg string
|
||||||
|
setupHook func() types.EvmHooks
|
||||||
|
expFunc func(hook types.EvmHooks, result error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"log collect hook",
|
||||||
|
func() types.EvmHooks {
|
||||||
|
return &LogRecordHook{}
|
||||||
|
},
|
||||||
|
func(hook types.EvmHooks, result error) {
|
||||||
|
suite.Require().NoError(result)
|
||||||
|
suite.Require().Equal(1, len((hook.(*LogRecordHook).Logs)))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"always fail hook",
|
||||||
|
func() types.EvmHooks {
|
||||||
|
return &FailureHook{}
|
||||||
|
},
|
||||||
|
func(hook types.EvmHooks, result error) {
|
||||||
|
suite.Require().Error(result)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.SetupTest()
|
||||||
|
hook := tc.setupHook()
|
||||||
|
suite.app.EvmKeeper.SetHooks(keeper.NewMultiEvmHooks(hook))
|
||||||
|
|
||||||
|
k := suite.app.EvmKeeper
|
||||||
|
txHash := common.BigToHash(big.NewInt(1))
|
||||||
|
k.SetTxHashTransient(txHash)
|
||||||
|
k.AddLog(ðtypes.Log{
|
||||||
|
Topics: []common.Hash{},
|
||||||
|
Address: suite.address,
|
||||||
|
})
|
||||||
|
logs := k.GetTxLogs(txHash)
|
||||||
|
result := k.PostTxProcessing(txHash, logs)
|
||||||
|
|
||||||
|
tc.expFunc(hook, result)
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,9 @@ type Keeper struct {
|
|||||||
// trace EVM state transition execution. This value is obtained from the `--trace` flag.
|
// trace EVM state transition execution. This value is obtained from the `--trace` flag.
|
||||||
// For more info check https://geth.ethereum.org/docs/dapp/tracing
|
// For more info check https://geth.ethereum.org/docs/dapp/tracing
|
||||||
debug bool
|
debug bool
|
||||||
|
|
||||||
|
// EVM Hooks for tx post-processing
|
||||||
|
hooks types.EvmHooks
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeeper generates new evm module keeper
|
// NewKeeper generates new evm module keeper
|
||||||
@ -416,3 +419,21 @@ func (k Keeper) ResetAccount(addr common.Address) {
|
|||||||
k.DeleteCode(addr)
|
k.DeleteCode(addr)
|
||||||
k.DeleteAccountStorage(addr)
|
k.DeleteAccountStorage(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHooks sets the hooks for the EVM module
|
||||||
|
func (k *Keeper) SetHooks(eh types.EvmHooks) *Keeper {
|
||||||
|
if k.hooks != nil {
|
||||||
|
panic("cannot set evm hooks twice")
|
||||||
|
}
|
||||||
|
|
||||||
|
k.hooks = eh
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostTxProcessing delegate the call to the hooks. If no hook has been registered, this function returns with a `nil` error
|
||||||
|
func (k *Keeper) PostTxProcessing(txHash common.Hash, logs []*ethtypes.Log) error {
|
||||||
|
if k.hooks == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return k.hooks.PostTxProcessing(k.Ctx(), txHash, logs)
|
||||||
|
}
|
||||||
|
@ -175,6 +175,9 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
|
|||||||
panic("context stack shouldn't be dirty before apply message")
|
panic("context stack shouldn't be dirty before apply message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contains the tx processing and post processing in same scope
|
||||||
|
revision := k.Snapshot()
|
||||||
|
|
||||||
// pass false to execute in real mode, which do actual gas refunding
|
// pass false to execute in real mode, which do actual gas refunding
|
||||||
res, err := k.ApplyMessage(evm, msg, ethCfg, false)
|
res, err := k.ApplyMessage(evm, msg, ethCfg, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -183,6 +186,17 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
|
|||||||
|
|
||||||
res.Hash = txHash.Hex()
|
res.Hash = txHash.Hex()
|
||||||
logs := k.GetTxLogs(txHash)
|
logs := k.GetTxLogs(txHash)
|
||||||
|
|
||||||
|
if !res.Failed() {
|
||||||
|
// Only call hooks if tx executed successfully.
|
||||||
|
if err = k.PostTxProcessing(txHash, logs); err != nil {
|
||||||
|
// If hooks return error, revert the whole tx.
|
||||||
|
k.RevertToSnapshot(revision)
|
||||||
|
res.VmError = types.ErrPostTxProcessing.Error()
|
||||||
|
k.Logger(ctx).Error("tx post processing failed", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(logs) > 0 {
|
if len(logs) > 0 {
|
||||||
res.Logs = types.NewLogsFromEth(logs)
|
res.Logs = types.NewLogsFromEth(logs)
|
||||||
// Update transient block bloom filter
|
// Update transient block bloom filter
|
||||||
|
@ -32,6 +32,10 @@ const (
|
|||||||
codeErrInvalidBaseFee
|
codeErrInvalidBaseFee
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrPostTxProcessing = errors.New("failed to execute post processing")
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrInvalidState returns an error resulting from an invalid Storage State.
|
// ErrInvalidState returns an error resulting from an invalid Storage State.
|
||||||
ErrInvalidState = sdkerrors.Register(ModuleName, codeErrInvalidState, "invalid storage state")
|
ErrInvalidState = sdkerrors.Register(ModuleName, codeErrInvalidState, "invalid storage state")
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||||
|
ethcmn "github.com/ethereum/go-ethereum/common"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccountKeeper defines the expected account keeper interface
|
// AccountKeeper defines the expected account keeper interface
|
||||||
@ -33,3 +35,12 @@ type StakingKeeper interface {
|
|||||||
GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool)
|
GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool)
|
||||||
GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator stakingtypes.Validator, found bool)
|
GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator stakingtypes.Validator, found bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 return an error, the whole transaction is reverted.
|
||||||
|
PostTxProcessing(ctx sdk.Context, txHash ethcmn.Hash, logs []*ethtypes.Log) error
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user