diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 28d5aa62bb..9aa488e85c 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -94,6 +94,6 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co txContext := NewCVMTxContext(msg) vm := vm.NewCVM(context, txContext, statedb, config, cfg) - _, _, _, _, err = ApplyMessage(vm, msg, gaspool, quotaPool) + _, err = ApplyMessage(vm, msg, gaspool, quotaPool) return err } diff --git a/core/state_processor.go b/core/state_processor.go index ae9b47f641..7d3fd036b4 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -112,16 +112,16 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, qp // Update the evm with the new transaction context. cvm.Reset(txContext, statedb) // Apply the transaction to the current state (included in the env) - _, gas, quota, failed, err := ApplyMessage(cvm, msg, gp, qp) + result, err := ApplyMessage(cvm, msg, gp, qp) if err != nil { return nil, 0, err } - if quota > 0 { - header.QuotaUsed += quota + if result.Quota > 0 { + header.QuotaUsed += result.Quota if header.Quota < header.QuotaUsed { - header.QuotaUsed -= quota + header.QuotaUsed -= result.Quota return nil, 0, ErrQuotaLimitReached //errors.New("quota") } } @@ -134,13 +134,13 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, qp root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() } - *usedGas += gas + *usedGas += result.UsedGas // Create a new receipt for the transaction, storing the intermediate root and gas used // by the tx. - receipt := types.NewReceipt(root, failed, *usedGas) + receipt := types.NewReceipt(root, result.Failed(), *usedGas) receipt.TxHash = tx.Hash() - receipt.GasUsed = gas + receipt.GasUsed = result.UsedGas // if the transaction created a contract, store the creation address in the receipt. if msg.To == nil { receipt.ContractAddress = crypto.CreateAddress(cvm.TxContext.Origin, tx.Nonce()) @@ -152,7 +152,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, qp receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) - return receipt, gas, err + return receipt, result.UsedGas, err } // ApplyTransaction attempts to apply a transaction to the given state database diff --git a/core/state_transition.go b/core/state_transition.go index 47d5e2d0e1..b9c48b550f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -33,6 +33,43 @@ import ( torrentfs "github.com/CortexFoundation/torrentfs/types" ) +// ExecutionResult includes all output after executing given evm +// message no matter the execution itself is successful or not. +type ExecutionResult struct { + UsedGas uint64 // Total used gas, not including the refunded gas + Quota uint64 + RefundedGas uint64 // Total gas refunded after execution + Err error // Any error encountered during the execution(listed in core/vm/errors.go) + ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) +} + +// Unwrap returns the internal evm error which allows us for further +// analysis outside. +func (result *ExecutionResult) Unwrap() error { + return result.Err +} + +// Failed returns the indicator whether the execution is successful or not +func (result *ExecutionResult) Failed() bool { return result.Err != nil } + +// Return is a helper function to help caller distinguish between revert reason +// and function return. Return returns the data after execution if no error occurs. +func (result *ExecutionResult) Return() []byte { + if result.Err != nil { + return nil + } + return common.CopyBytes(result.ReturnData) +} + +// Revert returns the concrete revert reason if the execution is aborted by `REVERT` +// opcode. Note the reason can be nil if no data supplied with revert opcode. +func (result *ExecutionResult) Revert() []byte { + if result.Err != vm.ErrExecutionReverted { + return nil + } + return common.CopyBytes(result.ReturnData) +} + var ( errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas") //PER_UPLOAD_BYTES uint64 = 10 * 512 * 1024 @@ -161,7 +198,7 @@ func NewStateTransition(cvm *vm.CVM, msg *Message, gp *GasPool, qp *QuotaPool) * // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(cvm *vm.CVM, msg *Message, gp *GasPool, qp *QuotaPool) ([]byte, uint64, uint64, bool, error) { +func ApplyMessage(cvm *vm.CVM, msg *Message, gp *GasPool, qp *QuotaPool) (*ExecutionResult, error) { return NewStateTransition(cvm, msg, gp, qp).TransitionDb() } @@ -295,9 +332,9 @@ func (st *StateTransition) TorrentSync(meta common.Address, dir string, errCh ch // TransitionDb will transition the state by applying the current message and // returning the result including the used gas. It returns an error if failed. // An error indicates a consensus issue. -func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, quotaUsed uint64, failed bool, err error) { - if err = st.preCheck(); err != nil { - return +func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { + if err := st.preCheck(); err != nil { + return nil, err } msg := st.msg @@ -316,20 +353,20 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, quotaUsed // Pay intrinsic gas gas, err := IntrinsicGas(msg.Data, contractCreation, st.uploading(), homestead, istanbul) if err != nil { - return nil, 0, 0, false, err + return nil, err } if st.gas < gas { - return nil, 0, 0, false, fmt.Errorf("%w: have %d, want %d", vm.ErrOutOfGas, st.gas, gas) + return nil, fmt.Errorf("%w: have %d, want %d", vm.ErrOutOfGas, st.gas, gas) } st.gas -= gas if msg.Value.Sign() > 0 && !st.cvm.Context.CanTransfer(st.state, msg.From, msg.Value) { - return nil, 0, 0, false, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex()) + return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex()) } if blocked, num := security.IsBlocked(msg.From); blocked && st.cvm.Context.BlockNumber.Cmp(big.NewInt(num)) >= 0 { log.Debug("Bad address encounter!!", "addr", msg.From, "number", num) - return nil, 0, 0, false, fmt.Errorf("%w: address %v", errors.New("Bad address encounter"), msg.From.Hex()) + return nil, fmt.Errorf("%w: address %v", errors.New("Bad address encounter"), msg.From.Hex()) } var ( @@ -337,6 +374,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, quotaUsed // vm errors do not effect consensus and are therefor // not assigned to err, except for insufficient balance // error. + ret []byte vmerr error ) if contractCreation { @@ -352,7 +390,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, quotaUsed if vmerr != nil { if vmerr == vm.ErrRuntime { - return nil, 0, 0, false, vmerr + return nil, vmerr } log.Debug("VM returned with error", "err", vmerr, "number", cvm.Context.BlockNumber, "from", msg.From.Hex()) @@ -361,7 +399,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, quotaUsed // sufficient balance to make the transfer happen. The first // balance transfer may never fail. if vmerr == vm.ErrInsufficientBalance { - return nil, 0, 0, false, vmerr + return nil, vmerr } //if vmerr == vm.ErrMetaInfoNotMature { @@ -371,7 +409,8 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, quotaUsed //gas cost below this line - st.refundGas() + var gasRefund uint64 + gasRefund = st.refundGas() //model gas gu := st.gasUsed() //if (vmerr == nil || vmerr == vm.ErrOutOfGas) && st.modelGas != nil && len(st.modelGas) > 0 { //pay ctx to the model authors by the model gas * current price @@ -382,7 +421,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, quotaUsed } if gu < mgas { - return nil, 0, 0, false, vm.ErrInsufficientBalance + return nil, vm.ErrInsufficientBalance } gu -= mgas @@ -431,23 +470,29 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, quotaUsed request = inputMeta.RawSize - remain } } else { - return nil, 0, 0, false, vm.ErrRuntime + return nil, vm.ErrRuntime } if err != nil { - return nil, 0, 0, false, vm.ErrRuntime + return nil, vm.ErrRuntime } info := common.StorageEntry{ Hash: ih, Size: request, } if err = synapse.Engine().Download(info); err != nil { - return nil, 0, 0, false, err + return nil, err } } } - return ret, st.gasUsed(), quota, vmerr != nil, err + return &ExecutionResult{ + UsedGas: st.gasUsed(), + Quota: quota, + RefundedGas: gasRefund, + Err: vmerr, + ReturnData: ret, + }, err } // vote to model @@ -455,7 +500,7 @@ func (st *StateTransition) uploading() bool { return st.msg != nil && st.msg.To != nil && st.msg.Value.Sign() == 0 && st.state.Uploading(st.to()) // && st.gas >= params.UploadGas } -func (st *StateTransition) refundGas() { +func (st *StateTransition) refundGas() uint64 { // Apply refund counter, capped to half of the used gas. refund := st.gasUsed() / 2 if refund > st.state.GetRefund() { @@ -470,6 +515,8 @@ func (st *StateTransition) refundGas() { // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gas) + + return refund } // gasUsed returns the amount of gas used up by the state transition. diff --git a/ctxc/api.go b/ctxc/api.go index bdca846588..2ec68dd3e5 100644 --- a/ctxc/api.go +++ b/ctxc/api.go @@ -423,7 +423,7 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewCVM(context, txContext, statedb, api.config, vm.Config{}) - if _, _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), new(core.QuotaPool).AddQuota(math.MaxUint64)); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), new(core.QuotaPool).AddQuota(math.MaxUint64)); err != nil { return nil, vm.BlockContext{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/ctxc/state_accessor.go b/ctxc/state_accessor.go index f8e0a1e88f..ae14b5fa5e 100644 --- a/ctxc/state_accessor.go +++ b/ctxc/state_accessor.go @@ -214,7 +214,7 @@ func (ctxc *Cortex) stateAtTransaction(block *types.Block, txIndex int, reexec u // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewCVM(context, txContext, statedb, ctxc.blockchain.Config(), vm.Config{}) statedb.SetTxContext(tx.Hash(), idx) - if _, _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), new(core.QuotaPool).AddQuota(block.Quota())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), new(core.QuotaPool).AddQuota(block.Quota())); err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/ctxc/tracers/api.go b/ctxc/tracers/api.go index 231db4fbdc..cb4acac60f 100644 --- a/ctxc/tracers/api.go +++ b/ctxc/tracers/api.go @@ -565,7 +565,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config vmenv = vm.NewCVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) ) statedb.SetTxContext(tx.Hash(), i) - if _, _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), new(core.QuotaPool).AddQuota(block.Quota())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), new(core.QuotaPool).AddQuota(block.Quota())); err != nil { log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) // We intentionally don't return the error here: if we do, then the RPC server will not // return the roots. Most likely, the caller already knows that a certain transaction fails to @@ -661,7 +661,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac msg, _ := core.TransactionToMessage(tx, signer) statedb.SetTxContext(tx.Hash(), i) vmenv := vm.NewCVM(blockCtx, core.NewCVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) - if _, _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), new(core.QuotaPool).AddQuota(block.Quota())); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), new(core.QuotaPool).AddQuota(block.Quota())); err != nil { failed = err break } @@ -772,7 +772,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block // Execute the transaction and flush any traces to disk vmenv := vm.NewCVM(vmctx, txContext, statedb, chainConfig, vmConf) statedb.SetTxContext(tx.Hash(), i) - _, _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), new(core.QuotaPool).AddQuota(block.Quota())) + _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), new(core.QuotaPool).AddQuota(block.Quota())) if writer != nil { writer.Flush() } @@ -945,7 +945,7 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte // Run the transaction with tracing enabled. // Call Prepare to clear out the statedb access list statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) - if _, _, _, _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit), new(core.QuotaPool).AddQuota(vmctx.Quota)); err != nil { + if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit), new(core.QuotaPool).AddQuota(vmctx.Quota)); err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } return tracer.GetResult() diff --git a/internal/ctxcapi/api.go b/internal/ctxcapi/api.go index 87ffdfae8a..b16dc566ab 100644 --- a/internal/ctxcapi/api.go +++ b/internal/ctxcapi/api.go @@ -778,8 +778,8 @@ func (s *PublicBlockChainAPI) GetSolidityBytes(ctx context.Context, address comm header := block.Header() msg, err := core.TransactionToMessage(tx, types.MakeSigner(s.b.ChainConfig(), block.Number(), block.Time())) cvm := s.b.GetCVM(ctx, msg, state, header, vm.Config{}) - _, _, _, failed, err := core.ApplyMessage(cvm, msg, gp, qp) - if err != nil || failed { + result, err := core.ApplyMessage(cvm, msg, gp, qp) + if err != nil || result.Failed() { return nil, err } state.Finalise(true) @@ -911,7 +911,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr // and apply the message. gp := new(core.GasPool).AddGas(math.MaxUint64) qp := new(core.QuotaPool).AddQuota(math.MaxUint64) - res, gas, _, failed, err := core.ApplyMessage(cvm, msg, gp, qp) + result, err := core.ApplyMessage(cvm, msg, gp, qp) if err := state.Error(); err != nil { return nil, 0, false, err } @@ -921,7 +921,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout) } - return res, gas, failed, err + return result.Return(), result.UsedGas, result.Failed(), err } // Call executes the given transaction on the state for the given block number.