evm: use stack of contexts to implement snapshot revert (#399)

* use stack of contexts to implement snapshot revert

Closes #338

add exception revert test case

verify partial revert

mutate state after the reverted subcall

polish

update comments

name the module after the type name

remove the unnecessary Snapshot in outer layer

and add snapshot unit test

assert context stack is clean after tx processing

cleanups

fix context revert

fix comments

update comments

it's ok to commit in failed case too

Update x/evm/keeper/context_stack.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

Update x/evm/keeper/context_stack.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

Update x/evm/keeper/context_stack.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

update comment and error message

add comment to cacheContext

k -> cs

Update x/evm/keeper/context_stack.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

evm can handle state revert

renames and unit tests

* use table driven tests

* keep all the cosmos events

* changelog

* check for if commit function is nil

* fix changelog

* Update x/evm/keeper/context_stack.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang 2021-08-10 15:22:46 +08:00 committed by GitHub
parent 96328453e5
commit 9227e78c79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 417 additions and 154 deletions

View File

@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (evm) [tharsis#342](https://github.com/tharsis/ethermint/issues/342) Don't clear balance when resetting the account. * (evm) [tharsis#342](https://github.com/tharsis/ethermint/issues/342) Don't clear balance when resetting the account.
* (evm) [tharsis#334](https://github.com/tharsis/ethermint/pull/334) Log index changed to the index in block rather than * (evm) [tharsis#334](https://github.com/tharsis/ethermint/pull/334) Log index changed to the index in block rather than
tx. tx.
* (evm) [tharsis#399](https://github.com/tharsis/ethermint/pull/399) Exception in sub-message call reverts the call if it's not propagated.
### API Breaking ### API Breaking

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
contract State {
uint256 a = 0;
function set(uint256 input) public {
a = input;
require(a < 10);
}
function force_set(uint256 input) public {
a = input;
}
function query() public view returns(uint256) {
return a;
}
}
contract TestRevert {
State state;
uint256 b = 0;
uint256 c = 0;
constructor() {
state = new State();
}
function try_set(uint256 input) public {
b = input;
try state.set(input) {
} catch (bytes memory) {
}
c = input;
}
function set(uint256 input) public {
state.force_set(input);
}
function query_a() public view returns(uint256) {
return state.query();
}
function query_b() public view returns(uint256) {
return b;
}
function query_c() public view returns(uint256) {
return c;
}
}

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
contract Migrations {
address public owner = msg.sender;
uint public last_completed_migration;
modifier restricted() {
require(
msg.sender == owner,
"This function is restricted to the contract's owner"
);
_;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
}

View File

@ -0,0 +1,5 @@
const Migrations = artifacts.require("Migrations");
module.exports = function (deployer) {
deployer.deploy(Migrations);
};

View File

@ -0,0 +1,15 @@
{
"name": "exception",
"version": "1.0.0",
"author": "huangyi <huang@crypto.com>",
"license": "GPL-3.0-or-later",
"scripts": {
"test-ganache": "yarn truffle test",
"test-ethermint": "yarn truffle test --network ethermint"
},
"devDependencies": {
"truffle": "^5.1.42",
"truffle-assertions": "^0.9.2",
"web3": "^1.2.11"
}
}

View File

@ -0,0 +1,35 @@
const TestRevert = artifacts.require("TestRevert")
const truffleAssert = require('truffle-assertions');
async function expectRevert(promise) {
try {
await promise;
} catch (error) {
if (error.message.indexOf('revert') === -1) {
expect('revert').to.equal(error.message, 'Wrong kind of exception received');
}
return;
}
expect.fail('Expected an exception but none was received');
}
contract('TestRevert', (accounts) => {
let revert
beforeEach(async () => {
revert = await TestRevert.new()
})
it('should revert', async () => {
await revert.try_set(10)
no = await revert.query_a()
assert.equal(no, '0', 'The modification on a should be reverted')
no = await revert.query_b()
assert.equal(no, '10', 'The modification on b should not be reverted')
no = await revert.query_c()
assert.equal(no, '10', 'The modification on c should not be reverted')
await revert.set(10)
no = await revert.query_a()
assert.equal(no, '10', 'The force set should not be reverted')
})
})

View File

@ -0,0 +1,17 @@
module.exports = {
networks: {
// Development network is just left as truffle's default settings
ethermint: {
host: "127.0.0.1", // Localhost (default: none)
port: 8545, // Standard Ethereum port (default: none)
network_id: "*", // Any network (default: none)
gas: 5000000, // Gas sent with each transaction
gasPrice: 1000000000, // 1 gwei (in wei)
},
},
compilers: {
solc: {
version: "0.8.6",
},
},
}

View File

@ -0,0 +1,87 @@
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// cachedContext is a pair of cache context and its corresponding commit method.
// They are obtained from the return value of `context.CacheContext()`.
type cachedContext struct {
ctx sdk.Context
commit func()
}
// ContextStack manages the initial context and a stack of cached contexts,
// to support the `StateDB.Snapshot` and `StateDB.RevertToSnapshot` methods.
type ContextStack struct {
// Context of the initial state before transaction execution.
// It's the context used by `StateDB.CommitedState`.
initialCtx sdk.Context
cachedContexts []cachedContext
}
// CurrentContext returns the top context of cached stack,
// if the stack is empty, returns the initial context.
func (cs *ContextStack) CurrentContext() sdk.Context {
l := len(cs.cachedContexts)
if l == 0 {
return cs.initialCtx
}
return cs.cachedContexts[l-1].ctx
}
// Reset sets the initial context and clear the cache context stack.
func (cs *ContextStack) Reset(ctx sdk.Context) {
cs.initialCtx = ctx
if len(cs.cachedContexts) > 0 {
cs.cachedContexts = []cachedContext{}
}
}
// IsEmpty returns true if the cache context stack is empty.
func (cs *ContextStack) IsEmpty() bool {
return len(cs.cachedContexts) == 0
}
// Commit commits all the cached contexts from top to bottom in order and clears the stack by setting an empty slice of cache contexts.
func (cs *ContextStack) Commit() {
// commit in order from top to bottom
for i := len(cs.cachedContexts) - 1; i >= 0; i-- {
// keep all the cosmos events
cs.initialCtx.EventManager().EmitEvents(cs.cachedContexts[i].ctx.EventManager().Events())
if cs.cachedContexts[i].commit == nil {
panic(fmt.Sprintf("commit function at index %d should not be nil", i))
} else {
cs.cachedContexts[i].commit()
}
}
cs.cachedContexts = []cachedContext{}
}
// Snapshot pushes a new cached context to the stack,
// and returns the index of it.
func (cs *ContextStack) Snapshot() int {
i := len(cs.cachedContexts)
ctx, commit := cs.CurrentContext().CacheContext()
cs.cachedContexts = append(cs.cachedContexts, cachedContext{ctx: ctx, commit: commit})
return i
}
// RevertToSnapshot pops all the cached contexts after the target index (inclusive).
// the target should be snapshot index returned by `Snapshot`.
// This function panics if the index is out of bounds.
func (cs *ContextStack) RevertToSnapshot(target int) {
if target < 0 || target >= len(cs.cachedContexts) {
panic(fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cachedContexts)))
}
cs.cachedContexts = cs.cachedContexts[:target]
}
// RevertAll discards all the cache contexts.
func (cs *ContextStack) RevertAll() {
if len(cs.cachedContexts) > 0 {
cs.RevertToSnapshot(0)
}
}

View File

@ -265,7 +265,7 @@ func (k Keeper) BlockBloom(c context.Context, req *types.QueryBlockBloomRequest)
bloom, found := k.GetBlockBloom(ctx, req.Height) bloom, found := k.GetBlockBloom(ctx, req.Height)
if !found { if !found {
// if the bloom is not found, query the transient store at the current height // if the bloom is not found, query the transient store at the current height
k.ctx = ctx k.WithContext(ctx)
bloomInt := k.GetBlockBloomTransient() bloomInt := k.GetBlockBloomTransient()
if bloomInt.Sign() == 0 { if bloomInt.Sign() == 0 {
@ -381,6 +381,7 @@ func (k Keeper) EthCall(c context.Context, req *types.EthCallRequest) (*types.Ms
evm := k.NewEVM(msg, ethCfg, params, coinbase) evm := k.NewEVM(msg, ethCfg, params, coinbase)
// pass true means execute in query mode, which don't do actual gas refund. // pass true means execute in query mode, which don't do actual gas refund.
res, err := k.ApplyMessage(evm, msg, ethCfg, true) res, err := k.ApplyMessage(evm, msg, ethCfg, true)
k.ctxStack.RevertAll()
if err != nil { if err != nil {
return nil, status.Error(codes.Internal, err.Error()) return nil, status.Error(codes.Internal, err.Error())
} }
@ -443,15 +444,15 @@ func (k Keeper) EstimateGas(c context.Context, req *types.EthCallRequest) (*type
executable := func(gas uint64) (bool, *types.MsgEthereumTxResponse, error) { executable := func(gas uint64) (bool, *types.MsgEthereumTxResponse, error) {
args.Gas = (*hexutil.Uint64)(&gas) args.Gas = (*hexutil.Uint64)(&gas)
// Execute the call in an isolated context // Reset to the initial context
k.BeginCachedContext() k.WithContext(ctx)
msg := args.ToMessage(req.GasCap) msg := args.ToMessage(req.GasCap)
evm := k.NewEVM(msg, ethCfg, params, coinbase) evm := k.NewEVM(msg, ethCfg, params, coinbase)
// pass true means execute in query mode, which don't do actual gas refund. // pass true means execute in query mode, which don't do actual gas refund.
rsp, err := k.ApplyMessage(evm, msg, ethCfg, true) rsp, err := k.ApplyMessage(evm, msg, ethCfg, true)
k.EndCachedContext() k.ctxStack.RevertAll()
if err != nil { if err != nil {
if errors.Is(stacktrace.RootCause(err), core.ErrIntrinsicGas) { if errors.Is(stacktrace.RootCause(err), core.ErrIntrinsicGas) {

View File

@ -40,13 +40,11 @@ type Keeper struct {
// access historical headers for EVM state transition execution // access historical headers for EVM state transition execution
stakingKeeper types.StakingKeeper stakingKeeper types.StakingKeeper
// Context for accessing the store, emit events and log info. // Manage the initial context and cache context stack for accessing the store,
// emit events and log info.
// It is kept as a field to make is accessible by the StateDb // It is kept as a field to make is accessible by the StateDb
// functions. Resets on every transaction/block. // functions. Resets on every transaction/block.
ctx sdk.Context ctxStack ContextStack
// Context of the committed state (before transaction execution).
// Required for StateDB.CommitedState. Set in `BeginCachedContext`.
committedCtx sdk.Context
// chain ID number obtained from the context's chain id // chain ID number obtained from the context's chain id
eip155ChainID *big.Int eip155ChainID *big.Int
@ -86,9 +84,19 @@ func NewKeeper(
} }
} }
// CommittedCtx returns the committed context // Ctx returns the current context from the context stack
func (k Keeper) CommittedCtx() sdk.Context { func (k Keeper) Ctx() sdk.Context {
return k.committedCtx return k.ctxStack.CurrentContext()
}
// CommitCachedContexts commit all the cache contexts created by `StateDB.Snapshot`.
func (k *Keeper) CommitCachedContexts() {
k.ctxStack.Commit()
}
// CachedContextsEmpty returns true if there's no cache contexts.
func (k *Keeper) CachedContextsEmpty() bool {
return k.ctxStack.IsEmpty()
} }
// Logger returns a module-specific logger. // Logger returns a module-specific logger.
@ -96,10 +104,9 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", types.ModuleName) return ctx.Logger().With("module", types.ModuleName)
} }
// WithContext sets an updated SDK context to the keeper // WithContext clears the context stack, and set the initial context.
func (k *Keeper) WithContext(ctx sdk.Context) { func (k *Keeper) WithContext(ctx sdk.Context) {
k.ctx = ctx k.ctxStack.Reset(ctx)
k.committedCtx = ctx
} }
// WithChainID sets the chain id to the local variable in the keeper // WithChainID sets the chain id to the local variable in the keeper
@ -147,8 +154,8 @@ func (k Keeper) SetBlockBloom(ctx sdk.Context, height int64, bloom ethtypes.Bloo
// GetBlockBloomTransient returns bloom bytes for the current block height // GetBlockBloomTransient returns bloom bytes for the current block height
func (k Keeper) GetBlockBloomTransient() *big.Int { func (k Keeper) GetBlockBloomTransient() *big.Int {
store := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientBloom) store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
heightBz := sdk.Uint64ToBigEndian(uint64(k.ctx.BlockHeight())) heightBz := sdk.Uint64ToBigEndian(uint64(k.Ctx().BlockHeight()))
bz := store.Get(heightBz) bz := store.Get(heightBz)
if len(bz) == 0 { if len(bz) == 0 {
return big.NewInt(0) return big.NewInt(0)
@ -160,8 +167,8 @@ func (k Keeper) GetBlockBloomTransient() *big.Int {
// SetBlockBloomTransient sets the given bloom bytes to the transient store. This value is reset on // SetBlockBloomTransient sets the given bloom bytes to the transient store. This value is reset on
// every block. // every block.
func (k Keeper) SetBlockBloomTransient(bloom *big.Int) { func (k Keeper) SetBlockBloomTransient(bloom *big.Int) {
store := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientBloom) store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
heightBz := sdk.Uint64ToBigEndian(uint64(k.ctx.BlockHeight())) heightBz := sdk.Uint64ToBigEndian(uint64(k.Ctx().BlockHeight()))
store.Set(heightBz, bloom.Bytes()) store.Set(heightBz, bloom.Bytes())
} }
@ -171,7 +178,7 @@ func (k Keeper) SetBlockBloomTransient(bloom *big.Int) {
// GetTxHashTransient returns the hash of current processing transaction // GetTxHashTransient returns the hash of current processing transaction
func (k Keeper) GetTxHashTransient() common.Hash { func (k Keeper) GetTxHashTransient() common.Hash {
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientTxHash) bz := store.Get(types.KeyPrefixTransientTxHash)
if len(bz) == 0 { if len(bz) == 0 {
return common.Hash{} return common.Hash{}
@ -182,13 +189,13 @@ func (k Keeper) GetTxHashTransient() common.Hash {
// SetTxHashTransient set the hash of processing transaction // SetTxHashTransient set the hash of processing transaction
func (k Keeper) SetTxHashTransient(hash common.Hash) { func (k Keeper) SetTxHashTransient(hash common.Hash) {
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientTxHash, hash.Bytes()) store.Set(types.KeyPrefixTransientTxHash, hash.Bytes())
} }
// GetTxIndexTransient returns EVM transaction index on the current block. // GetTxIndexTransient returns EVM transaction index on the current block.
func (k Keeper) GetTxIndexTransient() uint64 { func (k Keeper) GetTxIndexTransient() uint64 {
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientTxIndex) bz := store.Get(types.KeyPrefixTransientTxIndex)
if len(bz) == 0 { if len(bz) == 0 {
return 0 return 0
@ -201,7 +208,7 @@ func (k Keeper) GetTxIndexTransient() uint64 {
// value by one and then sets the new index back to the transient store. // value by one and then sets the new index back to the transient store.
func (k Keeper) IncreaseTxIndexTransient() { func (k Keeper) IncreaseTxIndexTransient() {
txIndex := k.GetTxIndexTransient() txIndex := k.GetTxIndexTransient()
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientTxIndex, sdk.Uint64ToBigEndian(txIndex+1)) store.Set(types.KeyPrefixTransientTxIndex, sdk.Uint64ToBigEndian(txIndex+1))
} }
@ -235,7 +242,7 @@ func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
// GetLogs returns the current logs for a given transaction hash from the KVStore. // GetLogs returns the current logs for a given transaction hash from the KVStore.
// This function returns an empty, non-nil slice if no logs are found. // This function returns an empty, non-nil slice if no logs are found.
func (k Keeper) GetTxLogs(txHash common.Hash) []*ethtypes.Log { func (k Keeper) GetTxLogs(txHash common.Hash) []*ethtypes.Log {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixLogs) store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixLogs)
bz := store.Get(txHash.Bytes()) bz := store.Get(txHash.Bytes())
if len(bz) == 0 { if len(bz) == 0 {
@ -250,7 +257,7 @@ func (k Keeper) GetTxLogs(txHash common.Hash) []*ethtypes.Log {
// SetLogs sets the logs for a transaction in the KVStore. // SetLogs sets the logs for a transaction in the KVStore.
func (k Keeper) SetLogs(txHash common.Hash, logs []*ethtypes.Log) { func (k Keeper) SetLogs(txHash common.Hash, logs []*ethtypes.Log) {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixLogs) store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixLogs)
txLogs := types.NewTransactionLogsFromEth(txHash, logs) txLogs := types.NewTransactionLogsFromEth(txHash, logs)
bz := k.cdc.MustMarshal(&txLogs) bz := k.cdc.MustMarshal(&txLogs)
@ -266,7 +273,7 @@ func (k Keeper) DeleteTxLogs(ctx sdk.Context, txHash common.Hash) {
// GetLogSizeTransient returns EVM log index on the current block. // GetLogSizeTransient returns EVM log index on the current block.
func (k Keeper) GetLogSizeTransient() uint64 { func (k Keeper) GetLogSizeTransient() uint64 {
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientLogSize) bz := store.Get(types.KeyPrefixTransientLogSize)
if len(bz) == 0 { if len(bz) == 0 {
return 0 return 0
@ -279,7 +286,7 @@ func (k Keeper) GetLogSizeTransient() uint64 {
// value by one and then sets the new index back to the transient store. // value by one and then sets the new index back to the transient store.
func (k Keeper) IncreaseLogSizeTransient() { func (k Keeper) IncreaseLogSizeTransient() {
logSize := k.GetLogSizeTransient() logSize := k.GetLogSizeTransient()
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientLogSize, sdk.Uint64ToBigEndian(logSize+1)) store.Set(types.KeyPrefixTransientLogSize, sdk.Uint64ToBigEndian(logSize+1))
} }
@ -308,7 +315,7 @@ func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (type
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
func (k Keeper) DeleteState(addr common.Address, key common.Hash) { func (k Keeper) DeleteState(addr common.Address, key common.Hash) {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.AddressStoragePrefix(addr))
key = types.KeyAddressStorage(addr, key) key = types.KeyAddressStorage(addr, key)
store.Delete(key.Bytes()) store.Delete(key.Bytes())
} }
@ -329,22 +336,22 @@ func (k Keeper) DeleteCode(addr common.Address) {
return return
} }
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode) store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixCode)
store.Delete(hash.Bytes()) store.Delete(hash.Bytes())
} }
// ClearBalance subtracts the EVM all the balance denomination from the address // ClearBalance subtracts the EVM all the balance denomination from the address
// balance while also updating the total supply. // balance while also updating the total supply.
func (k Keeper) ClearBalance(addr sdk.AccAddress) (prevBalance sdk.Coin, err error) { func (k Keeper) ClearBalance(addr sdk.AccAddress) (prevBalance sdk.Coin, err error) {
params := k.GetParams(k.ctx) params := k.GetParams(k.Ctx())
prevBalance = k.bankKeeper.GetBalance(k.ctx, addr, params.EvmDenom) prevBalance = k.bankKeeper.GetBalance(k.Ctx(), addr, params.EvmDenom)
if prevBalance.IsPositive() { if prevBalance.IsPositive() {
if err := k.bankKeeper.SendCoinsFromAccountToModule(k.ctx, addr, types.ModuleName, sdk.Coins{prevBalance}); err != nil { if err := k.bankKeeper.SendCoinsFromAccountToModule(k.Ctx(), addr, types.ModuleName, sdk.Coins{prevBalance}); err != nil {
return sdk.Coin{}, stacktrace.Propagate(err, "failed to transfer to module account") return sdk.Coin{}, stacktrace.Propagate(err, "failed to transfer to module account")
} }
if err := k.bankKeeper.BurnCoins(k.ctx, types.ModuleName, sdk.Coins{prevBalance}); err != nil { if err := k.bankKeeper.BurnCoins(k.Ctx(), types.ModuleName, sdk.Coins{prevBalance}); err != nil {
return sdk.Coin{}, stacktrace.Propagate(err, "failed to burn coins from evm module account") return sdk.Coin{}, stacktrace.Propagate(err, "failed to burn coins from evm module account")
} }
} }
@ -358,15 +365,3 @@ func (k Keeper) ResetAccount(addr common.Address) {
k.DeleteCode(addr) k.DeleteCode(addr)
k.DeleteAccountStorage(addr) k.DeleteAccountStorage(addr)
} }
// BeginCachedContext create the cached context
func (k *Keeper) BeginCachedContext() (commit func()) {
k.committedCtx = k.ctx
k.ctx, commit = k.ctx.CacheContext()
return
}
// EndCachedContext recover the committed context
func (k *Keeper) EndCachedContext() {
k.ctx = k.committedCtx
}

View File

@ -34,9 +34,9 @@ func (k *Keeper) NewEVM(msg core.Message, config *params.ChainConfig, params typ
Transfer: core.Transfer, Transfer: core.Transfer,
GetHash: k.GetHashFn(), GetHash: k.GetHashFn(),
Coinbase: coinbase, Coinbase: coinbase,
GasLimit: ethermint.BlockGasLimit(k.ctx), GasLimit: ethermint.BlockGasLimit(k.Ctx()),
BlockNumber: big.NewInt(k.ctx.BlockHeight()), BlockNumber: big.NewInt(k.Ctx().BlockHeight()),
Time: big.NewInt(k.ctx.BlockHeader().Time.Unix()), Time: big.NewInt(k.Ctx().BlockHeader().Time.Unix()),
Difficulty: big.NewInt(0), // unused. Only required in PoW context Difficulty: big.NewInt(0), // unused. Only required in PoW context
} }
@ -65,38 +65,38 @@ func (k Keeper) GetHashFn() vm.GetHashFunc {
return func(height uint64) common.Hash { return func(height uint64) common.Hash {
h := int64(height) h := int64(height)
switch { switch {
case k.ctx.BlockHeight() == h: case k.Ctx().BlockHeight() == h:
// Case 1: The requested height matches the one from the context so we can retrieve the header // Case 1: The requested height matches the one from the context so we can retrieve the header
// hash directly from the context. // hash directly from the context.
// Note: The headerHash is only set at begin block, it will be nil in case of a query context // Note: The headerHash is only set at begin block, it will be nil in case of a query context
headerHash := k.ctx.HeaderHash() headerHash := k.Ctx().HeaderHash()
if len(headerHash) != 0 { if len(headerHash) != 0 {
return common.BytesToHash(headerHash) return common.BytesToHash(headerHash)
} }
// only recompute the hash if not set (eg: checkTxState) // only recompute the hash if not set (eg: checkTxState)
contextBlockHeader := k.ctx.BlockHeader() contextBlockHeader := k.Ctx().BlockHeader()
header, err := tmtypes.HeaderFromProto(&contextBlockHeader) header, err := tmtypes.HeaderFromProto(&contextBlockHeader)
if err != nil { if err != nil {
k.Logger(k.ctx).Error("failed to cast tendermint header from proto", "error", err) k.Logger(k.Ctx()).Error("failed to cast tendermint header from proto", "error", err)
return common.Hash{} return common.Hash{}
} }
headerHash = header.Hash() headerHash = header.Hash()
return common.BytesToHash(headerHash) return common.BytesToHash(headerHash)
case k.ctx.BlockHeight() > h: case k.Ctx().BlockHeight() > h:
// Case 2: if the chain is not the current height we need to retrieve the hash from the store for the // Case 2: if the chain is not the current height we need to retrieve the hash from the store for the
// current chain epoch. This only applies if the current height is greater than the requested height. // current chain epoch. This only applies if the current height is greater than the requested height.
histInfo, found := k.stakingKeeper.GetHistoricalInfo(k.ctx, h) histInfo, found := k.stakingKeeper.GetHistoricalInfo(k.Ctx(), h)
if !found { if !found {
k.Logger(k.ctx).Debug("historical info not found", "height", h) k.Logger(k.Ctx()).Debug("historical info not found", "height", h)
return common.Hash{} return common.Hash{}
} }
header, err := tmtypes.HeaderFromProto(&histInfo.Header) header, err := tmtypes.HeaderFromProto(&histInfo.Header)
if err != nil { if err != nil {
k.Logger(k.ctx).Error("failed to cast tendermint header from proto", "error", err) k.Logger(k.Ctx()).Error("failed to cast tendermint header from proto", "error", err)
return common.Hash{} return common.Hash{}
} }
@ -128,20 +128,17 @@ func (k Keeper) GetHashFn() vm.GetHashFunc {
func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) { func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricKeyTransitionDB) defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricKeyTransitionDB)
params := k.GetParams(k.ctx) params := k.GetParams(k.Ctx())
ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID) ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID)
// get the latest signer according to the chain rules from the config // get the latest signer according to the chain rules from the config
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(k.ctx.BlockHeight())) signer := ethtypes.MakeSigner(ethCfg, big.NewInt(k.Ctx().BlockHeight()))
msg, err := tx.AsMessage(signer) msg, err := tx.AsMessage(signer)
if err != nil { if err != nil {
return nil, stacktrace.Propagate(err, "failed to return ethereum transaction as core message") return nil, stacktrace.Propagate(err, "failed to return ethereum transaction as core message")
} }
// we use a cached context to avoid modifying to state in case EVM msg is reverted
commit := k.BeginCachedContext()
// get the coinbase address from the block proposer // get the coinbase address from the block proposer
coinbase, err := k.GetCoinbaseAddress() coinbase, err := k.GetCoinbaseAddress()
if err != nil { if err != nil {
@ -158,6 +155,10 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
k.SetTxHashTransient(txHash) k.SetTxHashTransient(txHash)
k.IncreaseTxIndexTransient() k.IncreaseTxIndexTransient()
if !k.ctxStack.IsEmpty() {
panic("context stack shouldn't be dirty before apply message")
}
// 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 {
@ -166,26 +167,18 @@ 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 len(logs) > 0 {
// Commit and switch to committed context
if !res.Failed() {
// keep the cosmos events emitted in the cache context
k.committedCtx.EventManager().EmitEvents(k.ctx.EventManager().Events())
commit()
}
k.EndCachedContext()
// Logs needs to be ignored when tx is reverted
// Set the log and bloom filter only when the tx is NOT REVERTED
if !res.Failed() {
res.Logs = types.NewLogsFromEth(logs) res.Logs = types.NewLogsFromEth(logs)
// Update block bloom filter in the original context because blockbloom is set in EndBlock // Update transient block bloom filter
bloom := k.GetBlockBloomTransient() bloom := k.GetBlockBloomTransient()
bloom.Or(bloom, big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs))) bloom.Or(bloom, big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs)))
k.SetBlockBloomTransient(bloom) k.SetBlockBloomTransient(bloom)
} }
// Since we've implemented `RevertToSnapshot` api, so for the vm error cases,
// the state is reverted, so it's ok to call the commit here anyway.
k.CommitCachedContexts()
// update the gas used after refund // update the gas used after refund
k.resetGasMeterAndConsumeGas(res.GasUsed) k.resetGasMeterAndConsumeGas(res.GasUsed)
return res, nil return res, nil
@ -292,7 +285,7 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainCo
// GetEthIntrinsicGas returns the intrinsic gas cost for the transaction // GetEthIntrinsicGas returns the intrinsic gas cost for the transaction
func (k *Keeper) GetEthIntrinsicGas(msg core.Message, cfg *params.ChainConfig, isContractCreation bool) (uint64, error) { func (k *Keeper) GetEthIntrinsicGas(msg core.Message, cfg *params.ChainConfig, isContractCreation bool) (uint64, error) {
height := big.NewInt(k.ctx.BlockHeight()) height := big.NewInt(k.Ctx().BlockHeight())
homestead := cfg.IsHomestead(height) homestead := cfg.IsHomestead(height)
istanbul := cfg.IsIstanbul(height) istanbul := cfg.IsIstanbul(height)
@ -347,12 +340,12 @@ func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64) (uint64, error)
return leftoverGas, sdkerrors.Wrapf(types.ErrInvalidRefund, "refunded amount value cannot be negative %d", remaining.Int64()) return leftoverGas, sdkerrors.Wrapf(types.ErrInvalidRefund, "refunded amount value cannot be negative %d", remaining.Int64())
case 1: case 1:
// positive amount refund // positive amount refund
params := k.GetParams(k.ctx) params := k.GetParams(k.Ctx())
refundedCoins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(remaining))} refundedCoins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(remaining))}
// refund to sender from the fee collector module account, which is the escrow account in charge of collecting tx fees // refund to sender from the fee collector module account, which is the escrow account in charge of collecting tx fees
err := k.bankKeeper.SendCoinsFromModuleToAccount(k.ctx, authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins) err := k.bankKeeper.SendCoinsFromModuleToAccount(k.Ctx(), authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins)
if err != nil { if err != nil {
err = sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "fee collector account failed to refund fees: %s", err.Error()) err = sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "fee collector account failed to refund fees: %s", err.Error())
return leftoverGas, stacktrace.Propagate(err, "failed to refund %d leftover gas (%s)", leftoverGas, refundedCoins.String()) return leftoverGas, stacktrace.Propagate(err, "failed to refund %d leftover gas (%s)", leftoverGas, refundedCoins.String())
@ -368,14 +361,14 @@ func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64) (uint64, error)
// 'gasUsed' // 'gasUsed'
func (k *Keeper) resetGasMeterAndConsumeGas(gasUsed uint64) { func (k *Keeper) resetGasMeterAndConsumeGas(gasUsed uint64) {
// reset the gas count // reset the gas count
k.ctx.GasMeter().RefundGas(k.ctx.GasMeter().GasConsumed(), "reset the gas count") k.Ctx().GasMeter().RefundGas(k.Ctx().GasMeter().GasConsumed(), "reset the gas count")
k.ctx.GasMeter().ConsumeGas(gasUsed, "apply evm transaction") k.Ctx().GasMeter().ConsumeGas(gasUsed, "apply evm transaction")
} }
// GetCoinbaseAddress returns the block proposer's validator operator address. // GetCoinbaseAddress returns the block proposer's validator operator address.
func (k Keeper) GetCoinbaseAddress() (common.Address, error) { func (k Keeper) GetCoinbaseAddress() (common.Address, error) {
consAddr := sdk.ConsAddress(k.ctx.BlockHeader().ProposerAddress) consAddr := sdk.ConsAddress(k.Ctx().BlockHeader().ProposerAddress)
validator, found := k.stakingKeeper.GetValidatorByConsAddr(k.ctx, consAddr) validator, found := k.stakingKeeper.GetValidatorByConsAddr(k.Ctx(), consAddr)
if !found { if !found {
return common.Address{}, stacktrace.Propagate( return common.Address{}, stacktrace.Propagate(
sdkerrors.Wrap(stakingtypes.ErrNoValidatorFound, consAddr.String()), sdkerrors.Wrap(stakingtypes.ErrNoValidatorFound, consAddr.String()),

View File

@ -30,7 +30,7 @@ var _ vm.StateDB = &Keeper{}
func (k *Keeper) CreateAccount(addr common.Address) { func (k *Keeper) CreateAccount(addr common.Address) {
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr)
log := "" log := ""
if account == nil { if account == nil {
log = "account created" log = "account created"
@ -39,10 +39,10 @@ func (k *Keeper) CreateAccount(addr common.Address) {
k.ResetAccount(addr) k.ResetAccount(addr)
} }
account = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr) account = k.accountKeeper.NewAccountWithAddress(k.Ctx(), cosmosAddr)
k.accountKeeper.SetAccount(k.ctx, account) k.accountKeeper.SetAccount(k.Ctx(), account)
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
log, log,
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -58,7 +58,7 @@ func (k *Keeper) CreateAccount(addr common.Address) {
// from the module parameters. // from the module parameters.
func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
if amount.Sign() != 1 { if amount.Sign() != 1 {
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"ignored non-positive amount addition", "ignored non-positive amount addition",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"amount", amount.Int64(), "amount", amount.Int64(),
@ -68,11 +68,11 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(k.ctx) params := k.GetParams(k.Ctx())
coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))} coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))}
if err := k.bankKeeper.MintCoins(k.ctx, types.ModuleName, coins); err != nil { if err := k.bankKeeper.MintCoins(k.Ctx(), types.ModuleName, coins); err != nil {
k.Logger(k.ctx).Error( k.Logger(k.Ctx()).Error(
"failed to mint coins when adding balance", "failed to mint coins when adding balance",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -81,8 +81,8 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
return return
} }
if err := k.bankKeeper.SendCoinsFromModuleToAccount(k.ctx, types.ModuleName, cosmosAddr, coins); err != nil { if err := k.bankKeeper.SendCoinsFromModuleToAccount(k.Ctx(), types.ModuleName, cosmosAddr, coins); err != nil {
k.Logger(k.ctx).Error( k.Logger(k.Ctx()).Error(
"failed to send from module to account when adding balance", "failed to send from module to account when adding balance",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -91,7 +91,7 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
return return
} }
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"balance addition", "balance addition",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -104,7 +104,7 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
// or the user doesn't have enough funds for the transfer. // or the user doesn't have enough funds for the transfer.
func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) { func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
if amount.Sign() != 1 { if amount.Sign() != 1 {
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"ignored non-positive amount addition", "ignored non-positive amount addition",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"amount", amount.Int64(), "amount", amount.Int64(),
@ -114,11 +114,11 @@ func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(k.ctx) params := k.GetParams(k.Ctx())
coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))} coins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(amount))}
if err := k.bankKeeper.SendCoinsFromAccountToModule(k.ctx, cosmosAddr, types.ModuleName, coins); err != nil { if err := k.bankKeeper.SendCoinsFromAccountToModule(k.Ctx(), cosmosAddr, types.ModuleName, coins); err != nil {
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"failed to send from account to module when subtracting balance", "failed to send from account to module when subtracting balance",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -128,8 +128,8 @@ func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
return return
} }
if err := k.bankKeeper.BurnCoins(k.ctx, types.ModuleName, coins); err != nil { if err := k.bankKeeper.BurnCoins(k.Ctx(), types.ModuleName, coins); err != nil {
k.Logger(k.ctx).Error( k.Logger(k.Ctx()).Error(
"failed to burn coins when subtracting balance", "failed to burn coins when subtracting balance",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -138,7 +138,7 @@ func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
return return
} }
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"balance subtraction", "balance subtraction",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -149,8 +149,8 @@ func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
// denomination is obtained from the module parameters. // denomination is obtained from the module parameters.
func (k *Keeper) GetBalance(addr common.Address) *big.Int { func (k *Keeper) GetBalance(addr common.Address) *big.Int {
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(k.ctx) params := k.GetParams(k.Ctx())
balance := k.bankKeeper.GetBalance(k.ctx, cosmosAddr, params.EvmDenom) balance := k.bankKeeper.GetBalance(k.Ctx(), cosmosAddr, params.EvmDenom)
return balance.Amount.BigInt() return balance.Amount.BigInt()
} }
@ -163,9 +163,9 @@ func (k *Keeper) GetBalance(addr common.Address) *big.Int {
// sequence (i.e nonce). The function performs a no-op if the account is not found. // sequence (i.e nonce). The function performs a no-op if the account is not found.
func (k *Keeper) GetNonce(addr common.Address) uint64 { func (k *Keeper) GetNonce(addr common.Address) uint64 {
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
nonce, err := k.accountKeeper.GetSequence(k.ctx, cosmosAddr) nonce, err := k.accountKeeper.GetSequence(k.Ctx(), cosmosAddr)
if err != nil { if err != nil {
k.Logger(k.ctx).Error( k.Logger(k.Ctx()).Error(
"account not found", "account not found",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -180,20 +180,20 @@ func (k *Keeper) GetNonce(addr common.Address) uint64 {
// account doesn't exist, a new one will be created from the address. // account doesn't exist, a new one will be created from the address.
func (k *Keeper) SetNonce(addr common.Address, nonce uint64) { func (k *Keeper) SetNonce(addr common.Address, nonce uint64) {
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr)
if account == nil { if account == nil {
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"account not found", "account not found",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
) )
// create address if it doesn't exist // create address if it doesn't exist
account = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr) account = k.accountKeeper.NewAccountWithAddress(k.Ctx(), cosmosAddr)
} }
if err := account.SetSequence(nonce); err != nil { if err := account.SetSequence(nonce); err != nil {
k.Logger(k.ctx).Error( k.Logger(k.Ctx()).Error(
"failed to set nonce", "failed to set nonce",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -204,9 +204,9 @@ func (k *Keeper) SetNonce(addr common.Address, nonce uint64) {
return return
} }
k.accountKeeper.SetAccount(k.ctx, account) k.accountKeeper.SetAccount(k.Ctx(), account)
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"nonce set", "nonce set",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -222,7 +222,7 @@ func (k *Keeper) SetNonce(addr common.Address, nonce uint64) {
// exist or is not an EthAccount type, GetCodeHash returns the empty code hash value. // exist or is not an EthAccount type, GetCodeHash returns the empty code hash value.
func (k *Keeper) GetCodeHash(addr common.Address) common.Hash { func (k *Keeper) GetCodeHash(addr common.Address) common.Hash {
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr)
if account == nil { if account == nil {
return common.BytesToHash(types.EmptyCodeHash) return common.BytesToHash(types.EmptyCodeHash)
} }
@ -244,11 +244,11 @@ func (k *Keeper) GetCode(addr common.Address) []byte {
return nil return nil
} }
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode) store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixCode)
code := store.Get(hash.Bytes()) code := store.Get(hash.Bytes())
if len(code) == 0 { if len(code) == 0 {
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"code not found", "code not found",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"code-hash", hash.Hex(), "code-hash", hash.Hex(),
@ -262,20 +262,20 @@ func (k *Keeper) GetCode(addr common.Address) []byte {
// code hash to the given account. The code is deleted from the store if it is empty. // code hash to the given account. The code is deleted from the store if it is empty.
func (k *Keeper) SetCode(addr common.Address, code []byte) { func (k *Keeper) SetCode(addr common.Address, code []byte) {
if bytes.Equal(code, types.EmptyCodeHash) { if bytes.Equal(code, types.EmptyCodeHash) {
k.Logger(k.ctx).Debug("passed in EmptyCodeHash, but expected empty code") k.Logger(k.Ctx()).Debug("passed in EmptyCodeHash, but expected empty code")
} }
hash := crypto.Keccak256Hash(code) hash := crypto.Keccak256Hash(code)
// update account code hash // update account code hash
account := k.accountKeeper.GetAccount(k.ctx, addr.Bytes()) account := k.accountKeeper.GetAccount(k.Ctx(), addr.Bytes())
if account == nil { if account == nil {
account = k.accountKeeper.NewAccountWithAddress(k.ctx, addr.Bytes()) account = k.accountKeeper.NewAccountWithAddress(k.Ctx(), addr.Bytes())
k.accountKeeper.SetAccount(k.ctx, account) k.accountKeeper.SetAccount(k.Ctx(), account)
} }
ethAccount, isEthAccount := account.(*ethermint.EthAccount) ethAccount, isEthAccount := account.(*ethermint.EthAccount)
if !isEthAccount { if !isEthAccount {
k.Logger(k.ctx).Error( k.Logger(k.Ctx()).Error(
"invalid account type", "invalid account type",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"code-hash", hash.Hex(), "code-hash", hash.Hex(),
@ -284,9 +284,9 @@ func (k *Keeper) SetCode(addr common.Address, code []byte) {
} }
ethAccount.CodeHash = hash.Hex() ethAccount.CodeHash = hash.Hex()
k.accountKeeper.SetAccount(k.ctx, ethAccount) k.accountKeeper.SetAccount(k.Ctx(), ethAccount)
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.KeyPrefixCode) store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixCode)
action := "updated" action := "updated"
@ -298,7 +298,7 @@ func (k *Keeper) SetCode(addr common.Address, code []byte) {
store.Set(hash.Bytes(), code) store.Set(hash.Bytes(), code)
} }
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
fmt.Sprintf("code %s", action), fmt.Sprintf("code %s", action),
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"code-hash", hash.Hex(), "code-hash", hash.Hex(),
@ -327,7 +327,7 @@ func (k *Keeper) AddRefund(gas uint64) {
refund += gas refund += gas
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund)) store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund))
} }
@ -343,14 +343,14 @@ func (k *Keeper) SubRefund(gas uint64) {
refund -= gas refund -= gas
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund)) store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund))
} }
// GetRefund returns the amount of gas available for return after the tx execution // GetRefund returns the amount of gas available for return after the tx execution
// finalizes. This value is reset to 0 on every transaction. // finalizes. This value is reset to 0 on every transaction.
func (k *Keeper) GetRefund() uint64 { func (k *Keeper) GetRefund() uint64 {
store := k.ctx.TransientStore(k.transientKey) store := k.Ctx().TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientRefund) bz := store.Get(types.KeyPrefixTransientRefund)
if len(bz) == 0 { if len(bz) == 0 {
@ -379,19 +379,19 @@ func doGetState(ctx sdk.Context, storeKey sdk.StoreKey, addr common.Address, has
// GetCommittedState returns the value set in store for the given key hash. If the key is not registered // GetCommittedState returns the value set in store for the given key hash. If the key is not registered
// this function returns the empty hash. // this function returns the empty hash.
func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
return doGetState(k.committedCtx, k.storeKey, addr, hash) return doGetState(k.ctxStack.initialCtx, k.storeKey, addr, hash)
} }
// GetState returns the committed state for the given key hash, as all changes are committed directly // GetState returns the committed state for the given key hash, as all changes are committed directly
// to the KVStore. // to the KVStore.
func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash { func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash {
return doGetState(k.ctx, k.storeKey, addr, hash) return doGetState(k.Ctx(), k.storeKey, addr, hash)
} }
// SetState sets the given hashes (key, value) to the KVStore. If the value hash is empty, this // SetState sets the given hashes (key, value) to the KVStore. If the value hash is empty, this
// function deletes the key from the store. // function deletes the key from the store.
func (k *Keeper) SetState(addr common.Address, key, value common.Hash) { func (k *Keeper) SetState(addr common.Address, key, value common.Hash) {
store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.AddressStoragePrefix(addr))
key = types.KeyAddressStorage(addr, key) key = types.KeyAddressStorage(addr, key)
action := "updated" action := "updated"
@ -402,7 +402,7 @@ func (k *Keeper) SetState(addr common.Address, key, value common.Hash) {
store.Set(key.Bytes(), value.Bytes()) store.Set(key.Bytes(), value.Bytes())
} }
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
fmt.Sprintf("state %s", action), fmt.Sprintf("state %s", action),
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"key", key.Hex(), "key", key.Hex(),
@ -425,7 +425,7 @@ func (k *Keeper) Suicide(addr common.Address) bool {
_, err := k.ClearBalance(cosmosAddr) _, err := k.ClearBalance(cosmosAddr)
if err != nil { if err != nil {
k.Logger(k.ctx).Error( k.Logger(k.Ctx()).Error(
"failed to subtract balance on suicide", "failed to subtract balance on suicide",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -438,10 +438,10 @@ func (k *Keeper) Suicide(addr common.Address) bool {
// TODO: (@fedekunze) do we also need to delete the storage state and the code? // TODO: (@fedekunze) do we also need to delete the storage state and the code?
// Set a single byte to the transient store // Set a single byte to the transient store
store := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided) store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientSuicided)
store.Set(addr.Bytes(), []byte{1}) store.Set(addr.Bytes(), []byte{1})
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"account suicided", "account suicided",
"ethereum-address", addr.Hex(), "ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(), "cosmos-address", cosmosAddr.String(),
@ -454,7 +454,7 @@ func (k *Keeper) Suicide(addr common.Address) bool {
// current block. Accounts that are suicided will be returned as non-nil during queries and "cleared" // current block. Accounts that are suicided will be returned as non-nil during queries and "cleared"
// after the block has been committed. // after the block has been committed.
func (k *Keeper) HasSuicided(addr common.Address) bool { func (k *Keeper) HasSuicided(addr common.Address) bool {
store := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided) store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientSuicided)
return store.Has(addr.Bytes()) return store.Has(addr.Bytes())
} }
@ -471,7 +471,7 @@ func (k *Keeper) Exist(addr common.Address) bool {
} }
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr)
return account != nil return account != nil
} }
@ -486,7 +486,7 @@ func (k *Keeper) Empty(addr common.Address) bool {
codeHash := types.EmptyCodeHash codeHash := types.EmptyCodeHash
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) account := k.accountKeeper.GetAccount(k.Ctx(), cosmosAddr)
if account != nil { if account != nil {
nonce = account.GetSequence() nonce = account.GetSequence()
@ -537,7 +537,7 @@ func (k *Keeper) PrepareAccessList(sender common.Address, dest *common.Address,
// AddressInAccessList returns true if the address is registered on the transient store. // AddressInAccessList returns true if the address is registered on the transient store.
func (k *Keeper) AddressInAccessList(addr common.Address) bool { func (k *Keeper) AddressInAccessList(addr common.Address) bool {
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress) ts := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
return ts.Has(addr.Bytes()) return ts.Has(addr.Bytes())
} }
@ -550,7 +550,7 @@ func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addres
// addressSlotInAccessList returns true if the address's slot is registered on the transient store. // addressSlotInAccessList returns true if the address's slot is registered on the transient store.
func (k *Keeper) addressSlotInAccessList(addr common.Address, slot common.Hash) bool { func (k *Keeper) addressSlotInAccessList(addr common.Address, slot common.Hash) bool {
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot) ts := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
key := append(addr.Bytes(), slot.Bytes()...) key := append(addr.Bytes(), slot.Bytes()...)
return ts.Has(key) return ts.Has(key)
} }
@ -562,7 +562,7 @@ func (k *Keeper) AddAddressToAccessList(addr common.Address) {
return return
} }
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress) ts := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
ts.Set(addr.Bytes(), []byte{0x1}) ts.Set(addr.Bytes(), []byte{0x1})
} }
@ -574,7 +574,7 @@ func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) {
return return
} }
ts := prefix.NewStore(k.ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot) ts := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
key := append(addr.Bytes(), slot.Bytes()...) key := append(addr.Bytes(), slot.Bytes()...)
ts.Set(key, []byte{0x1}) ts.Set(key, []byte{0x1})
} }
@ -583,16 +583,15 @@ func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) {
// Snapshotting // Snapshotting
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Snapshot return zero as the state changes won't be committed if the state transition fails. So there // Snapshot return the index in the cached context stack
// is no need to snapshot before the VM execution.
// See Cosmos SDK docs for more info: https://docs.cosmos.network/master/core/baseapp.html#delivertx-state-updates
func (k *Keeper) Snapshot() int { func (k *Keeper) Snapshot() int {
return 0 return k.ctxStack.Snapshot()
} }
// RevertToSnapshot performs a no-op because when a transaction execution fails on the EVM, the state // RevertToSnapshot pop all the cached contexts after(including) the snapshot
// won't be persisted during ABCI DeliverTx. func (k *Keeper) RevertToSnapshot(target int) {
func (k *Keeper) RevertToSnapshot(_ int) {} k.ctxStack.RevertToSnapshot(target)
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Log // Log
@ -602,7 +601,7 @@ func (k *Keeper) RevertToSnapshot(_ int) {}
// context. This function also fills in the tx hash, block hash, tx index and log index fields before setting the log // context. This function also fills in the tx hash, block hash, tx index and log index fields before setting the log
// to store. // to store.
func (k *Keeper) AddLog(log *ethtypes.Log) { func (k *Keeper) AddLog(log *ethtypes.Log) {
log.BlockHash = common.BytesToHash(k.ctx.HeaderHash()) log.BlockHash = common.BytesToHash(k.Ctx().HeaderHash())
log.TxIndex = uint(k.GetTxIndexTransient()) log.TxIndex = uint(k.GetTxIndexTransient())
log.TxHash = k.GetTxHashTransient() log.TxHash = k.GetTxHashTransient()
@ -614,7 +613,7 @@ func (k *Keeper) AddLog(log *ethtypes.Log) {
k.SetLogs(log.TxHash, logs) k.SetLogs(log.TxHash, logs)
k.Logger(k.ctx).Debug( k.Logger(k.Ctx()).Debug(
"log added", "log added",
"tx-hash-ethereum", log.TxHash.Hex(), "tx-hash-ethereum", log.TxHash.Hex(),
"log-index", int(log.Index), "log-index", int(log.Index),
@ -637,7 +636,7 @@ func (k *Keeper) AddPreimage(_ common.Hash, _ []byte) {}
// ForEachStorage uses the store iterator to iterate over all the state keys and perform a callback // ForEachStorage uses the store iterator to iterate over all the state keys and perform a callback
// function on each of them. // function on each of them.
func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
store := k.ctx.KVStore(k.storeKey) store := k.Ctx().KVStore(k.storeKey)
prefix := types.AddressStoragePrefix(addr) prefix := types.AddressStoragePrefix(addr)
iterator := sdk.KVStorePrefixIterator(store, prefix) iterator := sdk.KVStorePrefixIterator(store, prefix)

View File

@ -392,7 +392,7 @@ func (suite *KeeperTestSuite) TestCommittedState() {
suite.app.EvmKeeper.SetState(suite.address, key, value1) suite.app.EvmKeeper.SetState(suite.address, key, value1)
commit := suite.app.EvmKeeper.BeginCachedContext() suite.app.EvmKeeper.Snapshot()
suite.app.EvmKeeper.SetState(suite.address, key, value2) suite.app.EvmKeeper.SetState(suite.address, key, value2)
tmp := suite.app.EvmKeeper.GetState(suite.address, key) tmp := suite.app.EvmKeeper.GetState(suite.address, key)
@ -400,8 +400,7 @@ func (suite *KeeperTestSuite) TestCommittedState() {
tmp = suite.app.EvmKeeper.GetCommittedState(suite.address, key) tmp = suite.app.EvmKeeper.GetCommittedState(suite.address, key)
suite.Require().Equal(value1, tmp) suite.Require().Equal(value1, tmp)
commit() suite.app.EvmKeeper.CommitCachedContexts()
suite.app.EvmKeeper.EndCachedContext()
tmp = suite.app.EvmKeeper.GetCommittedState(suite.address, key) tmp = suite.app.EvmKeeper.GetCommittedState(suite.address, key)
suite.Require().Equal(value2, tmp) suite.Require().Equal(value2, tmp)
@ -476,9 +475,62 @@ func (suite *KeeperTestSuite) TestEmpty() {
} }
func (suite *KeeperTestSuite) TestSnapshot() { func (suite *KeeperTestSuite) TestSnapshot() {
revision := suite.app.EvmKeeper.Snapshot()
suite.Require().Zero(revision) var key = common.BytesToHash([]byte("key"))
suite.app.EvmKeeper.RevertToSnapshot(revision) // no-op var value1 = common.BytesToHash([]byte("value1"))
var value2 = common.BytesToHash([]byte("value2"))
testCases := []struct {
name string
malleate func()
}{
{"simple revert", func() {
revision := suite.app.EvmKeeper.Snapshot()
suite.Require().Zero(revision)
suite.app.EvmKeeper.SetState(suite.address, key, value1)
suite.Require().Equal(value1, suite.app.EvmKeeper.GetState(suite.address, key))
suite.app.EvmKeeper.RevertToSnapshot(revision)
// reverted
suite.Require().Equal(common.Hash{}, suite.app.EvmKeeper.GetState(suite.address, key))
}},
{"nested snapshot/revert", func() {
revision1 := suite.app.EvmKeeper.Snapshot()
suite.Require().Zero(revision1)
suite.app.EvmKeeper.SetState(suite.address, key, value1)
revision2 := suite.app.EvmKeeper.Snapshot()
suite.app.EvmKeeper.SetState(suite.address, key, value2)
suite.Require().Equal(value2, suite.app.EvmKeeper.GetState(suite.address, key))
suite.app.EvmKeeper.RevertToSnapshot(revision2)
suite.Require().Equal(value1, suite.app.EvmKeeper.GetState(suite.address, key))
suite.app.EvmKeeper.RevertToSnapshot(revision1)
suite.Require().Equal(common.Hash{}, suite.app.EvmKeeper.GetState(suite.address, key))
}},
{"jump revert", func() {
revision1 := suite.app.EvmKeeper.Snapshot()
suite.app.EvmKeeper.SetState(suite.address, key, value1)
suite.app.EvmKeeper.Snapshot()
suite.app.EvmKeeper.SetState(suite.address, key, value2)
suite.app.EvmKeeper.RevertToSnapshot(revision1)
suite.Require().Equal(common.Hash{}, suite.app.EvmKeeper.GetState(suite.address, key))
}},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()
tc.malleate()
// the test case should finish in clean state
suite.Require().True(suite.app.EvmKeeper.CachedContextsEmpty())
})
}
} }
func (suite *KeeperTestSuite) CreateTestTx(msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey) authsigning.Tx { func (suite *KeeperTestSuite) CreateTestTx(msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey) authsigning.Tx {