github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/evm/types/state_transition.go (about)

     1  package types
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"math/big"
     8  	"strings"
     9  
    10  	types2 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/types"
    11  	"github.com/fibonacci-chain/fbc/libs/system/trace"
    12  	"github.com/fibonacci-chain/fbc/libs/tendermint/types"
    13  
    14  	"github.com/ethereum/go-ethereum/accounts/abi"
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethereum/go-ethereum/common/hexutil"
    17  	"github.com/ethereum/go-ethereum/core"
    18  	ethtypes "github.com/ethereum/go-ethereum/core/types"
    19  	"github.com/ethereum/go-ethereum/core/vm"
    20  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    21  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
    22  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/innertx"
    23  )
    24  
    25  // StateTransition defines data to transitionDB in evm
    26  type StateTransition struct {
    27  	// TxData fields
    28  	AccountNonce uint64
    29  	Price        *big.Int
    30  	GasLimit     uint64
    31  	Recipient    *common.Address
    32  	Amount       *big.Int
    33  	Payload      []byte
    34  
    35  	ChainID    *big.Int
    36  	Csdb       *CommitStateDB
    37  	TxHash     *common.Hash
    38  	Sender     common.Address
    39  	Simulate   bool // i.e CheckTx execution
    40  	TraceTx    bool // reexcute tx or its predesessors
    41  	TraceTxLog bool // trace tx for its evm logs (predesessors are set to false)
    42  }
    43  
    44  // GasInfo returns the gas limit, gas consumed and gas refunded from the EVM transition
    45  // execution
    46  type GasInfo struct {
    47  	GasLimit    uint64
    48  	GasConsumed uint64
    49  }
    50  
    51  // ExecutionResult represents what's returned from a transition
    52  type ExecutionResult struct {
    53  	Logs      []*ethtypes.Log
    54  	Bloom     *big.Int
    55  	Result    *sdk.Result
    56  	GasInfo   GasInfo
    57  	TraceLogs []byte
    58  }
    59  
    60  // GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases:
    61  //  1. The requested height matches the current height (and thus same epoch number)
    62  //  2. The requested height is from an previous height from the same chain epoch
    63  //  3. The requested height is from a height greater than the latest one
    64  func GetHashFn(ctx sdk.Context, csdb *CommitStateDB) vm.GetHashFunc {
    65  	return func(height uint64) common.Hash {
    66  		switch {
    67  		case ctx.BlockHeight() == int64(height):
    68  			// Case 1: The requested height matches the one from the context so we can retrieve the header
    69  			// hash directly from the context.
    70  			return csdb.bhash
    71  
    72  		case ctx.BlockHeight() > int64(height):
    73  			// Case 2: if the chain is not the current height we need to retrieve the hash from the store for the
    74  			// current chain epoch. This only applies if the current height is greater than the requested height.
    75  			return csdb.WithContext(ctx).GetHeightHash(height)
    76  
    77  		default:
    78  			// Case 3: heights greater than the current one returns an empty hash.
    79  			return common.Hash{}
    80  		}
    81  	}
    82  }
    83  
    84  func (st *StateTransition) newEVM(
    85  	ctx sdk.Context,
    86  	csdb *CommitStateDB,
    87  	gasLimit uint64,
    88  	gasPrice *big.Int,
    89  	config *ChainConfig,
    90  	vmConfig vm.Config,
    91  ) *vm.EVM {
    92  	// Create context for evm
    93  	blockCtx := vm.BlockContext{
    94  		CanTransfer: core.CanTransfer,
    95  		Transfer:    core.Transfer,
    96  		GetHash:     GetHashFn(ctx, csdb),
    97  		Coinbase:    common.BytesToAddress(ctx.BlockProposerAddress()),
    98  		BlockNumber: big.NewInt(ctx.BlockHeight()),
    99  		Time:        big.NewInt(ctx.BlockTime().Unix()),
   100  		Difficulty:  big.NewInt(0), // unused. Only required in PoW context
   101  		GasLimit:    gasLimit,
   102  	}
   103  
   104  	txCtx := vm.TxContext{
   105  		Origin:   st.Sender,
   106  		GasPrice: gasPrice,
   107  	}
   108  
   109  	return vm.NewEVM(blockCtx, txCtx, csdb, config.EthereumConfig(st.ChainID), vmConfig)
   110  }
   111  
   112  func (st *StateTransition) applyOverrides(ctx sdk.Context, csdb *CommitStateDB) error {
   113  	overrideBytes := ctx.OverrideBytes()
   114  	if overrideBytes != nil {
   115  		var stateOverrides StateOverrides
   116  		err := json.Unmarshal(overrideBytes, &stateOverrides)
   117  		if err != nil {
   118  			return fmt.Errorf("failed to decode stateOverrides")
   119  		}
   120  		stateOverrides.Apply(csdb)
   121  	}
   122  	return nil
   123  }
   124  
   125  // TransitionDb will transition the state by applying the current transaction and
   126  // returning the evm execution result.
   127  // NOTE: State transition checks are run during AnteHandler execution.
   128  func (st StateTransition) TransitionDb(ctx sdk.Context, config ChainConfig) (exeRes *ExecutionResult, resData *ResultData, err error, innerTxs, erc20Contracts interface{}) {
   129  	preSSId := st.Csdb.Snapshot()
   130  	contractCreation := st.Recipient == nil
   131  
   132  	defer func() {
   133  		if e := recover(); e != nil {
   134  			if !st.Simulate {
   135  				st.Csdb.RevertToSnapshot(preSSId)
   136  			}
   137  
   138  			// if the msg recovered can be asserted into type 'ErrContractBlockedVerify', it must be captured by the panics of blocked
   139  			// contract calling
   140  			switch rType := e.(type) {
   141  			case ErrContractBlockedVerify:
   142  				err = ErrCallBlockedContract(rType.Descriptor)
   143  			default:
   144  				panic(e)
   145  			}
   146  		}
   147  	}()
   148  
   149  	cost, err := core.IntrinsicGas(st.Payload, []ethtypes.AccessTuple{}, contractCreation, config.IsHomestead(), config.IsIstanbul())
   150  	if err != nil {
   151  		return exeRes, resData, sdkerrors.Wrap(err, "invalid intrinsic gas for transaction"), innerTxs, erc20Contracts
   152  	}
   153  
   154  	consumedGas := ctx.GasMeter().GasConsumed()
   155  	if consumedGas < cost {
   156  		// If Cosmos standard tx ante handler cost is less than EVM intrinsic cost
   157  		// gas must be consumed to match to accurately simulate an Ethereum transaction
   158  		ctx.GasMeter().ConsumeGas(cost-consumedGas, "Intrinsic gas match")
   159  	}
   160  
   161  	// This gas limit the the transaction gas limit with intrinsic gas subtracted
   162  	gasLimit := st.GasLimit - ctx.GasMeter().GasConsumed()
   163  
   164  	// This gas meter is set up to consume gas from gaskv during evm execution and be ignored
   165  	currentGasMeter := ctx.GasMeter()
   166  	evmGasMeter := sdk.NewInfiniteGasMeter()
   167  	ctx.SetGasMeter(evmGasMeter)
   168  	csdb := st.Csdb.WithContext(ctx)
   169  
   170  	StartTxLog := func(tag string) {
   171  		if !ctx.IsCheckTx() {
   172  			trace.StartTxLog(tag)
   173  		}
   174  	}
   175  	StopTxLog := func(tag string) {
   176  		if !ctx.IsCheckTx() {
   177  			trace.StopTxLog(tag)
   178  		}
   179  	}
   180  	if ctx.IsCheckTx() {
   181  		if err = st.applyOverrides(ctx, csdb); err != nil {
   182  			return
   183  		}
   184  	}
   185  
   186  	params := csdb.GetParams()
   187  
   188  	var senderStr = EthAddressToString(&st.Sender)
   189  
   190  	to := ""
   191  	var recipientStr string
   192  	if st.Recipient != nil {
   193  		to = EthAddressToString(st.Recipient)
   194  		recipientStr = to
   195  	}
   196  	tracer := newTracer(ctx, st.TxHash)
   197  	vmConfig := vm.Config{
   198  		ExtraEips:               params.ExtraEIPs,
   199  		Debug:                   st.TraceTxLog,
   200  		Tracer:                  tracer,
   201  		ContractVerifier:        NewContractVerifier(params),
   202  		EnablePreimageRecording: st.TraceTxLog,
   203  	}
   204  
   205  	evm := st.newEVM(ctx, csdb, gasLimit, st.Price, &config, vmConfig)
   206  
   207  	var (
   208  		ret             []byte
   209  		leftOverGas     uint64
   210  		contractAddress common.Address
   211  		recipientLog    string
   212  		senderRef       = vm.AccountRef(st.Sender)
   213  		gasConsumed     uint64
   214  	)
   215  
   216  	// Get nonce of account outside of the EVM
   217  	currentNonce := csdb.GetNonce(st.Sender)
   218  	// Set nonce of sender account before evm state transition for usage in generating Create address
   219  	csdb.SetNonce(st.Sender, st.AccountNonce)
   220  
   221  	//add InnerTx
   222  	callTx := innertx.AddDefaultInnerTx(evm, innertx.CosmosDepth, senderStr, "", "", "", st.Amount, nil)
   223  
   224  	// create contract or execute call
   225  	switch contractCreation {
   226  	case true:
   227  		if !params.EnableCreate {
   228  			if !st.Simulate {
   229  				st.Csdb.RevertToSnapshot(preSSId)
   230  			}
   231  
   232  			return exeRes, resData, ErrCreateDisabled, innerTxs, erc20Contracts
   233  		}
   234  
   235  		// check whether the deployer address is in the whitelist if the whitelist is enabled
   236  		senderAccAddr := st.Sender.Bytes()
   237  		if params.EnableContractDeploymentWhitelist && !csdb.IsDeployerInWhitelist(senderAccAddr) {
   238  			if !st.Simulate {
   239  				st.Csdb.RevertToSnapshot(preSSId)
   240  			}
   241  
   242  			return exeRes, resData, ErrUnauthorizedAccount(senderAccAddr), innerTxs, erc20Contracts
   243  		}
   244  
   245  		StartTxLog(trace.EVMCORE)
   246  		defer StopTxLog(trace.EVMCORE)
   247  		nonce := evm.StateDB.GetNonce(st.Sender)
   248  		ret, contractAddress, leftOverGas, err = evm.Create(senderRef, st.Payload, gasLimit, st.Amount)
   249  
   250  		contractAddressStr := EthAddressToString(&contractAddress)
   251  		recipientLog = strings.Join([]string{"contract address ", contractAddressStr}, "")
   252  		gasConsumed = gasLimit - leftOverGas
   253  		if !csdb.GuFactor.IsNegative() {
   254  			gasConsumed = csdb.GuFactor.MulInt(sdk.NewIntFromUint64(gasConsumed)).TruncateInt().Uint64()
   255  		}
   256  		//if no err, we must be check weather out of gas because, we may increase gasConsumed by 'csdb.GuFactor'.
   257  		if err == nil {
   258  			if gasLimit < gasConsumed {
   259  				err = vm.ErrOutOfGas
   260  				//if out of gas,then err is ErrOutOfGas, gasConsumed change to gasLimit for can not make line.295 panic that will lead to 'RevertToSnapshot' panic
   261  				gasConsumed = gasLimit
   262  			}
   263  		} else {
   264  			if gasConsumed > gasLimit {
   265  				gasConsumed = gasLimit
   266  				defer func() {
   267  					panic(types2.ErrorOutOfGas{Descriptor: "EVM execution consumption"})
   268  				}()
   269  			}
   270  		}
   271  		innertx.UpdateDefaultInnerTx(callTx, contractAddressStr, innertx.CosmosCallType, innertx.EvmCreateName, gasConsumed, nonce)
   272  	default:
   273  		if !params.EnableCall {
   274  			if !st.Simulate {
   275  				st.Csdb.RevertToSnapshot(preSSId)
   276  			}
   277  
   278  			return exeRes, resData, ErrCallDisabled, innerTxs, erc20Contracts
   279  		}
   280  
   281  		// Increment the nonce for the next transaction	(just for evm state transition)
   282  		csdb.SetNonce(st.Sender, csdb.GetNonce(st.Sender)+1)
   283  		StartTxLog(trace.EVMCORE)
   284  		defer StopTxLog(trace.EVMCORE)
   285  		ret, leftOverGas, err = evm.Call(senderRef, *st.Recipient, st.Payload, gasLimit, st.Amount)
   286  
   287  		if recipientStr == "" {
   288  			recipientStr = EthAddressToString(st.Recipient)
   289  		}
   290  
   291  		recipientLog = strings.Join([]string{"recipient address ", recipientStr}, "")
   292  		gasConsumed = gasLimit - leftOverGas
   293  		if !csdb.GuFactor.IsNegative() {
   294  			gasConsumed = csdb.GuFactor.MulInt(sdk.NewIntFromUint64(gasConsumed)).TruncateInt().Uint64()
   295  		}
   296  		//if no err, we must be check weather out of gas because, we may increase gasConsumed by 'csdb.GuFactor'.
   297  		if err == nil {
   298  			if gasLimit < gasConsumed {
   299  				err = vm.ErrOutOfGas
   300  				//if out of gas,then err is ErrOutOfGas, gasConsumed change to gasLimit for can not make line.295 panic that will lead to 'RevertToSnapshot' panic
   301  				gasConsumed = gasLimit
   302  			}
   303  		} else {
   304  			// For cover err != nil,but gasConsumed which is caculated by gufactor  >  gaslimit,we must be make gasConsumed = gasLimit and panic same as currentGasMeter.ConsumeGas. so we can not use height isolation
   305  			if gasConsumed > gasLimit {
   306  				gasConsumed = gasLimit
   307  				defer func() {
   308  					panic(types2.ErrorOutOfGas{Descriptor: "EVM execution consumption"})
   309  				}()
   310  			}
   311  		}
   312  
   313  		innertx.UpdateDefaultInnerTx(callTx, recipientStr, innertx.CosmosCallType, innertx.EvmCallName, gasConsumed, 0)
   314  	}
   315  
   316  	innerTxs, erc20Contracts = innertx.ParseInnerTxAndContract(evm, err != nil)
   317  
   318  	defer func() {
   319  		// Consume gas from evm execution
   320  		// Out of gas check does not need to be done here since it is done within the EVM execution
   321  		currentGasMeter.ConsumeGas(gasConsumed, "EVM execution consumption")
   322  	}()
   323  
   324  	// return trace log if tracetxlog no matter err = nil  or not nil
   325  	defer func() {
   326  		var traceLogs []byte
   327  		if st.TraceTxLog {
   328  			result := &core.ExecutionResult{
   329  				UsedGas:    gasConsumed,
   330  				Err:        err,
   331  				ReturnData: ret,
   332  			}
   333  			traceLogs, err = GetTracerResult(tracer, result)
   334  			if err != nil {
   335  				traceLogs = []byte(err.Error())
   336  			} else {
   337  				traceLogs, err = integratePreimage(csdb, traceLogs)
   338  				if err != nil {
   339  					traceLogs = []byte(err.Error())
   340  				}
   341  			}
   342  			if exeRes == nil {
   343  				exeRes = &ExecutionResult{
   344  					Result: &sdk.Result{},
   345  				}
   346  			}
   347  			exeRes.TraceLogs = traceLogs
   348  		}
   349  	}()
   350  	if err != nil {
   351  		if !st.Simulate {
   352  			st.Csdb.RevertToSnapshot(preSSId)
   353  		}
   354  
   355  		// Consume gas before returning
   356  		return exeRes, resData, newRevertError(ret, err), innerTxs, erc20Contracts
   357  	}
   358  
   359  	// Resets nonce to value pre state transition
   360  	csdb.SetNonce(st.Sender, currentNonce)
   361  
   362  	// Generate bloom filter to be saved in tx receipt data
   363  	bloomInt := big.NewInt(0)
   364  
   365  	var (
   366  		bloomFilter ethtypes.Bloom
   367  		logs        []*ethtypes.Log
   368  	)
   369  
   370  	if st.TxHash != nil && !st.Simulate {
   371  		logs, err = csdb.GetLogs(*st.TxHash)
   372  		if err != nil {
   373  			st.Csdb.RevertToSnapshot(preSSId)
   374  			return
   375  		}
   376  
   377  		bloomInt = big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs))
   378  		bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes())
   379  	}
   380  
   381  	if !st.Simulate {
   382  		if types.HigherThanMars(ctx.BlockHeight()) {
   383  			if ctx.IsDeliver() {
   384  				csdb.IntermediateRoot(true)
   385  			}
   386  		} else {
   387  			csdb.Commit(true)
   388  		}
   389  	}
   390  
   391  	// Encode all necessary data into slice of bytes to return in sdk result
   392  	resData = &ResultData{
   393  		Bloom:  bloomFilter,
   394  		Logs:   logs,
   395  		Ret:    ret,
   396  		TxHash: *st.TxHash,
   397  	}
   398  
   399  	if contractCreation {
   400  		resData.ContractAddress = contractAddress
   401  	}
   402  
   403  	resBz, err := EncodeResultData(resData)
   404  	if err != nil {
   405  		return
   406  	}
   407  
   408  	resultLog := strings.Join([]string{"executed EVM state transition; sender address ", senderStr, "; ", recipientLog}, "")
   409  	exeRes = &ExecutionResult{
   410  		Logs:  logs,
   411  		Bloom: bloomInt,
   412  		Result: &sdk.Result{
   413  			Data: resBz,
   414  			Log:  resultLog,
   415  		},
   416  		GasInfo: GasInfo{
   417  			GasConsumed: gasConsumed,
   418  			GasLimit:    gasLimit,
   419  		},
   420  	}
   421  	return
   422  }
   423  
   424  func newRevertError(data []byte, e error) error {
   425  	var resultError []string
   426  	if data == nil || e.Error() != vm.ErrExecutionReverted.Error() {
   427  		return e
   428  	}
   429  	resultError = append(resultError, e.Error())
   430  	reason, errUnpack := abi.UnpackRevert(data)
   431  	if errUnpack == nil {
   432  		resultError = append(resultError, vm.ErrExecutionReverted.Error()+":"+reason)
   433  	} else {
   434  		resultError = append(resultError, hexutil.Encode(data))
   435  	}
   436  	resultError = append(resultError, ErrorHexData)
   437  	resultError = append(resultError, hexutil.Encode(data))
   438  	ret, error := json.Marshal(resultError)
   439  
   440  	//failed to marshal, return original data in error
   441  	if error != nil {
   442  		return fmt.Errorf(e.Error()+"[%v]", hexutil.Encode(data))
   443  	}
   444  	return errors.New(string(ret))
   445  }
   446  
   447  func integratePreimage(csdb *CommitStateDB, traceLogs []byte) ([]byte, error) {
   448  	var traceLogsMap map[string]interface{}
   449  	if err := json.Unmarshal(traceLogs, &traceLogsMap); err != nil {
   450  		return nil, err
   451  	}
   452  
   453  	preimageMap := make(map[string]interface{})
   454  	for k, v := range csdb.preimages {
   455  		preimageMap[k.Hex()] = hexutil.Encode(v)
   456  	}
   457  	traceLogsMap["preimage"] = preimageMap
   458  	return json.Marshal(traceLogsMap)
   459  }