public long Estimate(Transaction tx, BlockHeader header, EstimateGasTracer gasTracer) { IReleaseSpec releaseSpec = _specProvider.GetSpec(header.Number + 1); long intrinsicGas = tx.GasLimit - gasTracer.IntrinsicGasAt; if (tx.GasLimit > header.GasLimit) { return(Math.Max(intrinsicGas, gasTracer.GasSpent + gasTracer.CalculateAdditionalGasRequired(tx, releaseSpec))); } tx.SenderAddress ??= Address.Zero; //If sender is not specified, use zero address. // Setting boundaries for binary search - determine lowest and highest gas can be used during the estimation: long leftBound = (gasTracer.GasSpent != 0 && gasTracer.GasSpent >= Transaction.BaseTxGasCost) ? gasTracer.GasSpent - 1 : Transaction.BaseTxGasCost - 1; long rightBound = (tx.GasLimit != 0 && tx.GasPrice >= Transaction.BaseTxGasCost) ? tx.GasLimit : header.GasLimit; UInt256 senderBalance = _stateProvider.GetBalance(tx.SenderAddress); // Calculate and return additional gas required in case of insufficient funds. if (tx.Value != UInt256.Zero && tx.Value >= senderBalance) { return(gasTracer.CalculateAdditionalGasRequired(tx, releaseSpec)); } // Execute binary search to find the optimal gas estimation. return(BinarySearchEstimate(leftBound, rightBound, rightBound, tx, header)); }
private void ApplyDaoTransition() { Address withdrawAccount = DaoData.DaoWithdrawalAccount; foreach (Address daoAccount in DaoData.DaoAccounts) { BigInteger balance = _stateProvider.GetBalance(daoAccount); _stateProvider.UpdateBalance(withdrawAccount, balance, Dao.Instance); _stateProvider.UpdateBalance(daoAccount, -balance, Dao.Instance); } }
private void ApplyDaoTransition() { Address withdrawAccount = DaoData.DaoWithdrawalAccount; foreach (Address daoAccount in DaoData.DaoAccounts) { UInt256 balance = _stateProvider.GetBalance(daoAccount); _stateProvider.AddToBalance(withdrawAccount, balance, Dao.Instance); _stateProvider.SubtractFromBalance(daoAccount, balance, Dao.Instance); } }
public AddingTxEventArgs CanAddTransaction(Block block, Transaction currentTx, IReadOnlySet <Transaction> transactionsInBlock, IStateProvider stateProvider) { AddingTxEventArgs args = new(transactionsInBlock.Count, currentTx, block, transactionsInBlock); long gasRemaining = block.Header.GasLimit - block.GasUsed; // No more gas available in block for any transactions, // the only case we have to really stop if (GasCostOf.Transaction > gasRemaining) { return(args.Set(TxAction.Stop, "Block full")); } if (currentTx.SenderAddress is null) { return(args.Set(TxAction.Skip, "Null sender")); } if (currentTx.GasLimit > gasRemaining) { return(args.Set(TxAction.Skip, $"Not enough gas in block, gas limit {currentTx.GasLimit} > {gasRemaining}")); } if (transactionsInBlock.Contains(currentTx)) { return(args.Set(TxAction.Skip, "Transaction already in block")); } IReleaseSpec spec = _specProvider.GetSpec(block.Number); if (stateProvider.IsInvalidContractSender(spec, currentTx.SenderAddress)) { return(args.Set(TxAction.Skip, $"Sender is contract")); } UInt256 expectedNonce = stateProvider.GetNonce(currentTx.SenderAddress); if (expectedNonce != currentTx.Nonce) { return(args.Set(TxAction.Skip, $"Invalid nonce - expected {expectedNonce}")); } UInt256 balance = stateProvider.GetBalance(currentTx.SenderAddress); if (!HasEnoughFounds(currentTx, balance, args, block, spec)) { return(args); } AddingTransaction?.Invoke(this, args); return(args); }
public BigInteger GetBalance(Address address) { try { _readerWriterLockSlim.EnterReadLock(); _stateProvider.StateRoot = _blockTree.Head.StateRoot; return(_stateProvider.GetBalance(address)); } finally { _readerWriterLockSlim.ExitReadLock(); } }
private bool ValidateSenderBalance(PublicEntry entry, ExecutionEnvironment env, ulong intrinsicGas, ITxTracer txTracer) { var senderBalance = _stateProvider.GetBalance(env.Sender); if (intrinsicGas * env.GasPrice + env.Value <= senderBalance) { return(true); } TraceLogInvalidTx(entry, $"INSUFFICIENT_SENDER_BALANCE: ({env.Sender})_BALANCE = {senderBalance.ToString()}"); QuickFail(entry, env, txTracer); return(false); }
private void ApplyDaoTransition(Block block) { if (_specProvider.DaoBlockNumber.HasValue && _specProvider.DaoBlockNumber.Value == block.Header.Number) { if (_logger.IsInfo) { _logger.Info("Applying the DAO transition"); } Address withdrawAccount = DaoData.DaoWithdrawalAccount; if (!_stateProvider.AccountExists(withdrawAccount)) { _stateProvider.CreateAccount(withdrawAccount, 0); } foreach (Address daoAccount in DaoData.DaoAccounts) { UInt256 balance = _stateProvider.GetBalance(daoAccount); _stateProvider.AddToBalance(withdrawAccount, balance, Dao.Instance); _stateProvider.SubtractFromBalance(daoAccount, balance, Dao.Instance); } } }
private List <string> RunAssertions(LegacyBlockchainTest test, Block headBlock, IStorageProvider storageProvider, IStateProvider stateProvider) { if (test.PostStateRoot != null) { return(test.PostStateRoot != stateProvider.StateRoot ? new List <string> { "state root mismatch" } : Enumerable.Empty <string>().ToList()); } TestBlockHeaderJson testHeaderJson = test.Blocks .Where(b => b.BlockHeader != null) .SingleOrDefault(b => new Keccak(b.BlockHeader.Hash) == headBlock.Hash)?.BlockHeader ?? test.GenesisBlockHeader; BlockHeader testHeader = JsonToBlockchainTest.Convert(testHeaderJson); List <string> differences = new List <string>(); var deletedAccounts = test.Pre.Where(pre => !test.PostState.ContainsKey(pre.Key)); foreach (KeyValuePair <Address, AccountState> deletedAccount in deletedAccounts) { if (stateProvider.AccountExists(deletedAccount.Key)) { differences.Add($"Pre state account {deletedAccount.Key} was not deleted as expected."); } } foreach (KeyValuePair <Address, AccountState> accountState in test.PostState) { int differencesBefore = differences.Count; if (differences.Count > 8) { Console.WriteLine("More than 8 differences..."); break; } bool accountExists = stateProvider.AccountExists(accountState.Key); BigInteger?balance = accountExists ? stateProvider.GetBalance(accountState.Key) : (BigInteger?)null; BigInteger?nonce = accountExists ? stateProvider.GetNonce(accountState.Key) : (BigInteger?)null; if (accountState.Value.Balance != balance) { differences.Add($"{accountState.Key} balance exp: {accountState.Value.Balance}, actual: {balance}, diff: {balance - accountState.Value.Balance}"); } if (accountState.Value.Nonce != nonce) { differences.Add($"{accountState.Key} nonce exp: {accountState.Value.Nonce}, actual: {nonce}"); } byte[] code = accountExists ? stateProvider.GetCode(accountState.Key) : new byte[0]; if (!Bytes.AreEqual(accountState.Value.Code, code)) { differences.Add($"{accountState.Key} code exp: {accountState.Value.Code?.Length}, actual: {code?.Length}"); } if (differences.Count != differencesBefore) { _logger.Info($"ACCOUNT STATE ({accountState.Key}) HAS DIFFERENCES"); } differencesBefore = differences.Count; KeyValuePair <UInt256, byte[]>[] clearedStorages = new KeyValuePair <UInt256, byte[]> [0]; if (test.Pre.ContainsKey(accountState.Key)) { clearedStorages = test.Pre[accountState.Key].Storage.Where(s => !accountState.Value.Storage.ContainsKey(s.Key)).ToArray(); } foreach (KeyValuePair <UInt256, byte[]> clearedStorage in clearedStorages) { byte[] value = !stateProvider.AccountExists(accountState.Key) ? Bytes.Empty : storageProvider.Get(new StorageAddress(accountState.Key, clearedStorage.Key)); if (!value.IsZero()) { differences.Add($"{accountState.Key} storage[{clearedStorage.Key}] exp: 0x00, actual: {value.ToHexString(true)}"); } } foreach (KeyValuePair <UInt256, byte[]> storageItem in accountState.Value.Storage) { byte[] value = !stateProvider.AccountExists(accountState.Key) ? Bytes.Empty : storageProvider.Get(new StorageAddress(accountState.Key, storageItem.Key)) ?? new byte[0]; if (!Bytes.AreEqual(storageItem.Value, value)) { differences.Add($"{accountState.Key} storage[{storageItem.Key}] exp: {storageItem.Value.ToHexString(true)}, actual: {value.ToHexString(true)}"); } } if (differences.Count != differencesBefore) { _logger.Info($"ACCOUNT STORAGE ({accountState.Key}) HAS DIFFERENCES"); } } BigInteger gasUsed = headBlock.Header.GasUsed; if ((testHeader?.GasUsed ?? 0) != gasUsed) { differences.Add($"GAS USED exp: {testHeader?.GasUsed ?? 0}, actual: {gasUsed}"); } if (headBlock.Transactions.Any() && testHeader.Bloom.ToString() != headBlock.Header.Bloom.ToString()) { differences.Add($"BLOOM exp: {testHeader.Bloom}, actual: {headBlock.Header.Bloom}"); } if (testHeader.StateRoot != stateProvider.StateRoot) { differences.Add($"STATE ROOT exp: {testHeader.StateRoot}, actual: {stateProvider.StateRoot}"); } if (testHeader.TxRoot != headBlock.Header.TxRoot) { differences.Add($"TRANSACTIONS ROOT exp: {testHeader.TxRoot}, actual: {headBlock.Header.TxRoot}"); } if (testHeader.ReceiptsRoot != headBlock.Header.ReceiptsRoot) { differences.Add($"RECEIPT ROOT exp: {testHeader.ReceiptsRoot}, actual: {headBlock.Header.ReceiptsRoot}"); } if (test.LastBlockHash != headBlock.Hash) { differences.Add($"LAST BLOCK HASH exp: {test.LastBlockHash}, actual: {headBlock.Hash}"); } foreach (string difference in differences) { _logger.Info(difference); } return(differences); }
public BigInteger GetBalance(Address address) { return(_stateProvider.GetBalance(address)); }
private void RunAssertions(BlockchainTest test, Block headBlock, IStorageProvider storageProvider, IStateProvider stateProvider) { TestBlockHeaderJson testHeaderJson = test.Blocks .Where(b => b.BlockHeader != null) .SingleOrDefault(b => new Keccak(b.BlockHeader.Hash) == headBlock.Hash)?.BlockHeader ?? test.GenesisBlockHeader; BlockHeader testHeader = Convert(testHeaderJson); List <string> differences = new List <string>(); foreach (KeyValuePair <Address, AccountState> accountState in test.PostState) { int differencesBefore = differences.Count; if (differences.Count > 8) { Console.WriteLine("More than 8 differences..."); break; } bool accountExists = stateProvider.AccountExists(accountState.Key); BigInteger?balance = accountExists ? stateProvider.GetBalance(accountState.Key) : (BigInteger?)null; BigInteger?nonce = accountExists ? stateProvider.GetNonce(accountState.Key) : (BigInteger?)null; if (accountState.Value.Balance != balance) { differences.Add($"{accountState.Key} balance exp: {accountState.Value.Balance}, actual: {balance}, diff: {balance - accountState.Value.Balance}"); } if (accountState.Value.Nonce != nonce) { differences.Add($"{accountState.Key} nonce exp: {accountState.Value.Nonce}, actual: {nonce}"); } byte[] code = accountExists ? stateProvider.GetCode(accountState.Key) : new byte[0]; if (!Bytes.UnsafeCompare(accountState.Value.Code, code)) { differences.Add($"{accountState.Key} code exp: {accountState.Value.Code?.Length}, actual: {code?.Length}"); } if (differences.Count != differencesBefore) { _logger.Info($"ACCOUNT STATE ({accountState.Key}) HAS DIFFERENCES"); } differencesBefore = differences.Count; foreach (KeyValuePair <BigInteger, byte[]> storageItem in accountState.Value.Storage) { byte[] value = storageProvider.Get(new StorageAddress(accountState.Key, storageItem.Key)) ?? new byte[0]; if (!Bytes.UnsafeCompare(storageItem.Value, value)) { differences.Add($"{accountState.Key} storage[{storageItem.Key}] exp: {Hex.FromBytes(storageItem.Value, true)}, actual: {Hex.FromBytes(value, true)}"); } } if (differences.Count != differencesBefore) { _logger.Info($"ACCOUNT STORAGE ({accountState.Key}) HAS DIFFERENCES"); } } BigInteger gasUsed = headBlock.Header.GasUsed; if ((testHeader?.GasUsed ?? 0) != gasUsed) { differences.Add($"GAS USED exp: {testHeader?.GasUsed ?? 0}, actual: {gasUsed}"); } if (headBlock.Transactions.Any() && testHeader.Bloom.ToString() != headBlock.Header.Bloom.ToString()) { differences.Add($"BLOOM exp: {testHeader.Bloom}, actual: {headBlock.Header.Bloom}"); } if (testHeader.StateRoot != stateProvider.StateRoot) { differences.Add($"STATE ROOT exp: {testHeader.StateRoot}, actual: {stateProvider.StateRoot}"); } if (testHeader.TransactionsRoot != headBlock.Header.TransactionsRoot) { differences.Add($"TRANSACTIONS ROOT exp: {testHeader.TransactionsRoot}, actual: {headBlock.Header.TransactionsRoot}"); } if (testHeader.ReceiptsRoot != headBlock.Header.ReceiptsRoot) { differences.Add($"RECEIPT ROOT exp: {testHeader.ReceiptsRoot}, actual: {headBlock.Header.ReceiptsRoot}"); } if (test.LastBlockHash != headBlock.Hash) { differences.Add($"LAST BLOCK HASH exp: {test.LastBlockHash}, actual: {headBlock.Hash}"); } foreach (string difference in differences) { _logger.Info(difference); } Assert.Zero(differences.Count, "differences"); }
public IEnumerable <Transaction> SelectTransactions(long gasLimit) { UInt256 GetCurrentNonce(IDictionary <Address, UInt256> noncesDictionary, Address address) { if (!noncesDictionary.TryGetValue(address, out var nonce)) { noncesDictionary[address] = nonce = _stateProvider.GetNonce(address); } return(nonce); } UInt256 GetRemainingBalance(IDictionary <Address, UInt256> balances, Address address) { if (!balances.TryGetValue(address, out var balance)) { balances[address] = balance = _stateProvider.GetBalance(address); } return(balance); } bool HasEnoughFounds(IDictionary <Address, UInt256> balances, Transaction transaction) { var balance = GetRemainingBalance(balances, transaction.SenderAddress); var transactionPotentialCost = transaction.GasPrice * (ulong)transaction.GasLimit + transaction.Value; if (balance < transactionPotentialCost) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction - transaction cost ({transactionPotentialCost}) is higher than sender balance ({balance})."); } return(false); } else { balances[transaction.SenderAddress] = balance - transactionPotentialCost; return(true); } } var pendingTransactions = _transactionPool.GetPendingTransactions(); var transactions = pendingTransactions.OrderBy(t => t.Nonce).ThenByDescending(t => t.GasPrice).ThenBy(t => t.GasLimit); IDictionary <Address, UInt256> remainingBalance = new Dictionary <Address, UInt256>(); Dictionary <Address, UInt256> nonces = new Dictionary <Address, UInt256>(); List <Transaction> selected = new List <Transaction>(); long gasRemaining = gasLimit; if (_logger.IsDebug) { _logger.Debug($"Collecting pending transactions at min gas price {_minGasPriceForMining} and block gas limit {gasRemaining}."); } foreach (Transaction transaction in transactions) { if (transaction.SenderAddress == null) { if (_logger.IsTrace) { _logger.Trace("Rejecting null sender pending transaction."); } continue; } if (transaction.GasPrice < _minGasPriceForMining) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction - gas price ({transaction.GasPrice}) too low (min gas price: {_minGasPriceForMining}."); } continue; } if (GetCurrentNonce(nonces, transaction.SenderAddress) != transaction.Nonce) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction based on nonce."); } continue; } if (transaction.GasLimit > gasRemaining) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction - gas limit ({transaction.GasLimit}) more than remaining gas ({gasRemaining})."); } break; } if (!HasEnoughFounds(remainingBalance, transaction)) { continue; } selected.Add(transaction); nonces[transaction.SenderAddress] = transaction.Nonce + 1; gasRemaining -= transaction.GasLimit; } if (_logger.IsDebug) { _logger.Debug($"Collected {selected.Count} out of {pendingTransactions.Length} pending transactions."); } return(selected); }
private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTracer, ExecutionOptions executionOptions) { bool eip658NotEnabled = !_specProvider.GetSpec(block.Number).IsEip658Enabled; // restore is CallAndRestore - previous call, we will restore state after the execution bool restore = (executionOptions & ExecutionOptions.Restore) == ExecutionOptions.Restore; bool noValidation = (executionOptions & ExecutionOptions.NoValidation) == ExecutionOptions.NoValidation; // commit - is for standard execute, we will commit thee state after execution bool commit = (executionOptions & ExecutionOptions.Commit) == ExecutionOptions.Commit || eip658NotEnabled; //!commit - is for build up during block production, we won't commit state after each transaction to support rollbacks //we commit only after all block is constructed bool notSystemTransaction = !transaction.IsSystem(); bool deleteCallerAccount = false; IReleaseSpec spec = _specProvider.GetSpec(block.Number); if (!notSystemTransaction) { spec = new SystemTransactionReleaseSpec(spec); } UInt256 value = transaction.Value; if (!transaction.TryCalculatePremiumPerGas(block.BaseFeePerGas, out UInt256 premiumPerGas) && !noValidation) { TraceLogInvalidTx(transaction, "MINER_PREMIUM_IS_NEGATIVE"); QuickFail(transaction, block, txTracer, eip658NotEnabled, "miner premium is negative"); return; } UInt256 effectiveGasPrice = transaction.CalculateEffectiveGasPrice(spec.IsEip1559Enabled, block.BaseFeePerGas); long gasLimit = transaction.GasLimit; byte[] machineCode = transaction.IsContractCreation ? transaction.Data : null; byte[] data = transaction.IsMessageCall ? transaction.Data : Array.Empty <byte>(); Address?caller = transaction.SenderAddress; if (_logger.IsTrace) { _logger.Trace($"Executing tx {transaction.Hash}"); } if (caller is null) { TraceLogInvalidTx(transaction, "SENDER_NOT_SPECIFIED"); QuickFail(transaction, block, txTracer, eip658NotEnabled, "sender not specified"); return; } if (!noValidation && _stateProvider.IsInvalidContractSender(spec, caller)) { TraceLogInvalidTx(transaction, "SENDER_IS_CONTRACT"); QuickFail(transaction, block, txTracer, eip658NotEnabled, "sender has deployed code"); 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, eip658NotEnabled, "gas limit below intrinsic gas"); return; } if (!noValidation && gasLimit > block.GasLimit - block.GasUsed) { TraceLogInvalidTx(transaction, $"BLOCK_GAS_LIMIT_EXCEEDED {gasLimit} > {block.GasLimit} - {block.GasUsed}"); QuickFail(transaction, block, txTracer, eip658NotEnabled, "block gas limit exceeded"); return; } } if (!_stateProvider.AccountExists(caller)) { // hacky fix for the potential recovery issue if (transaction.Signature != null) { transaction.SenderAddress = _ecdsa.RecoverAddress(transaction, !spec.ValidateChainId); } if (caller != transaction.SenderAddress) { if (_logger.IsWarn) { _logger.Warn( $"TX recovery issue fixed - tx was coming with sender {caller} and the now it recovers to {transaction.SenderAddress}"); } caller = transaction.SenderAddress; } else { TraceLogInvalidTx(transaction, $"SENDER_ACCOUNT_DOES_NOT_EXIST {caller}"); if (!commit || noValidation || effectiveGasPrice == UInt256.Zero) { deleteCallerAccount = !commit || restore; _stateProvider.CreateAccount(caller, UInt256.Zero); } } if (caller is null) { throw new InvalidDataException( $"Failed to recover sender address on tx {transaction.Hash} when previously recovered sender account did not exist."); } } UInt256 senderReservedGasPayment = noValidation ? UInt256.Zero : (ulong)gasLimit * effectiveGasPrice; if (notSystemTransaction) { UInt256 senderBalance = _stateProvider.GetBalance(caller); if (!noValidation && ((ulong)intrinsicGas * effectiveGasPrice + value > senderBalance || senderReservedGasPayment + value > senderBalance)) { TraceLogInvalidTx(transaction, $"INSUFFICIENT_SENDER_BALANCE: ({caller})_BALANCE = {senderBalance}"); QuickFail(transaction, block, txTracer, eip658NotEnabled, "insufficient sender balance"); return; } if (!noValidation && spec.IsEip1559Enabled && !transaction.IsFree() && senderBalance < (UInt256)transaction.GasLimit * transaction.MaxFeePerGas + value) { TraceLogInvalidTx(transaction, $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({caller})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {transaction.MaxFeePerGas}"); QuickFail(transaction, block, txTracer, eip658NotEnabled, "insufficient MaxFeePerGas for sender balance"); return; } if (transaction.Nonce != _stateProvider.GetNonce(caller)) { TraceLogInvalidTx(transaction, $"WRONG_TRANSACTION_NONCE: {transaction.Nonce} (expected {_stateProvider.GetNonce(caller)})"); QuickFail(transaction, block, txTracer, eip658NotEnabled, "wrong transaction nonce"); return; } _stateProvider.IncrementNonce(caller); } _stateProvider.SubtractFromBalance(caller, senderReservedGasPayment, spec); if (commit) { _stateProvider.Commit(spec, txTracer.IsTracingState ? txTracer : NullTxTracer.Instance); } long unspentGas = gasLimit - intrinsicGas; long spentGas = gasLimit; Snapshot snapshot = _worldState.TakeSnapshot(); _stateProvider.SubtractFromBalance(caller, value, spec); byte statusCode = StatusCode.Failure; TransactionSubstate substate = null; Address?recipientOrNull = null; try { Address?recipient = transaction.GetRecipient(transaction.IsContractCreation ? _stateProvider.GetNonce(caller) : 0); if (transaction.IsContractCreation) { // if transaction is a contract creation then recipient address is the contract deployment address Address contractAddress = recipient; PrepareAccountForContractDeployment(contractAddress !, spec); } if (recipient == null) { // this transaction is not a contract creation so it should have the recipient known and not null throw new InvalidDataException("Recipient has not been resolved properly before tx execution"); } recipientOrNull = recipient; ExecutionEnvironment env = new(); env.TxExecutionContext = new TxExecutionContext(block, caller, effectiveGasPrice); env.Value = value; env.TransferValue = value; env.Caller = caller; env.CodeSource = recipient; env.ExecutingAccount = recipient; env.InputData = data ?? Array.Empty <byte>(); env.CodeInfo = machineCode == null ? _virtualMachine.GetCachedCodeInfo(_worldState, recipient, spec) : new CodeInfo(machineCode); ExecutionType executionType = transaction.IsContractCreation ? ExecutionType.Create : ExecutionType.Call; using (EvmState state = new(unspentGas, env, executionType, true, snapshot, false)) { if (spec.UseTxAccessLists) { state.WarmUp(transaction.AccessList); // eip-2930 } if (spec.UseHotAndColdStorage) { state.WarmUp(caller); // eip-2929 state.WarmUp(recipient); // eip-2929 } substate = _virtualMachine.Run(state, _worldState, txTracer); unspentGas = state.GasAvailable; if (txTracer.IsTracingAccess) { txTracer.ReportAccess(state.AccessedAddresses, state.AccessedStorageCells); } } if (substate.ShouldRevert || substate.IsError) { if (_logger.IsTrace) { _logger.Trace("Restoring state from before transaction"); } _worldState.Restore(snapshot); } 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.ChargeForTopLevelCreate) { throw new OutOfGasException(); } if (CodeDepositHandler.CodeIsInvalid(spec, substate.Output)) { throw new InvalidCodeException(); } 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}"); } _storageProvider.ClearStorage(toBeDestroyed); _stateProvider.DeleteAccount(toBeDestroyed); if (txTracer.IsTracingRefunds) { txTracer.ReportRefund(RefundOf.Destroy(spec.IsEip3529Enabled)); } } statusCode = StatusCode.Success; } spentGas = Refund(gasLimit, unspentGas, substate, caller, effectiveGasPrice, 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}"); } _worldState.Restore(snapshot); } if (_logger.IsTrace) { _logger.Trace("Gas spent: " + spentGas); } Address gasBeneficiary = block.GasBeneficiary; bool gasBeneficiaryNotDestroyed = substate?.DestroyList.Contains(gasBeneficiary) != true; if (statusCode == StatusCode.Failure || gasBeneficiaryNotDestroyed) { if (notSystemTransaction) { UInt256 fees = (ulong)spentGas * premiumPerGas; if (_stateProvider.AccountExists(gasBeneficiary)) { _stateProvider.AddToBalance(gasBeneficiary, fees, spec); } else { _stateProvider.CreateAccount(gasBeneficiary, fees); } if (!transaction.IsFree() && spec.IsEip1559Enabled && spec.Eip1559FeeCollector is not null) { UInt256 burntFees = (ulong)spentGas * block.BaseFeePerGas; if (!burntFees.IsZero) { if (_stateProvider.AccountExists(spec.Eip1559FeeCollector)) { _stateProvider.AddToBalance(spec.Eip1559FeeCollector, burntFees, spec); } else { _stateProvider.CreateAccount(spec.Eip1559FeeCollector, burntFees); } } } } } if (restore) { _storageProvider.Reset(); _stateProvider.Reset(); if (deleteCallerAccount) { _stateProvider.DeleteAccount(caller); } else { _stateProvider.AddToBalance(caller, senderReservedGasPayment, spec); if (notSystemTransaction) { _stateProvider.DecrementNonce(caller); } _stateProvider.Commit(spec); } } else if (commit) { _storageProvider.Commit(txTracer.IsTracingState ? txTracer : NullStorageTracer.Instance); _stateProvider.Commit(spec, txTracer.IsTracingState ? txTracer : NullStateTracer.Instance); } if (!noValidation && notSystemTransaction) { block.GasUsed += spentGas; } if (txTracer.IsTracingReceipt) { Keccak stateRoot = null; if (eip658NotEnabled) { _stateProvider.RecalculateStateRoot(); stateRoot = _stateProvider.StateRoot; } if (statusCode == StatusCode.Failure) { txTracer.MarkAsFailed(recipientOrNull, spentGas, (substate?.ShouldRevert ?? false) ? substate.Output.ToArray() : Array.Empty <byte>(), substate?.Error, stateRoot); } else { txTracer.MarkAsSuccess(recipientOrNull, spentGas, substate.Output.ToArray(), substate.Logs.Any() ? substate.Logs.ToArray() : Array.Empty <LogEntry>(), stateRoot); } } }
public IEnumerable <Transaction> SelectTransactions(long gasLimit) { UInt256 GetCurrentNonce(IDictionary <Address, UInt256> noncesDictionary, Address address) { if (!noncesDictionary.TryGetValue(address, out var nonce)) { noncesDictionary[address] = nonce = _stateProvider.GetNonce(address); } return(nonce); } UInt256 GetRemainingBalance(IDictionary <Address, UInt256> balances, Address address) { if (!balances.TryGetValue(address, out var balance)) { balances[address] = balance = _stateProvider.GetBalance(address); } return(balance); } bool HasEnoughFounds(IDictionary <Address, UInt256> balances, Transaction transaction) { var balance = GetRemainingBalance(balances, transaction.SenderAddress); var transactionPotentialCost = transaction.GasPrice * (ulong)transaction.GasLimit + transaction.Value; if (balance < transactionPotentialCost) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction - transaction cost ({transactionPotentialCost}) is higher than sender balance ({balance})."); } return(false); } balances[transaction.SenderAddress] = balance - transactionPotentialCost; return(true); } var pendingTransactions = _transactionPool.GetPendingTransactions(); var transactions = pendingTransactions.OrderBy(t => t.Nonce).ThenByDescending(t => t.GasPrice).ThenBy(t => t.GasLimit); IDictionary <Address, UInt256> remainingBalance = new Dictionary <Address, UInt256>(); Dictionary <Address, UInt256> nonces = new Dictionary <Address, UInt256>(); List <Transaction> selected = new List <Transaction>(); long gasRemaining = gasLimit; if (_logger.IsDebug) { _logger.Debug($"Collecting pending transactions at min gas price {_minGasPriceForMining} and block gas limit {gasRemaining}."); } foreach (Transaction tx in transactions) { if (gasRemaining < Transaction.BaseTxGasCost) { continue; } if (tx.GasLimit > gasRemaining) { if (_logger.IsInfo) { _logger.Info($"Rejecting (tx gas limit {tx.GasLimit} above remaining block gas {gasRemaining}) {tx.ToShortString()}"); } continue; } if (tx.SenderAddress == null) { _transactionPool.RemoveTransaction(tx.Hash, 0); if (_logger.IsInfo) { _logger.Info($"Rejecting (null sender) {tx.ToShortString()}"); } continue; } if (tx.GasPrice < _minGasPriceForMining) { if (_logger.IsInfo) { _logger.Info($"Rejecting (gas price too low - min gas price: {_minGasPriceForMining}) {tx.ToShortString()}"); } continue; } UInt256 expectedNonce = GetCurrentNonce(nonces, tx.SenderAddress); if (expectedNonce != tx.Nonce) { if (tx.Nonce < expectedNonce) { _transactionPool.RemoveTransaction(tx.Hash, 0); } if (tx.Nonce > expectedNonce + 16) { _transactionPool.RemoveTransaction(tx.Hash, 0); } if (_logger.IsInfo) { _logger.Info($"Rejecting (invalid nonce - expected {expectedNonce}) {tx.ToShortString()}"); } continue; } if (!HasEnoughFounds(remainingBalance, tx)) { if (_logger.IsInfo) { _logger.Info($"Rejecting (sender balance too low) {tx.ToShortString()}"); } continue; } selected.Add(tx); nonces[tx.SenderAddress] = tx.Nonce + 1; gasRemaining -= tx.GasLimit; } if (_logger.IsDebug) { _logger.Debug($"Collected {selected.Count} out of {pendingTransactions.Length} pending transactions."); } return(selected); }
public TransactionReceipt Execute( Transaction transaction, BlockHeader block) { TransactionTrace trace = null; if (_tracer.IsTracingEnabled) { trace = new TransactionTrace(); } IReleaseSpec spec = _specProvider.GetSpec(block.Number); Address recipient = transaction.To; BigInteger value = transaction.Value; BigInteger gasPrice = transaction.GasPrice; long gasLimit = (long)transaction.GasLimit; byte[] machineCode = transaction.Init; byte[] data = transaction.Data ?? Bytes.Empty; Address sender = transaction.SenderAddress; if (_logger.IsDebugEnabled) { _logger.Debug($"SPEC: {spec.GetType().Name}"); _logger.Debug("HASH: " + transaction.Hash); _logger.Debug("IS_CONTRACT_CREATION: " + transaction.IsContractCreation); _logger.Debug("IS_MESSAGE_CALL: " + transaction.IsMessageCall); _logger.Debug("IS_TRANSFER: " + transaction.IsTransfer); _logger.Debug("SENDER: " + sender); _logger.Debug("TO: " + transaction.To); _logger.Debug("GAS LIMIT: " + transaction.GasLimit); _logger.Debug("GAS PRICE: " + transaction.GasPrice); _logger.Debug("VALUE: " + transaction.Value); _logger.Debug("DATA_LENGTH: " + (transaction.Data?.Length ?? 0)); _logger.Debug("NONCE: " + transaction.Nonce); } if (sender == null) { if (_logger.IsDebugEnabled) { _logger.Debug($"SENDER_NOT_SPECIFIED"); } return(GetNullReceipt(block, 0L)); } long intrinsicGas = _intrinsicGasCalculator.Calculate(transaction, spec); if (_logger.IsDebugEnabled) { _logger.Debug("INTRINSIC GAS: " + intrinsicGas); } if (gasLimit < intrinsicGas) { if (_logger.IsDebugEnabled) { _logger.Debug($"GAS_LIMIT_BELOW_INTRINSIC_GAS {gasLimit} < {intrinsicGas}"); } return(GetNullReceipt(block, 0L)); } if (gasLimit > block.GasLimit - block.GasUsed) { if (_logger.IsDebugEnabled) { _logger.Debug($"BLOCK_GAS_LIMIT_EXCEEDED {gasLimit} > {block.GasLimit} - {block.GasUsed}"); } return(GetNullReceipt(block, 0L)); } if (!_stateProvider.AccountExists(sender)) { if (_logger.IsDebugEnabled) { _logger.Debug($"SENDER_ACCOUNT_DOES_NOT_EXIST {sender}"); } _stateProvider.CreateAccount(sender, 0); } BigInteger senderBalance = _stateProvider.GetBalance(sender); if (intrinsicGas * gasPrice + value > senderBalance) { if (_logger.IsDebugEnabled) { _logger.Debug($"INSUFFICIENT_SENDER_BALANCE: ({sender})b = {senderBalance}"); } return(GetNullReceipt(block, 0L)); } if (transaction.Nonce != _stateProvider.GetNonce(sender)) { if (_logger.IsDebugEnabled) { _logger.Debug($"WRONG_TRANSACTION_NONCE: {transaction.Nonce} (expected {_stateProvider.GetNonce(sender)})"); } return(GetNullReceipt(block, 0L)); } _stateProvider.IncrementNonce(sender); _stateProvider.UpdateBalance(sender, -new BigInteger(gasLimit) * gasPrice, spec); _stateProvider.Commit(spec); long unspentGas = gasLimit - intrinsicGas; long spentGas = gasLimit; List <LogEntry> logEntries = new List <LogEntry>(); if (transaction.IsContractCreation) { Rlp addressBaseRlp = Rlp.Encode( Rlp.Encode(sender), Rlp.Encode(_stateProvider.GetNonce(sender) - 1)); Keccak addressBaseKeccak = Keccak.Compute(addressBaseRlp); recipient = new Address(addressBaseKeccak); } int snapshot = _stateProvider.TakeSnapshot(); int storageSnapshot = _storageProvider.TakeSnapshot(); _stateProvider.UpdateBalance(sender, -value, spec); byte statusCode = StatusCode.Failure; HashSet <Address> destroyedAccounts = new HashSet <Address>(); try { if (transaction.IsContractCreation) { // TODO: review tests around it as it fails on Ropsten 230881 when we throw an exception if (_stateProvider.AccountExists(recipient) && !_stateProvider.IsEmptyAccount(recipient)) { // TODO: review // throw new TransactionCollisionException(); } } if (transaction.IsTransfer) // TODO: this is never called and wrong, to be removed { _stateProvider.UpdateBalance(sender, -value, spec); _stateProvider.UpdateBalance(recipient, value, spec); statusCode = StatusCode.Success; } else { bool isPrecompile = recipient.IsPrecompiled(spec); ExecutionEnvironment env = new ExecutionEnvironment(); env.Value = value; env.TransferValue = value; env.Sender = sender; 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 = isPrecompile ? ExecutionType.DirectPrecompile : transaction.IsContractCreation ? ExecutionType.DirectCreate : ExecutionType.Transaction; TransactionSubstate substate; byte[] output; using (EvmState state = new EvmState(unspentGas, env, executionType, false)) { (output, substate) = _virtualMachine.Run(state, spec, trace); unspentGas = state.GasAvailable; } if (substate.ShouldRevert) { if (_logger.IsDebugEnabled) { _logger.Debug("REVERTING"); } logEntries.Clear(); destroyedAccounts.Clear(); _stateProvider.Restore(snapshot); _storageProvider.Restore(storageSnapshot); } else { if (transaction.IsContractCreation) { long codeDepositGasCost = output.Length * GasCostOf.CodeDeposit; if (spec.IsEip170Enabled && output.Length > 0x6000) { codeDepositGasCost = long.MaxValue; } if (unspentGas < codeDepositGasCost && spec.IsEip2Enabled) { throw new OutOfGasException(); } if (unspentGas >= codeDepositGasCost) { Keccak codeHash = _stateProvider.UpdateCode(output); _stateProvider.UpdateCodeHash(recipient, codeHash, spec); unspentGas -= codeDepositGasCost; } } logEntries.AddRange(substate.Logs); foreach (Address toBeDestroyed in substate.DestroyList) { destroyedAccounts.Add(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.IsDebugEnabled) { _logger.Debug($"EVM EXCEPTION: {ex.GetType().Name}"); } logEntries.Clear(); destroyedAccounts.Clear(); _stateProvider.Restore(snapshot); _storageProvider.Restore(storageSnapshot); } foreach (Address toBeDestroyed in destroyedAccounts) { if (_logger.IsDebugEnabled) { _logger.Debug($"DESTROYING: {toBeDestroyed}"); } _stateProvider.DeleteAccount(toBeDestroyed); } if (_logger.IsDebugEnabled) { _logger.Debug("GAS SPENT: " + spentGas); } if (!destroyedAccounts.Contains(block.Beneficiary)) { if (!_stateProvider.AccountExists(block.Beneficiary)) { _stateProvider.CreateAccount(block.Beneficiary, spentGas * gasPrice); } else { _stateProvider.UpdateBalance(block.Beneficiary, spentGas * gasPrice, spec); } } _storageProvider.Commit(spec); _stateProvider.Commit(spec); block.GasUsed += spentGas; if (_tracer.IsTracingEnabled) { trace.Gas = spentGas; _tracer.SaveTrace(Transaction.CalculateHash(transaction), trace); } return(BuildTransactionReceipt(statusCode, logEntries.Any() ? logEntries.ToArray() : LogEntry.EmptyLogs, block.GasUsed, recipient)); }
public void Execute(Transaction transaction, BlockHeader block, ITxTracer txTracer, bool readOnly) { IReleaseSpec spec = _specProvider.GetSpec(block.Number); 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"); if (txTracer.IsTracingReceipt) { txTracer.MarkAsFailed(recipient, (long)transaction.GasLimit); } return; } long intrinsicGas = _intrinsicGasCalculator.Calculate(transaction, spec); if (_logger.IsTrace) { _logger.Trace($"Intrinsic gas calculated for {transaction.Hash}: " + intrinsicGas); } if (gasLimit < intrinsicGas) { TraceLogInvalidTx(transaction, $"GAS_LIMIT_BELOW_INTRINSIC_GAS {gasLimit} < {intrinsicGas}"); if (txTracer.IsTracingReceipt) { txTracer.MarkAsFailed(recipient, (long)transaction.GasLimit); } return; } if (gasLimit > block.GasLimit - block.GasUsed) { TraceLogInvalidTx(transaction, $"BLOCK_GAS_LIMIT_EXCEEDED {gasLimit} > {block.GasLimit} - {block.GasUsed}"); if (txTracer.IsTracingReceipt) { txTracer.MarkAsFailed(recipient, (long)transaction.GasLimit); } return; } if (!_stateProvider.AccountExists(sender)) { TraceLogInvalidTx(transaction, $"SENDER_ACCOUNT_DOES_NOT_EXIST {sender}"); if (gasPrice == UInt256.Zero) { _stateProvider.CreateAccount(sender, UInt256.Zero); } } UInt256 senderBalance = _stateProvider.GetBalance(sender); if ((ulong)intrinsicGas * gasPrice + value > senderBalance) { TraceLogInvalidTx(transaction, $"INSUFFICIENT_SENDER_BALANCE: ({sender})_BALANCE = {senderBalance}"); if (txTracer.IsTracingReceipt) { txTracer.MarkAsFailed(recipient, (long)transaction.GasLimit); } return; } if (transaction.Nonce != _stateProvider.GetNonce(sender)) { TraceLogInvalidTx(transaction, $"WRONG_TRANSACTION_NONCE: {transaction.Nonce} (expected {_stateProvider.GetNonce(sender)})"); if (txTracer.IsTracingReceipt) { txTracer.MarkAsFailed(recipient, (long)transaction.GasLimit); } 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(_specProvider.GetSpec(block.Number), 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 (_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.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 (EvmState state = new EvmState(unspentGas, env, executionType, isPrecompile, true, false)) { substate = _virtualMachine.Run(state, spec, 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 = substate.Output.Length * GasCostOf.CodeDeposit; if (spec.IsEip170Enabled && substate.Output.Length > 0x6000) { codeDepositGasCost = long.MaxValue; } 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 (!_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) { block.GasUsed += spentGas; } if (txTracer.IsTracingReceipt) { if (statusCode == StatusCode.Failure) { txTracer.MarkAsFailed(recipient, (long)transaction.GasLimit); } else { txTracer.MarkAsSuccess(recipient, spentGas, substate.Output, substate.Logs.Any() ? substate.Logs.ToArray() : LogEntry.EmptyLogs); } } }
private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTracer, bool isCall) { bool notSystemTransaction = !transaction.IsSystem(); bool wasSenderAccountCreatedInsideACall = false; 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 = transaction.GasLimit; byte[] machineCode = transaction.Init; byte[] data = transaction.Data ?? Array.Empty <byte>(); 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, "sender not specified"); 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, "gas limit below intrinsic gas"); return; } if (!isCall && gasLimit > block.GasLimit - block.GasUsed) { TraceLogInvalidTx(transaction, $"BLOCK_GAS_LIMIT_EXCEEDED {gasLimit} > {block.GasLimit} - {block.GasUsed}"); QuickFail(transaction, block, txTracer, "block gas limit exceeded"); return; } } if (!_stateProvider.AccountExists(sender)) { // hacky fix for the potential recovery issue if (transaction.Signature != null) { transaction.SenderAddress = _ecdsa.RecoverAddress(transaction, !spec.ValidateChainId); } 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 (isCall || gasPrice == UInt256.Zero) { wasSenderAccountCreatedInsideACall = isCall; _stateProvider.CreateAccount(sender, UInt256.Zero); } } } if (notSystemTransaction) { UInt256 senderBalance = _stateProvider.GetBalance(sender); if (!isCall && (ulong)intrinsicGas * gasPrice + value > senderBalance) { TraceLogInvalidTx(transaction, $"INSUFFICIENT_SENDER_BALANCE: ({sender})_BALANCE = {senderBalance}"); QuickFail(transaction, block, txTracer, "insufficient sender balance"); return; } if (transaction.Nonce != _stateProvider.GetNonce(sender)) { TraceLogInvalidTx(transaction, $"WRONG_TRANSACTION_NONCE: {transaction.Nonce} (expected {_stateProvider.GetNonce(sender)})"); QuickFail(transaction, block, txTracer, "wrong transaction nonce"); return; } _stateProvider.IncrementNonce(sender); } UInt256 senderReservedGasPayment = isCall ? UInt256.Zero : (ulong)gasLimit * gasPrice; _stateProvider.SubtractFromBalance(sender, senderReservedGasPayment, spec); _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 = transaction.IsSystem() ? transaction.SenderAddress : ContractAddress.From(sender, _stateProvider.GetNonce(sender) - 1); if (_stateProvider.AccountExists(recipient)) { if (_virtualMachine.GetCachedCodeInfo(recipient, spec).MachineCode.Length != 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); } } 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 ?? Array.Empty <byte>(); env.CodeInfo = machineCode == null?_virtualMachine.GetCachedCodeInfo(recipient, spec) : new CodeInfo(machineCode); env.Originator = sender; ExecutionType executionType = transaction.IsContractCreation ? ExecutionType.Create : ExecutionType.Call; using (EvmState state = new EvmState(unspentGas, env, executionType, 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}"); } _storageProvider.ClearStorage(toBeDestroyed); _stateProvider.DeleteAccount(toBeDestroyed); if (txTracer.IsTracingRefunds) { txTracer.ReportRefund(RefundOf.Destroy); } } 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 (!isCall) { _storageProvider.Commit(txTracer.IsTracingState ? txTracer : null); _stateProvider.Commit(spec, txTracer.IsTracingState ? txTracer : null); } else { _storageProvider.Reset(); _stateProvider.Reset(); if (wasSenderAccountCreatedInsideACall) { _stateProvider.DeleteAccount(sender); } else { _stateProvider.AddToBalance(sender, senderReservedGasPayment, spec); if (notSystemTransaction) { _stateProvider.DecrementNonce(sender); } } _stateProvider.Commit(spec); } if (!isCall && notSystemTransaction) { block.GasUsed += spentGas; } if (txTracer.IsTracingReceipt) { Keccak stateRoot = null; bool eip658Enabled = _specProvider.GetSpec(block.Number).IsEip658Enabled; if (!eip658Enabled) { _stateProvider.RecalculateStateRoot(); stateRoot = _stateProvider.StateRoot; } if (statusCode == StatusCode.Failure) { txTracer.MarkAsFailed(recipient, spentGas, (substate?.ShouldRevert ?? false) ? substate.Output : Array.Empty <byte>(), substate?.Error, stateRoot); } else { txTracer.MarkAsSuccess(recipient, spentGas, substate.Output, substate.Logs.Any() ? substate.Logs.ToArray() : Array.Empty <LogEntry>(), stateRoot); } } }
private Block PrepareBlock(Block parentBlock) { BlockHeader parentHeader = parentBlock.Header; if (parentHeader == null) { if (_logger.IsError) { _logger.Error($"Preparing new block on top of {parentBlock.ToString(Block.Format.Short)} - parent header is null"); } return(null); } if (_recentNotAllowedParent == parentBlock.Hash) { return(null); } if (!_sealer.CanSeal(parentHeader.Number + 1, parentHeader.Hash)) { if (_logger.IsInfo) { _logger.Info($"Not allowed to sign block ({parentBlock.Number + 1})"); } _recentNotAllowedParent = parentHeader.Hash; return(null); } if (_logger.IsInfo) { _logger.Info($"Preparing new block on top of {parentBlock.ToString(Block.Format.Short)}"); } UInt256 timestamp = _timestamp.EpochSeconds; BlockHeader header = new BlockHeader( parentBlock.Hash, Keccak.OfAnEmptySequenceRlp, Address.Zero, 1, parentBlock.Number + 1, parentBlock.GasLimit, timestamp > parentBlock.Timestamp ? timestamp : parentBlock.Timestamp + 1, new byte[0]); // If the block isn't a checkpoint, cast a random vote (good enough for now) UInt256 number = header.Number; // Assemble the voting snapshot to check which votes make sense Snapshot snapshot = _snapshotManager.GetOrCreateSnapshot(number - 1, header.ParentHash); bool isEpochBlock = (ulong)number % 30000 == 0; if (!isEpochBlock) { // Gather all the proposals that make sense voting on List <Address> addresses = new List <Address>(); foreach (var proposal in _proposals) { Address address = proposal.Key; bool authorize = proposal.Value; if (_snapshotManager.IsValidVote(snapshot, address, authorize)) { addresses.Add(address); } } // If there's pending proposals, cast a vote on them if (addresses.Count > 0) { header.Beneficiary = addresses[_cryptoRandom.NextInt(addresses.Count)]; header.Nonce = _proposals[header.Beneficiary] ? Clique.NonceAuthVote : Clique.NonceDropVote; } } // Set the correct difficulty header.Difficulty = CalculateDifficulty(snapshot, _address); header.TotalDifficulty = parentBlock.TotalDifficulty + header.Difficulty; if (_logger.IsDebug) { _logger.Debug($"Setting total difficulty to {parentBlock.TotalDifficulty} + {header.Difficulty}."); } // Set extra data int mainBytesLength = Clique.ExtraVanityLength + Clique.ExtraSealLength; int signerBytesLength = isEpochBlock ? 20 * snapshot.Signers.Count : 0; int extraDataLength = mainBytesLength + signerBytesLength; header.ExtraData = new byte[extraDataLength]; byte[] clientName = Encoding.UTF8.GetBytes("Nethermind"); Array.Copy(clientName, header.ExtraData, clientName.Length); if (isEpochBlock) { for (int i = 0; i < snapshot.Signers.Keys.Count; i++) { Address signer = snapshot.Signers.Keys[i]; int index = Clique.ExtraVanityLength + 20 * i; Array.Copy(signer.Bytes, 0, header.ExtraData, index, signer.Bytes.Length); } } // Mix digest is reserved for now, set to empty header.MixHash = Keccak.Zero; // Ensure the timestamp has the correct delay header.Timestamp = parentBlock.Timestamp + _config.BlockPeriod; if (header.Timestamp < _timestamp.EpochSeconds) { header.Timestamp = new UInt256(_timestamp.EpochSeconds); } var transactions = _transactionPool.GetPendingTransactions().OrderBy(t => t.Nonce).ThenByDescending(t => t.GasPrice).ThenBy(t => t.GasLimit); // by nonce in case there are two transactions for the same account var selectedTxs = new List <Transaction>(); BigInteger gasRemaining = header.GasLimit; if (_logger.IsDebug) { _logger.Debug($"Collecting pending transactions at min gas price {MinGasPriceForMining} and block gas limit {gasRemaining}."); } int total = 0; _stateProvider.StateRoot = parentHeader.StateRoot; Dictionary <Address, UInt256> nonces = new Dictionary <Address, UInt256>(); foreach (Transaction transaction in transactions) { total++; if (transaction.SenderAddress == null) { if (_logger.IsError) { _logger.Error("Rejecting null sender pending transaction."); } continue; } if (transaction.GasPrice < MinGasPriceForMining) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction - gas price ({transaction.GasPrice}) too low (min gas price: {MinGasPriceForMining}."); } continue; } if (transaction.Nonce != _stateProvider.GetNonce(transaction.SenderAddress) && (!nonces.ContainsKey(transaction.SenderAddress) || nonces[transaction.SenderAddress] + 1 != transaction.Nonce)) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction based on nonce."); } continue; } if (transaction.GasLimit > gasRemaining) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction - gas limit ({transaction.GasPrice}) more than remaining gas ({gasRemaining})."); } continue; } UInt256 requiredBalance = transaction.GasLimit + transaction.Value; UInt256 balance = _stateProvider.GetBalance(transaction.SenderAddress); if (transaction.GasLimit + transaction.Value > balance) { if (_logger.IsTrace) { _logger.Trace($"Rejecting transaction - insufficient balance - required {requiredBalance}, actual {balance}."); } continue; } selectedTxs.Add(transaction); nonces[transaction.SenderAddress] = transaction.Nonce; gasRemaining -= transaction.GasLimit; } if (_logger.IsDebug) { _logger.Debug($"Collected {selectedTxs.Count} out of {total} pending transactions."); } Block block = new Block(header, selectedTxs, new BlockHeader[0]); header.TransactionsRoot = block.CalculateTransactionsRoot(); block.Author = _address; return(block); }
public BigInteger GetBalance(Address address) { _stateProvider.StateRoot = _blockTree.Head.StateRoot; return(_stateProvider.GetBalance(address)); }