laconicd/x/evm/handler.go
Pierre 153478eb69
evm: roll back CommitStateDB after failing to execute handler (#677)
* Roll back CommitStateDB after failing to execute handler in evm module

* add function CopyCommitStateDB

* add comment

* add comment

* Add ut about the dirty data generated by CommitStateDB

* format code
2021-01-08 09:28:25 -03:00

163 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package evm
import (
"github.com/ethereum/go-ethereum/common"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
tmtypes "github.com/tendermint/tendermint/types"
)
// NewHandler returns a handler for Ethermint type messages.
func NewHandler(k *Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) (result *sdk.Result, err error) {
snapshotStateDB := k.CommitStateDB.Copy()
// The "recover" code here is used to solve the problem of dirty data
// in CommitStateDB due to insufficient gas.
// The following is a detailed description:
// If the gas is insufficient during the execution of the "handler",
// panic will be thrown from the function "ConsumeGas" and finally
// caught by the function "runTx" from Cosmos. The function "runTx"
// will think that the execution of Msg has failed and the modified
// data in the Store will not take effect.
// StacktracerunTx->runMsgs->handler->...->gaskv.Store.Set->ConsumeGas
// The problem is that when the modified data in the Store does not take
// effect, the data in the modified CommitStateDB is not rolled back,
// they take effect, and dirty data is generated.
// Therefore, the code here specifically deals with this situation.
// See https://github.com/cosmos/ethermint/issues/668 for more information.
defer func() {
if r := recover(); r != nil {
// We first used "k.CommitStateDB = snapshotStateDB" to roll back
// CommitStateDB, but this can only change the CommitStateDB in the
// current Keeper object, but the Keeper object will be destroyed
// soon, it is not a global variable, so the content pointed to by
// the CommitStateDB pointer can be modified to take effect.
types.CopyCommitStateDB(snapshotStateDB, k.CommitStateDB)
panic(r)
}
}()
ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) {
case types.MsgEthereumTx:
result, err = handleMsgEthereumTx(ctx, k, msg)
case types.MsgEthermint:
result, err = handleMsgEthermint(ctx, k, msg)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
}
if err != nil {
types.CopyCommitStateDB(snapshotStateDB, k.CommitStateDB)
}
return result, err
}
}
// handleMsgEthereumTx handles an Ethereum specific tx
func handleMsgEthereumTx(ctx sdk.Context, k *Keeper, msg types.MsgEthereumTx) (*sdk.Result, error) {
// execute state transition
res, err := k.EthereumTx(ctx, msg)
if err != nil {
return nil, err
}
// log state transition result
k.Logger(ctx).Info(res.Log)
return res, nil
}
// handleMsgEthermint handles an sdk.StdTx for an Ethereum state transition
func handleMsgEthermint(ctx sdk.Context, k *Keeper, msg types.MsgEthermint) (*sdk.Result, error) {
// parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
if err != nil {
return nil, err
}
txHash := tmtypes.Tx(ctx.TxBytes()).Hash()
ethHash := common.BytesToHash(txHash)
st := types.StateTransition{
AccountNonce: msg.AccountNonce,
Price: msg.Price.BigInt(),
GasLimit: msg.GasLimit,
Amount: msg.Amount.BigInt(),
Payload: msg.Payload,
Csdb: k.CommitStateDB.WithContext(ctx),
ChainID: chainIDEpoch,
TxHash: &ethHash,
Sender: common.BytesToAddress(msg.From.Bytes()),
Simulate: ctx.IsCheckTx(),
}
if msg.Recipient != nil {
to := common.BytesToAddress(msg.Recipient.Bytes())
st.Recipient = &to
}
if !st.Simulate {
// Prepare db for logs
blockHash := types.HashFromContext(ctx)
k.CommitStateDB.Prepare(ethHash, blockHash, k.TxCount)
k.TxCount++
}
config, found := k.GetChainConfig(ctx)
if !found {
return nil, types.ErrChainConfigNotFound
}
executionResult, err := st.TransitionDb(ctx, config)
if err != nil {
return nil, err
}
// update block bloom filter
if !st.Simulate {
k.Bloom.Or(k.Bloom, executionResult.Bloom)
// update transaction logs in KVStore
err = k.SetLogs(ctx, common.BytesToHash(txHash), executionResult.Logs)
if err != nil {
panic(err)
}
}
// log successful execution
k.Logger(ctx).Info(executionResult.Result.Log)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthermint,
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.From.String()),
),
})
if msg.Recipient != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeEthermint,
sdk.NewAttribute(types.AttributeKeyRecipient, msg.Recipient.String()),
),
)
}
// set the events to the result
executionResult.Result.Events = ctx.EventManager().Events()
return executionResult.Result, nil
}