laconicd/x/evm/keeper/keeper.go
yihuang 7d8664043e
impr: support batch eth txs (#901)
* support batch eth tx

Closes: 896

Allow multiple MsgEthereumTx in single tx

* fix transaction receipt api

* fix tx receipt api and accumulate tx gas used

* fix lint

* fix test

* fix rpc test

* cleanup

* fix cumulativeGasUsed and gasUsed

* fix lint

* Update app/ante/eth.go

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

* Update app/ante/eth.go

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

* Update rpc/ethereum/backend/utils.go

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

* pr suggestions

* typo

* fix lint

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
2022-01-14 10:37:33 +01:00

338 lines
11 KiB
Go

package keeper
import (
"math/big"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"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/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/tendermint/tendermint/libs/log"
ethermint "github.com/tharsis/ethermint/types"
"github.com/tharsis/ethermint/x/evm/statedb"
"github.com/tharsis/ethermint/x/evm/types"
)
// Keeper grants access to the EVM module state and implements the go-ethereum StateDB interface.
type Keeper struct {
// Protobuf codec
cdc codec.BinaryCodec
// Store key required for the EVM Prefix KVStore. It is required by:
// - storing account's Storage State
// - storing account's Code
// - storing transaction Logs
// - storing Bloom filters by block height. Needed for the Web3 API.
storeKey sdk.StoreKey
// key to access the transient store, which is reset on every block during Commit
transientKey sdk.StoreKey
// module specific parameter space that can be configured through governance
paramSpace paramtypes.Subspace
// access to account state
accountKeeper types.AccountKeeper
// update balance and accounting operations with coins
bankKeeper types.BankKeeper
// access historical headers for EVM state transition execution
stakingKeeper types.StakingKeeper
// fetch EIP1559 base fee and parameters
feeMarketKeeper types.FeeMarketKeeper
// chain ID number obtained from the context's chain id
eip155ChainID *big.Int
// Tracer used to collect execution traces from the EVM transaction execution
tracer string
// EVM Hooks for tx post-processing
hooks types.EvmHooks
}
// NewKeeper generates new evm module keeper
func NewKeeper(
cdc codec.BinaryCodec,
storeKey, transientKey sdk.StoreKey, paramSpace paramtypes.Subspace,
ak types.AccountKeeper, bankKeeper types.BankKeeper, sk types.StakingKeeper,
fmk types.FeeMarketKeeper,
tracer string,
) *Keeper {
// ensure evm module account is set
if addr := ak.GetModuleAddress(types.ModuleName); addr == nil {
panic("the EVM module account has not been set")
}
// set KeyTable if it has not already been set
if !paramSpace.HasKeyTable() {
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
}
// NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations
return &Keeper{
cdc: cdc,
paramSpace: paramSpace,
accountKeeper: ak,
bankKeeper: bankKeeper,
stakingKeeper: sk,
feeMarketKeeper: fmk,
storeKey: storeKey,
transientKey: transientKey,
tracer: tracer,
}
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", types.ModuleName)
}
// WithChainID sets the chain id to the local variable in the keeper
func (k *Keeper) WithChainID(ctx sdk.Context) {
chainID, err := ethermint.ParseChainID(ctx.ChainID())
if err != nil {
panic(err)
}
if k.eip155ChainID != nil && k.eip155ChainID.Cmp(chainID) != 0 {
panic("chain id already set")
}
k.eip155ChainID = chainID
}
// ChainID returns the EIP155 chain ID for the EVM context
func (k Keeper) ChainID() *big.Int {
return k.eip155ChainID
}
// ----------------------------------------------------------------------------
// Block Bloom
// Required by Web3 API.
// ----------------------------------------------------------------------------
// EmitBlockBloomEvent emit block bloom events
func (k Keeper) EmitBlockBloomEvent(ctx sdk.Context, bloom ethtypes.Bloom) {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeBlockBloom,
sdk.NewAttribute(types.AttributeKeyEthereumBloom, string(bloom.Bytes())),
),
)
}
// GetBlockBloomTransient returns bloom bytes for the current block height
func (k Keeper) GetBlockBloomTransient(ctx sdk.Context) *big.Int {
store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
heightBz := sdk.Uint64ToBigEndian(uint64(ctx.BlockHeight()))
bz := store.Get(heightBz)
if len(bz) == 0 {
return big.NewInt(0)
}
return new(big.Int).SetBytes(bz)
}
// SetBlockBloomTransient sets the given bloom bytes to the transient store. This value is reset on
// every block.
func (k Keeper) SetBlockBloomTransient(ctx sdk.Context, bloom *big.Int) {
store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
heightBz := sdk.Uint64ToBigEndian(uint64(ctx.BlockHeight()))
store.Set(heightBz, bloom.Bytes())
}
// ----------------------------------------------------------------------------
// Tx
// ----------------------------------------------------------------------------
// SetTxIndexTransient set the index of processing transaction
func (k Keeper) SetTxIndexTransient(ctx sdk.Context, index uint64) {
store := ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientTxIndex, sdk.Uint64ToBigEndian(index))
}
// GetTxIndexTransient returns EVM transaction index on the current block.
func (k Keeper) GetTxIndexTransient(ctx sdk.Context) uint64 {
store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientTxIndex)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// ----------------------------------------------------------------------------
// Log
// ----------------------------------------------------------------------------
// GetLogSizeTransient returns EVM log index on the current block.
func (k Keeper) GetLogSizeTransient(ctx sdk.Context) uint64 {
store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientLogSize)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// SetLogSizeTransient fetches the current EVM log index from the transient store, increases its
// value by one and then sets the new index back to the transient store.
func (k Keeper) SetLogSizeTransient(ctx sdk.Context, logSize uint64) {
store := ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientLogSize, sdk.Uint64ToBigEndian(logSize))
}
// ----------------------------------------------------------------------------
// Storage
// ----------------------------------------------------------------------------
// GetAccountStorage return state storage associated with an account
func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) types.Storage {
storage := types.Storage{}
k.ForEachStorage(ctx, address, func(key, value common.Hash) bool {
storage = append(storage, types.NewState(key, value))
return true
})
return storage
}
// ----------------------------------------------------------------------------
// Account
// ----------------------------------------------------------------------------
// SetHooks sets the hooks for the EVM module
// It should be called only once during initialization, it panic if called more than once.
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(ctx sdk.Context, from common.Address, to *common.Address, receipt *ethtypes.Receipt) error {
if k.hooks == nil {
return nil
}
return k.hooks.PostTxProcessing(ctx, from, to, receipt)
}
// Tracer return a default vm.Tracer based on current keeper state
func (k Keeper) Tracer(ctx sdk.Context, msg core.Message, ethCfg *params.ChainConfig) vm.Tracer {
return types.NewTracer(k.tracer, msg, ethCfg, ctx.BlockHeight())
}
// GetAccountWithoutBalance load nonce and codehash without balance,
// more efficient in cases where balance is not needed.
func (k *Keeper) GetAccountWithoutBalance(ctx sdk.Context, addr common.Address) *statedb.Account {
cosmosAddr := sdk.AccAddress(addr.Bytes())
acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
if acct == nil {
return nil
}
codeHash := types.EmptyCodeHash
ethAcct, ok := acct.(ethermint.EthAccountI)
if ok {
codeHash = ethAcct.GetCodeHash().Bytes()
}
return &statedb.Account{
Nonce: acct.GetSequence(),
CodeHash: codeHash,
}
}
// GetAccountOrEmpty returns empty account if not exist, returns error if it's not `EthAccount`
func (k *Keeper) GetAccountOrEmpty(ctx sdk.Context, addr common.Address) statedb.Account {
acct := k.GetAccount(ctx, addr)
if acct != nil {
return *acct
}
// empty account
return statedb.Account{
Balance: new(big.Int),
CodeHash: types.EmptyCodeHash,
}
}
// GetNonce returns the sequence number of an account, returns 0 if not exists.
func (k *Keeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 {
cosmosAddr := sdk.AccAddress(addr.Bytes())
acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
if acct == nil {
return 0
}
return acct.GetSequence()
}
// GetBalance load account's balance of gas token
func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int {
cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(ctx)
coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
return coin.Amount.BigInt()
}
// BaseFee returns current base fee, return values:
// - `nil`: london hardfork not enabled.
// - `0`: london hardfork enabled but feemarket is not enabled.
// - `n`: both london hardfork and feemarket are enabled.
func (k Keeper) BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int {
if !types.IsLondon(ethCfg, ctx.BlockHeight()) {
return nil
}
baseFee := k.feeMarketKeeper.GetBaseFee(ctx)
if baseFee == nil {
// return 0 if feemarket not enabled.
baseFee = big.NewInt(0)
}
return baseFee
}
// ResetTransientGasUsed reset gas used to prepare for execution of current cosmos tx, called in ante handler.
func (k Keeper) ResetTransientGasUsed(ctx sdk.Context) {
store := ctx.TransientStore(k.transientKey)
store.Delete(types.KeyPrefixTransientGasUsed)
}
// GetTransientGasUsed returns the gas used by current cosmos tx.
func (k Keeper) GetTransientGasUsed(ctx sdk.Context) uint64 {
store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientGasUsed)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// SetTransientGasUsed sets the gas used by current cosmos tx.
func (k Keeper) SetTransientGasUsed(ctx sdk.Context, gasUsed uint64) {
store := ctx.TransientStore(k.transientKey)
bz := sdk.Uint64ToBigEndian(gasUsed)
store.Set(types.KeyPrefixTransientGasUsed, bz)
}
// AddTransientGasUsed accumulate gas used by each eth msgs included in current cosmos tx.
func (k Keeper) AddTransientGasUsed(ctx sdk.Context, gasUsed uint64) (uint64, error) {
result := k.GetTransientGasUsed(ctx) + gasUsed
if result < gasUsed {
return 0, sdkerrors.Wrap(types.ErrGasOverflow, "transient gas used")
}
k.SetTransientGasUsed(ctx, result)
return result, nil
}