Exemple #1
0
        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);
                }
            }
        }