75fb3ccb89
* Fix wrong blockHash value in evm * fix ut * store current block hash in BeginBlock * update ut * update comment * add changelog
271 lines
7.7 KiB
Go
271 lines
7.7 KiB
Go
package types
|
|
|
|
import (
|
|
"errors"
|
|
"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/core/vm"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
)
|
|
|
|
// StateTransition defines data to transitionDB in evm
|
|
type StateTransition struct {
|
|
// TxData fields
|
|
AccountNonce uint64
|
|
Price *big.Int
|
|
GasLimit uint64
|
|
Recipient *common.Address
|
|
Amount *big.Int
|
|
Payload []byte
|
|
|
|
ChainID *big.Int
|
|
Csdb *CommitStateDB
|
|
TxHash *common.Hash
|
|
Sender common.Address
|
|
Simulate bool // i.e CheckTx execution
|
|
}
|
|
|
|
// GasInfo returns the gas limit, gas consumed and gas refunded from the EVM transition
|
|
// execution
|
|
type GasInfo struct {
|
|
GasLimit uint64
|
|
GasConsumed uint64
|
|
GasRefunded uint64
|
|
}
|
|
|
|
// ExecutionResult represents what's returned from a transition
|
|
type ExecutionResult struct {
|
|
Logs []*ethtypes.Log
|
|
Bloom *big.Int
|
|
Result *sdk.Result
|
|
GasInfo GasInfo
|
|
}
|
|
|
|
// GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases:
|
|
// 1. The requested height matches the current height (and thus same epoch number)
|
|
// 2. The requested height is from an previous height from the same chain epoch
|
|
// 3. The requested height is from a height greater than the latest one
|
|
func GetHashFn(ctx sdk.Context, csdb *CommitStateDB) vm.GetHashFunc {
|
|
return func(height uint64) common.Hash {
|
|
switch {
|
|
case ctx.BlockHeight() == int64(height):
|
|
// Case 1: The requested height matches the one from the CommitStateDB so we can retrieve the block
|
|
// hash directly from the CommitStateDB.
|
|
return csdb.bhash
|
|
|
|
case ctx.BlockHeight() > int64(height):
|
|
// 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.
|
|
return csdb.WithContext(ctx).GetHeightHash(height)
|
|
|
|
default:
|
|
// Case 3: heights greater than the current one returns an empty hash.
|
|
return common.Hash{}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (st StateTransition) newEVM(
|
|
ctx sdk.Context,
|
|
csdb *CommitStateDB,
|
|
gasLimit uint64,
|
|
gasPrice *big.Int,
|
|
config ChainConfig,
|
|
extraEIPs []int64,
|
|
) *vm.EVM {
|
|
// Create contexts for evm
|
|
|
|
blockCtx := vm.BlockContext{
|
|
CanTransfer: core.CanTransfer,
|
|
Transfer: core.Transfer,
|
|
GetHash: GetHashFn(ctx, csdb),
|
|
Coinbase: common.Address{}, // there's no beneficiary since we're not mining
|
|
BlockNumber: big.NewInt(ctx.BlockHeight()),
|
|
Time: big.NewInt(ctx.BlockHeader().Time.Unix()),
|
|
Difficulty: big.NewInt(0), // unused. Only required in PoW context
|
|
GasLimit: gasLimit,
|
|
}
|
|
|
|
txCtx := vm.TxContext{
|
|
Origin: st.Sender,
|
|
GasPrice: gasPrice,
|
|
}
|
|
|
|
eips := make([]int, len(extraEIPs))
|
|
for i, eip := range extraEIPs {
|
|
eips[i] = int(eip)
|
|
}
|
|
|
|
vmConfig := vm.Config{
|
|
ExtraEips: eips,
|
|
}
|
|
|
|
return vm.NewEVM(blockCtx, txCtx, csdb, config.EthereumConfig(st.ChainID), vmConfig)
|
|
}
|
|
|
|
// TransitionDb will transition the state by applying the current transaction and
|
|
// returning the evm execution result.
|
|
// NOTE: State transition checks are run during AnteHandler execution.
|
|
func (st StateTransition) TransitionDb(ctx sdk.Context, config ChainConfig) (*ExecutionResult, error) {
|
|
contractCreation := st.Recipient == nil
|
|
|
|
cost, err := core.IntrinsicGas(st.Payload, contractCreation, config.IsHomestead(), config.IsIstanbul())
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "invalid intrinsic gas for transaction")
|
|
}
|
|
|
|
// This gas limit the the transaction gas limit with intrinsic gas subtracted
|
|
gasLimit := st.GasLimit - ctx.GasMeter().GasConsumed()
|
|
|
|
csdb := st.Csdb.WithContext(ctx)
|
|
if st.Simulate {
|
|
// gasLimit is set here because stdTxs incur gaskv charges in the ante handler, but for eth_call
|
|
// the cost needs to be the same as an Ethereum transaction sent through the web3 API
|
|
consumedGas := ctx.GasMeter().GasConsumed()
|
|
gasLimit = st.GasLimit - cost
|
|
if consumedGas < cost {
|
|
// If Cosmos standard tx ante handler cost is less than EVM intrinsic cost
|
|
// gas must be consumed to match to accurately simulate an Ethereum transaction
|
|
ctx.GasMeter().ConsumeGas(cost-consumedGas, "Intrinsic gas match")
|
|
}
|
|
|
|
csdb = st.Csdb.Copy()
|
|
}
|
|
|
|
// This gas meter is set up to consume gas from gaskv during evm execution and be ignored
|
|
currentGasMeter := ctx.GasMeter()
|
|
evmGasMeter := sdk.NewInfiniteGasMeter()
|
|
csdb.WithContext(ctx.WithGasMeter(evmGasMeter))
|
|
|
|
// Clear cache of accounts to handle changes outside of the EVM
|
|
csdb.UpdateAccounts()
|
|
|
|
params := csdb.GetParams()
|
|
|
|
gasPrice := ctx.MinGasPrices().AmountOf(params.EvmDenom)
|
|
if gasPrice.IsNil() {
|
|
return nil, errors.New("gas price cannot be nil")
|
|
}
|
|
|
|
evm := st.newEVM(ctx, csdb, gasLimit, gasPrice.Int, config, params.ExtraEIPs)
|
|
|
|
var (
|
|
ret []byte
|
|
leftOverGas uint64
|
|
contractAddress common.Address
|
|
recipientLog string
|
|
senderRef = vm.AccountRef(st.Sender)
|
|
)
|
|
|
|
// Get nonce of account outside of the EVM
|
|
currentNonce := csdb.GetNonce(st.Sender)
|
|
// Set nonce of sender account before evm state transition for usage in generating Create address
|
|
csdb.SetNonce(st.Sender, st.AccountNonce)
|
|
|
|
// create contract or execute call
|
|
switch contractCreation {
|
|
case true:
|
|
if !params.EnableCreate {
|
|
return nil, ErrCreateDisabled
|
|
}
|
|
|
|
ret, contractAddress, leftOverGas, err = evm.Create(senderRef, st.Payload, gasLimit, st.Amount)
|
|
recipientLog = fmt.Sprintf("contract address %s", contractAddress.String())
|
|
default:
|
|
if !params.EnableCall {
|
|
return nil, ErrCallDisabled
|
|
}
|
|
|
|
// Increment the nonce for the next transaction (just for evm state transition)
|
|
csdb.SetNonce(st.Sender, csdb.GetNonce(st.Sender)+1)
|
|
ret, leftOverGas, err = evm.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount)
|
|
recipientLog = fmt.Sprintf("recipient address %s", st.Recipient.String())
|
|
}
|
|
|
|
gasConsumed := gasLimit - leftOverGas
|
|
|
|
if err != nil {
|
|
// Consume gas before returning
|
|
ctx.GasMeter().ConsumeGas(gasConsumed, "evm execution consumption")
|
|
return nil, err
|
|
}
|
|
|
|
// Resets nonce to value pre state transition
|
|
csdb.SetNonce(st.Sender, currentNonce)
|
|
|
|
// Generate bloom filter to be saved in tx receipt data
|
|
bloomInt := big.NewInt(0)
|
|
|
|
var (
|
|
bloomFilter ethtypes.Bloom
|
|
logs []*ethtypes.Log
|
|
)
|
|
|
|
if st.TxHash != nil && !st.Simulate {
|
|
logs, err = csdb.GetLogs(*st.TxHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bloomInt = big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs))
|
|
bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes())
|
|
}
|
|
|
|
if !st.Simulate {
|
|
// Finalise state if not a simulated transaction
|
|
// TODO: change to depend on config
|
|
if err := csdb.Finalise(true); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Encode all necessary data into slice of bytes to return in sdk result
|
|
resultData := ResultData{
|
|
Bloom: bloomFilter,
|
|
Logs: logs,
|
|
Ret: ret,
|
|
TxHash: *st.TxHash,
|
|
}
|
|
|
|
if contractCreation {
|
|
resultData.ContractAddress = contractAddress
|
|
}
|
|
|
|
resBz, err := EncodeResultData(resultData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resultLog := fmt.Sprintf(
|
|
"executed EVM state transition; sender address %s; %s", st.Sender.String(), recipientLog,
|
|
)
|
|
|
|
executionResult := &ExecutionResult{
|
|
Logs: logs,
|
|
Bloom: bloomInt,
|
|
Result: &sdk.Result{
|
|
Data: resBz,
|
|
Log: resultLog,
|
|
},
|
|
GasInfo: GasInfo{
|
|
GasConsumed: gasConsumed,
|
|
GasLimit: gasLimit,
|
|
GasRefunded: leftOverGas,
|
|
},
|
|
}
|
|
|
|
// TODO: Refund unused gas here, if intended in future
|
|
|
|
// Consume gas from evm execution
|
|
// Out of gas check does not need to be done here since it is done within the EVM execution
|
|
ctx.WithGasMeter(currentGasMeter).GasMeter().ConsumeGas(gasConsumed, "EVM execution consumption")
|
|
|
|
return executionResult, nil
|
|
}
|