public TestSpecProvider(IReleaseSpec initialSpecToReturn) { SpecToReturn = initialSpecToReturn; GenesisSpec = initialSpecToReturn; }
private UInt256 GetBlockReward(Block block) { IReleaseSpec spec = _specProvider.GetSpec(block.Number); return(spec.BlockReward); }
public long BaseGasCost(IReleaseSpec releaseSpec) { return(releaseSpec.IsEip1108Enabled ? 45000L : 100000L); }
public long DataGasCost(byte[] inputData, IReleaseSpec releaseSpec) { return(120L * EvmPooledMemory.Div32Ceiling((ulong)inputData.Length)); }
protected override bool ValidateGasLimitRange(BlockHeader header, BlockHeader parent, IReleaseSpec spec) => _blockGasLimitContractTransitions.TryGetForBlock(header.Number, out _) || base.ValidateGasLimitRange(header, parent, spec);
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 long DataGasCost(ReadOnlyMemory <byte> inputData, IReleaseSpec releaseSpec) { return(0L); }
public static long CalculateClaimableRefund(long spentGas, long totalRefund, IReleaseSpec spec) { long maxRefundQuotient = spec.IsEip3529Enabled ? MaxRefundQuotientEIP3529 : MaxRefundQuotient; return(Math.Min(spentGas / maxRefundQuotient, totalRefund)); }
protected virtual bool ValidateExtraData(BlockHeader header, BlockHeader?parent, IReleaseSpec spec, bool isUncle = false) { bool extraDataValid = header.ExtraData.Length <= spec.MaximumExtraDataSize && (isUncle || _daoBlockNumber == null || header.Number < _daoBlockNumber || header.Number >= _daoBlockNumber + 10 || Bytes.AreEqual(header.ExtraData, DaoExtraData)); if (!extraDataValid) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - DAO extra data not valid. MaximumExtraDataSize {spec.MaximumExtraDataSize}, ExtraDataLength {header.ExtraData.Length}, DaoBlockNumber: {_daoBlockNumber}"); } } return(extraDataValid); }
private UInt256 GetBlockReward(Block block) { IReleaseSpec spec = _specProvider.GetSpec(block.Number); return(spec.IsEip649Enabled ? spec.IsEip1234Enabled ? 2.Ether() : 3.Ether() : 5.Ether()); }
public TxTrieTests(bool useEip2718) { _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; }
public void GlobalSetup() { var dbProvider = new MemDbProvider(); ISnapshotableDb codeDb = dbProvider.CodeDb; ISnapshotableDb stateDb = dbProvider.StateDb; IDb blockInfoDb = new MemDb(10, 5); ISpecProvider specProvider = MainnetSpecProvider.Instance; IReleaseSpec spec = MainnetSpecProvider.Instance.GenesisSpec; StateProvider stateProvider = new StateProvider(stateDb, codeDb, LimboLogs.Instance); stateProvider.CreateAccount(Address.Zero, 1000.Ether()); stateProvider.Commit(spec); StorageProvider storageProvider = new StorageProvider(stateDb, stateProvider, LimboLogs.Instance); StateReader stateReader = new StateReader(stateDb, codeDb, LimboLogs.Instance); ChainLevelInfoRepository chainLevelInfoRepository = new ChainLevelInfoRepository(blockInfoDb); BlockTree blockTree = new BlockTree(dbProvider, chainLevelInfoRepository, specProvider, NullTxPool.Instance, NullBloomStorage.Instance, LimboLogs.Instance); _blockhashProvider = new BlockhashProvider(blockTree, LimboLogs.Instance); _virtualMachine = new VirtualMachine(stateProvider, storageProvider, _blockhashProvider, specProvider, LimboLogs.Instance); Block genesisBlock = Build.A.Block.Genesis.TestObject; blockTree.SuggestBlock(genesisBlock); Block block1 = Build.A.Block.WithParent(genesisBlock).WithNumber(1).TestObject; blockTree.SuggestBlock(block1); TransactionProcessor transactionProcessor = new TransactionProcessor(MainnetSpecProvider.Instance, stateProvider, storageProvider, _virtualMachine, LimboLogs.Instance); BlockProcessor blockProcessor = new BlockProcessor(specProvider, Always.Valid, new RewardCalculator(specProvider), transactionProcessor, stateDb, codeDb, stateProvider, storageProvider, NullTxPool.Instance, NullReceiptStorage.Instance, LimboLogs.Instance); EthereumEcdsa ecdsa = new EthereumEcdsa(specProvider.ChainId, LimboLogs.Instance); BlockchainProcessor blockchainProcessor = new BlockchainProcessor( blockTree, blockProcessor, new TxSignaturesRecoveryStep( ecdsa, NullTxPool.Instance, specProvider, LimboLogs.Instance), LimboLogs.Instance, BlockchainProcessor.Options.NoReceipts); blockchainProcessor.Process(genesisBlock, ProcessingOptions.None, NullBlockTracer.Instance); blockchainProcessor.Process(block1, ProcessingOptions.None, NullBlockTracer.Instance); IBloomStorage bloomStorage = new BloomStorage(new BloomConfig(), new MemDb(), new InMemoryDictionaryFileStoreFactory()); LogFinder logFinder = new LogFinder( blockTree, new InMemoryReceiptStorage(), bloomStorage, LimboLogs.Instance, new ReceiptsRecovery(ecdsa, specProvider)); BlockchainBridge bridge = new BlockchainBridge( new ReadOnlyTxProcessingEnv( new ReadOnlyDbProvider(dbProvider, false), new ReadOnlyBlockTree(blockTree), specProvider, LimboLogs.Instance), NullTxPool.Instance, NullReceiptStorage.Instance, NullFilterStore.Instance, NullFilterManager.Instance, ecdsa, Timestamper.Default, logFinder, false, false); _ethModule = new EthModule( new JsonRpcConfig(), bridge, blockTree, stateReader, NullTxPool.Instance, NullTxSender.Instance, NullWallet.Instance, LimboLogs.Instance); }
public long BaseGasCost(IReleaseSpec releaseSpec) => 0;
private bool ValidateTxType(Transaction transaction, IReleaseSpec releaseSpec) { return(transaction.Type == TxType.Legacy || (transaction.Type == TxType.AccessList && releaseSpec.UseTxAccessLists)); }
public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processingOptions, BlockReceiptsTracer receiptsTracer, IReleaseSpec spec) { for (int i = 0; i < block.Transactions.Length; i++) { Transaction currentTx = block.Transactions[i]; ProcessTransaction(block, currentTx, i, receiptsTracer, processingOptions); } return(receiptsTracer.TxReceipts.ToArray()); }
protected virtual bool ValidateGasLimitRange(BlockHeader header, BlockHeader parent, IReleaseSpec spec) { long adjustedParentGasLimit = Eip1559GasLimitAdjuster.AdjustGasLimit(spec, parent.GasLimit, header.Number); long maxGasLimitDifference = adjustedParentGasLimit / spec.GasLimitBoundDivisor; long maxNextGasLimit = adjustedParentGasLimit + maxGasLimitDifference; bool gasLimitNotTooHigh; bool notToHighWithOverflow = long.MaxValue - maxGasLimitDifference < adjustedParentGasLimit; if (notToHighWithOverflow) { // The edge case used in hive tests. If adjustedParentGasLimit + maxGasLimitDifference >= long.MaxValue, // we can check for long.MaxValue - maxGasLimitDifference < adjustedParentGasLimit to ensure that we are in range. // In hive we have tests that using long.MaxValue in the genesis block // Even if we add maxGasLimitDifference we don't get header.GasLimit higher than long.MaxValue gasLimitNotTooHigh = true; } else { gasLimitNotTooHigh = header.GasLimit < maxNextGasLimit; } if (!gasLimitNotTooHigh) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - gas limit too high"); } } bool gasLimitNotTooLow = header.GasLimit > adjustedParentGasLimit - maxGasLimitDifference && header.GasLimit >= spec.MinGasLimit; if (!gasLimitNotTooLow) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - gas limit too low"); } } return(gasLimitNotTooHigh && gasLimitNotTooLow); }
public long DataGasCost(byte[] inputData, IReleaseSpec releaseSpec) { return(0L); }
/// <summary> /// Note that this does not validate seal which is the responsibility of <see cref="ISealValidator"/>> /// </summary> /// <param name="header">BlockHeader to validate</param> /// <param name="parent">BlockHeader which is the parent of <paramref name="header"/></param> /// <param name="isUncle"><value>True</value> if uncle block, otherwise <value>False</value></param> /// <returns></returns> public virtual bool Validate(BlockHeader header, BlockHeader?parent, bool isUncle = false) { if (!ValidateFieldLimit(header)) { return(false); } bool hashAsExpected = ValidateHash(header); if (!hashAsExpected) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - invalid block hash"); } } IReleaseSpec spec = _specProvider.GetSpec(header.Number); bool extraDataValid = ValidateExtraData(header, parent, spec, isUncle); if (parent == null) { if (header.Number == 0) { bool isGenesisValid = ValidateGenesis(header); if (!isGenesisValid) { if (_logger.IsWarn) { _logger.Warn($"Invalid genesis block header ({header.Hash})"); } } return(isGenesisValid); } if (_logger.IsDebug) { _logger.Debug($"Orphan block, could not find parent ({header.ParentHash}) of ({header.Hash})"); } return(false); } bool totalDifficultyCorrect = ValidateTotalDifficulty(parent, header); bool sealParamsCorrect = _sealValidator.ValidateParams(parent, header); if (!sealParamsCorrect) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - seal parameters incorrect"); } } bool gasUsedBelowLimit = header.GasUsed <= header.GasLimit; if (!gasUsedBelowLimit) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - gas used above gas limit"); } } bool gasLimitInRange = ValidateGasLimitRange(header, parent, spec); // bool gasLimitAboveAbsoluteMinimum = header.GasLimit >= 125000; // described in the YellowPaper but not followed bool timestampValid = ValidateTimestamp(parent, header); bool numberIsParentPlusOne = header.Number == parent.Number + 1; if (!numberIsParentPlusOne) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - block number is not parent + 1"); } } if (_logger.IsTrace) { _logger.Trace($"Validating block {header.ToString(BlockHeader.Format.Short)}, extraData {header.ExtraData.ToHexString(true)}"); } bool eip1559Valid = true; bool isEip1559Enabled = spec.IsEip1559Enabled; if (isEip1559Enabled) { UInt256?expectedBaseFee = BaseFeeCalculator.Calculate(parent, spec); eip1559Valid = expectedBaseFee == header.BaseFeePerGas; if (expectedBaseFee != header.BaseFeePerGas) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.ToString(BlockHeader.Format.Short)}) incorrect base fee. Expected base fee: {expectedBaseFee}, Current base fee: {header.BaseFeePerGas} "); } eip1559Valid = false; } } return (totalDifficultyCorrect && gasUsedBelowLimit && gasLimitInRange && sealParamsCorrect && // gasLimitAboveAbsoluteMinimum && // described in the YellowPaper but not followed timestampValid && numberIsParentPlusOne && hashAsExpected && extraDataValid && eip1559Valid); }
public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) { return(_result); }
public SystemTransactionReleaseSpec(IReleaseSpec spec) { _spec = spec; }
public long DataGasCost(byte[] inputData, IReleaseSpec releaseSpec) { int k = inputData.Length / ItemSize; return(12000L * k * Discount.For(k) / 1000); }
public (byte[], bool) Run(byte[] inputData, IReleaseSpec releaseSpec) { return(inputData, true); }
public long BaseGasCost(IReleaseSpec releaseSpec) => 115000L;
private 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"); 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 (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); } } } 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(_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, 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); } } }
public long BaseGasCost(IReleaseSpec releaseSpec) { return(0L); }
private long Refund(long gasLimit, long unspentGas, TransactionSubstate substate, Address sender, UInt256 gasPrice, IReleaseSpec spec) { long spentGas = gasLimit; if (!substate.IsError) { spentGas -= unspentGas; long refund = substate.ShouldRevert ? 0 : RefundHelper.CalculateClaimableRefund(spentGas, substate.Refund + substate.DestroyList.Count * RefundOf.Destroy); if (_logger.IsTrace) { _logger.Trace("Refunding unused gas of " + unspentGas + " and refund of " + refund); } _stateProvider.AddToBalance(sender, (ulong)(unspentGas + refund) * gasPrice, spec); spentGas -= refund; } return(spentGas); }
/// <summary> /// Note that this does not validate seal which is the responsibility of <see cref="ISealValidator"/>> /// </summary> /// <param name="header">BlockHeader to validate</param> /// <param name="parent">BlockHeader which is the parent of <paramref name="header"/></param> /// <param name="isOmmer"><value>True</value> if uncle block, otherwise <value>False</value></param> /// <returns></returns> public bool Validate(BlockHeader header, BlockHeader parent, bool isOmmer = false) { bool hashAsExpected = ValidateHash(header); IReleaseSpec spec = _specProvider.GetSpec(header.Number); bool extraDataValid = header.ExtraData.Length <= spec.MaximumExtraDataSize && (isOmmer || _daoBlockNumber == null || header.Number < _daoBlockNumber || header.Number >= _daoBlockNumber + 10 || Bytes.AreEqual(header.ExtraData, DaoExtraData)); if (!extraDataValid) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - DAO extra data not valid"); } } if (parent == null) { if (header.Number == 0) { var isGenesisValid = ValidateGenesis(header); ; if (!isGenesisValid) { if (_logger.IsWarn) { _logger.Warn($"Invalid genesis block header ({header.Hash})"); } } return(isGenesisValid); } if (_logger.IsDebug) { _logger.Debug($"Orphan block, could not find parent ({header.Hash})"); } return(false); } // seal is validated when synchronizing so we can remove it from here - review and test bool sealParamsCorrect = _sealValidator.ValidateParams(parent, header); if (!sealParamsCorrect) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - seal parameters incorrect"); } } bool gasUsedBelowLimit = header.GasUsed <= header.GasLimit; if (!gasUsedBelowLimit) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - gas used above gas limit"); } } long maxGasLimitDifference = parent.GasLimit / spec.GasLimitBoundDivisor; bool gasLimitNotTooHigh = header.GasLimit <= parent.GasLimit + maxGasLimitDifference; if (!gasLimitNotTooHigh) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - gas limit too high"); } } bool gasLimitNotTooLow = header.GasLimit >= parent.GasLimit - maxGasLimitDifference && header.GasLimit >= spec.MinGasLimit; if (!gasLimitNotTooLow) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - gas limit too low"); } } // bool gasLimitAboveAbsoluteMinimum = header.GasLimit >= 125000; // described in the YellowPaper but not followed bool timestampMoreThanAtParent = header.Timestamp > parent.Timestamp; if (!timestampMoreThanAtParent) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - timestamp before parent"); } } bool numberIsParentPlusOne = header.Number == parent.Number + 1; if (!numberIsParentPlusOne) { if (_logger.IsWarn) { _logger.Warn($"Invalid block header ({header.Hash}) - block number is not parent + 1"); } } if (_logger.IsTrace) { _logger.Trace($"Validating block {header.ToString(BlockHeader.Format.Short)}, extraData {header.ExtraData.ToHexString(true)}"); } return (gasUsedBelowLimit && gasLimitNotTooLow && gasLimitNotTooHigh && sealParamsCorrect && // gasLimitAboveAbsoluteMinimum && // described in the YellowPaper but not followed timestampMoreThanAtParent && numberIsParentPlusOne && hashAsExpected && extraDataValid); }
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); } UInt256 value = transaction.Value; UInt256 feeCap = transaction.IsEip1559 ? transaction.FeeCap : transaction.GasPrice; UInt256 baseFee = block.BaseFee; if (baseFee > feeCap) { TraceLogInvalidTx(transaction, "MINER_PREMIUM_IS_NEGATIVE"); QuickFail(transaction, block, txTracer, "miner premium is negative"); return; } UInt256 premiumPerGas = UInt256.Min(transaction.GasPremium, feeCap - baseFee); UInt256 gasPrice = premiumPerGas + baseFee; 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, "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.GetActualGasLimit(spec) - block.GasUsed) { TraceLogInvalidTx(transaction, $"BLOCK_GAS_LIMIT_EXCEEDED {gasLimit} > {block.GetActualGasLimit(spec)} - {block.GasUsed}"); QuickFail(transaction, block, txTracer, "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 (isCall || gasPrice == UInt256.Zero) { wasSenderAccountCreatedInsideACall = isCall; _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."); } } if (notSystemTransaction) { UInt256 senderBalance = _stateProvider.GetBalance(caller); if (!isCall && (ulong)intrinsicGas * gasPrice + value > senderBalance) { TraceLogInvalidTx(transaction, $"INSUFFICIENT_SENDER_BALANCE: ({caller})_BALANCE = {senderBalance}"); QuickFail(transaction, block, txTracer, "insufficient sender balance"); return; } if (transaction.Nonce != _stateProvider.GetNonce(caller)) { TraceLogInvalidTx(transaction, $"WRONG_TRANSACTION_NONCE: {transaction.Nonce} (expected {_stateProvider.GetNonce(caller)})"); QuickFail(transaction, block, txTracer, "wrong transaction nonce"); return; } _stateProvider.IncrementNonce(caller); } UInt256 senderReservedGasPayment = isCall ? UInt256.Zero : (ulong)gasLimit * gasPrice; _stateProvider.SubtractFromBalance(caller, senderReservedGasPayment, spec); _stateProvider.Commit(spec, txTracer.IsTracingState ? txTracer : NullTxTracer.Instance); long unspentGas = gasLimit - intrinsicGas; long spentGas = gasLimit; int stateSnapshot = _stateProvider.TakeSnapshot(); int storageSnapshot = _storageProvider.TakeSnapshot(); _stateProvider.SubtractFromBalance(caller, value, spec); byte statusCode = StatusCode.Failure; TransactionSubstate substate = null; Address?recipientOrNull = null; try { Address recipient; if (transaction.IsContractCreation) { recipient = transaction.IsSystem() ? caller : ContractAddress.From(caller, _stateProvider.GetNonce(caller) - 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); } } else { recipient = transaction.To !; } if (recipient == null) { throw new InvalidDataException("Recipient has not been resolved properly before tx execution"); } recipientOrNull = recipient; ExecutionEnvironment env = new(); env.TxExecutionContext = new TxExecutionContext(block, caller, transaction.GasPrice); 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(recipient, spec) : new CodeInfo(machineCode); ExecutionType executionType = transaction.IsContractCreation ? ExecutionType.Create : ExecutionType.Call; using (EvmState state = new(unspentGas, env, executionType, true, 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, 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.ChargeForTopLevelCreate) { 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, caller, 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 * premiumPerGas); } else { _stateProvider.AddToBalance(gasBeneficiary, (ulong)spentGas * premiumPerGas, spec); } } } if (!isCall) { _storageProvider.Commit(txTracer.IsTracingState ? txTracer : NullStorageTracer.Instance); _stateProvider.Commit(spec, txTracer.IsTracingState ? txTracer : NullStateTracer.Instance); } else { _storageProvider.Reset(); _stateProvider.Reset(); if (wasSenderAccountCreatedInsideACall) { _stateProvider.DeleteAccount(caller); } else { _stateProvider.AddToBalance(caller, senderReservedGasPayment, spec); if (notSystemTransaction) { _stateProvider.DecrementNonce(caller); } } _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(recipientOrNull, spentGas, (substate?.ShouldRevert ?? false) ? substate.Output : Array.Empty <byte>(), substate?.Error, stateRoot); } else { txTracer.MarkAsSuccess(recipientOrNull, spentGas, substate.Output, substate.Logs.Any() ? substate.Logs.ToArray() : Array.Empty <LogEntry>(), stateRoot); } } }
public long DataGasCost(ReadOnlyMemory <byte> inputData, IReleaseSpec releaseSpec) { return((releaseSpec.IsEip1108Enabled ? 34000L : 80000L) * (inputData.Length / PairSize)); }
public Transaction BuildTransaction(long gaslimit, byte[] callData, Address sender, BlockHeader parent, IReleaseSpec spec, UInt256 nonce, bool systemTransaction) { Transaction transaction = systemTransaction ? new SystemTransaction() : new Transaction(); UInt256 fee = BaseFeeCalculator.Calculate(parent, spec); transaction.GasPrice = fee; transaction.GasLimit = gaslimit; transaction.To = _entryPointContractAddress; transaction.ChainId = _specProvider.ChainId; transaction.Nonce = nonce; transaction.Value = 0; transaction.Data = callData; transaction.Type = TxType.EIP1559; transaction.DecodedMaxFeePerGas = fee; transaction.SenderAddress = sender; if (!systemTransaction) { _signer.Sign(transaction); } transaction.Hash = transaction.CalculateHash(); return(transaction); }