private void Execute(Transaction transaction, StateUpdate block, ITxTracer txTracer, bool readOnly) { var notSystemTransaction = !transaction.IsSystem(); IReleaseSpec spec = _specProvider.GetSpec(block.Number); if (!notSystemTransaction) { spec = new SystemTransactionReleaseSpec(spec); } Address recipient = transaction.To; UInt256 value = transaction.Value; UInt256 gasPrice = transaction.GasPrice; long gasLimit = (long)transaction.GasLimit; byte[] machineCode = transaction.Init; byte[] data = transaction.Data ?? Bytes.Empty; Address sender = transaction.SenderAddress; if (_logger.IsTrace) { _logger.Trace($"Executing tx {transaction.Hash}"); } if (sender == null) { TraceLogInvalidTx(transaction, "SENDER_NOT_SPECIFIED"); QuickFail(transaction, block, txTracer, readOnly); return; } long intrinsicGas = _intrinsicGasCalculator.Calculate(transaction, spec); if (_logger.IsTrace) { _logger.Trace($"Intrinsic gas calculated for {transaction.Hash}: " + intrinsicGas); } if (notSystemTransaction) { if (gasLimit < intrinsicGas) { TraceLogInvalidTx(transaction, $"GAS_LIMIT_BELOW_INTRINSIC_GAS {gasLimit} < {intrinsicGas}"); QuickFail(transaction, block, txTracer, readOnly); return; } if (gasLimit > block.GasLimit - block.GasUsed) { TraceLogInvalidTx(transaction, $"BLOCK_GAS_LIMIT_EXCEEDED {gasLimit} > {block.GasLimit} - {block.GasUsed}"); QuickFail(transaction, block, txTracer, readOnly); return; } } if (!_stateProvider.AccountExists(sender)) { // hacky fix for the potential recovery issue if (transaction.Signature != null) { transaction.SenderAddress = _ecdsa.RecoverAddress(transaction, block.Number); } if (sender != transaction.SenderAddress) { if (_logger.IsWarn) { _logger.Warn($"TX recovery issue fixed - tx was coming with sender {sender} and the now it recovers to {transaction.SenderAddress}"); } sender = transaction.SenderAddress; } else { TraceLogInvalidTx(transaction, $"SENDER_ACCOUNT_DOES_NOT_EXIST {sender}"); if (gasPrice == UInt256.Zero) { _stateProvider.CreateAccount(sender, UInt256.Zero); } } } if (notSystemTransaction) { UInt256 senderBalance = _stateProvider.GetBalance(sender); if ((ulong)intrinsicGas * gasPrice + value > senderBalance) { TraceLogInvalidTx(transaction, $"INSUFFICIENT_SENDER_BALANCE: ({sender})_BALANCE = {senderBalance}"); QuickFail(transaction, block, txTracer, readOnly); return; } if (transaction.Nonce != _stateProvider.GetNonce(sender)) { TraceLogInvalidTx(transaction, $"WRONG_TRANSACTION_NONCE: {transaction.Nonce} (expected {_stateProvider.GetNonce(sender)})"); QuickFail(transaction, block, txTracer, readOnly); return; } _stateProvider.IncrementNonce(sender); } _stateProvider.SubtractFromBalance(sender, (ulong)gasLimit * gasPrice, spec); // TODO: I think we can skip this commit and decrease the tree operations this way _stateProvider.Commit(spec, txTracer.IsTracingState ? txTracer : null); long unspentGas = gasLimit - intrinsicGas; long spentGas = gasLimit; int stateSnapshot = _stateProvider.TakeSnapshot(); int storageSnapshot = _storageProvider.TakeSnapshot(); _stateProvider.SubtractFromBalance(sender, value, spec); byte statusCode = StatusCode.Failure; TransactionSubstate substate = null; try { if (transaction.IsContractCreation) { recipient = Address.OfContract(sender, _stateProvider.GetNonce(sender) - 1); if (transaction.IsSystem()) { recipient = transaction.SenderAddress; } if (_stateProvider.AccountExists(recipient)) { if ((_virtualMachine.GetCachedCodeInfo(recipient)?.MachineCode?.Length ?? 0) != 0 || _stateProvider.GetNonce(recipient) != 0) { if (_logger.IsTrace) { _logger.Trace($"Contract collision at {recipient}"); // the account already owns the contract with the code } throw new TransactionCollisionException(); } _stateProvider.UpdateStorageRoot(recipient, Keccak.EmptyTreeHash); } } bool isPrecompile = recipient.IsPrecompiled(spec); ExecutionEnvironment env = new ExecutionEnvironment(); env.Value = value; env.TransferValue = value; env.Sender = sender; env.CodeSource = recipient; env.ExecutingAccount = recipient; env.CurrentBlock = block; env.GasPrice = gasPrice; env.InputData = data ?? new byte[0]; env.CodeInfo = isPrecompile ? new CodeInfo(recipient) : machineCode == null?_virtualMachine.GetCachedCodeInfo(recipient) : new CodeInfo(machineCode); env.Originator = sender; ExecutionType executionType = transaction.IsContractCreation ? ExecutionType.Create : ExecutionType.Call; using (VmState state = new VmState(unspentGas, env, executionType, isPrecompile, true, false)) { substate = _virtualMachine.Run(state, txTracer); unspentGas = state.GasAvailable; } if (substate.ShouldRevert || substate.IsError) { if (_logger.IsTrace) { _logger.Trace("Restoring state from before transaction"); } _stateProvider.Restore(stateSnapshot); _storageProvider.Restore(storageSnapshot); } else { // tks: there is similar code fo contract creation from init and from CREATE // this may lead to inconsistencies (however it is tested extensively in blockchain tests) if (transaction.IsContractCreation) { long codeDepositGasCost = CodeDepositHandler.CalculateCost(substate.Output.Length, spec); if (unspentGas < codeDepositGasCost && spec.IsEip2Enabled) { throw new OutOfGasException(); } if (unspentGas >= codeDepositGasCost) { Keccak codeHash = _stateProvider.UpdateCode(substate.Output); _stateProvider.UpdateCodeHash(recipient, codeHash, spec); unspentGas -= codeDepositGasCost; } } foreach (Address toBeDestroyed in substate.DestroyList) { if (_logger.IsTrace) { _logger.Trace($"Destroying account {toBeDestroyed}"); } _stateProvider.DeleteAccount(toBeDestroyed); } statusCode = StatusCode.Success; } spentGas = Refund(gasLimit, unspentGas, substate, sender, gasPrice, spec); } catch (Exception ex) when(ex is EvmException || ex is OverflowException) // TODO: OverflowException? still needed? hope not { if (_logger.IsTrace) { _logger.Trace($"EVM EXCEPTION: {ex.GetType().Name}"); } _stateProvider.Restore(stateSnapshot); _storageProvider.Restore(storageSnapshot); } if (_logger.IsTrace) { _logger.Trace("Gas spent: " + spentGas); } Address gasBeneficiary = block.GasBeneficiary; if (statusCode == StatusCode.Failure || !(substate?.DestroyList.Contains(gasBeneficiary) ?? false)) { if (notSystemTransaction) { if (!_stateProvider.AccountExists(gasBeneficiary)) { _stateProvider.CreateAccount(gasBeneficiary, (ulong)spentGas * gasPrice); } else { _stateProvider.AddToBalance(gasBeneficiary, (ulong)spentGas * gasPrice, spec); } } } if (!readOnly) { _storageProvider.Commit(txTracer.IsTracingState ? txTracer : null); _stateProvider.Commit(spec, txTracer.IsTracingState ? txTracer : null); } else { _storageProvider.Reset(); _stateProvider.Reset(); } if (!readOnly && notSystemTransaction) { block.GasUsed += spentGas; } if (txTracer.IsTracingReceipt) { if (statusCode == StatusCode.Failure) { txTracer.MarkAsFailed(recipient, spentGas, (substate?.ShouldRevert ?? false) ? substate.Output : Bytes.Empty, substate?.Error); } else { txTracer.MarkAsSuccess(recipient, spentGas, substate.Output, substate.Logs.Any() ? substate.Logs.ToArray() : LogEntry.EmptyLogs); } } }