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 }