public IEnumerable <EthereumTestResult> RunTests() { List <EthereumTestResult> results = new(); IEnumerable <GeneralStateTest> tests = (IEnumerable <GeneralStateTest>)_testsSource.LoadTests(); foreach (GeneralStateTest test in tests) { EthereumTestResult result = null; if (_whenTrace != WhenTrace.Always) { result = RunTest(test, NullTxTracer.Instance); } if (_whenTrace != WhenTrace.Never && !(result?.Pass ?? false)) { StateTestTxTracer txTracer = new(); txTracer.IsTracingMemory = _traceMemory; txTracer.IsTracingStack = _traceStack; result = RunTest(test, txTracer); var txTrace = txTracer.BuildResult(); txTrace.Result.Time = result.TimeInMs; txTrace.State.StateRoot = result.StateRoot; txTrace.Result.GasUsed -= IntrinsicGasCalculator.Calculate(test.Transaction, test.Fork); WriteErr(txTrace); } results.Add(result); } WriteOut(results); Console.ReadLine(); return(results); }
public void Keccak_gas_cost_assumption_is_correct() { Rlp rlp = BuildHeader(); Transaction tx = Build.A.Transaction.WithData(rlp.Bytes).TestObject; IntrinsicGasCalculator calculator = new IntrinsicGasCalculator(); long gasCost = calculator.Calculate(tx, Spec); gasCost.Should().BeLessThan(21000 + 9600); var bytecode = Prepare.EvmCode .PushData("0x0200") .PushData(0) .PushData(0) .Op(Instruction.CALLDATACOPY) .PushData("0x0200") .PushData(0) .Op(Instruction.SHA3) .Done; (Block block, Transaction transaction) = PrepareTx( BlockNumber, 1000000, bytecode, rlp.Bytes, 0); CallOutputTracer callOutputTracer = new CallOutputTracer(); _processor.Execute(transaction, block.Header, callOutputTracer); long minorCostsEstimate = 100; long keccakCostEstimate = 30 + 512 / 6; callOutputTracer.GasSpent.Should().BeLessThan(21000 + 9600 + minorCostsEstimate + keccakCostEstimate); }
public void Can_estimate_with_single_call() { byte[] initByteCode = Prepare.EvmCode .ForInitOf(Bytes.FromHexString("6000")).Done; Address contractAddress = ContractAddress.From(TestItem.PrivateKeyA.Address, 0); byte[] byteCode = Prepare.EvmCode .Call(contractAddress, 46179).Done; long gasLimit = 100000; Transaction initTx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA, 1).WithInit(initByteCode).WithGasLimit(gasLimit).TestObject; Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA, 1).WithInit(byteCode).WithGasLimit(gasLimit).WithNonce(1).TestObject; Block block = Build.A.Block.WithNumber(MainNetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; IntrinsicGasCalculator gasCalculator = new IntrinsicGasCalculator(); long intrinsic = gasCalculator.Calculate(tx, MuirGlacier.Instance); _transactionProcessor.Execute(initTx, block.Header, NullTxTracer.Instance); EstimateGasTracer tracer = new EstimateGasTracer(); _transactionProcessor.CallAndRestore(tx, block.Header, tracer); long actualIntrinsic = tx.GasLimit - tracer.IntrinsicGasAt; actualIntrinsic.Should().Be(53000); tracer.AdditionalGasRequired.Should().Be(1); tracer.GasSpent.Should().Be(53724); long estimate = tracer.GasSpent + tracer.AdditionalGasRequired; estimate.Should().Be(53725); }
public void Can_estimate_with_stipend() { byte[] initByteCode = Prepare.EvmCode .CallWithValue(Address.Zero, 0, 1) .Op(Instruction.STOP) .Done; long gasLimit = 100000; Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA, _isEip155Enabled).WithInit(initByteCode).WithGasLimit(gasLimit).TestObject; Block block = Build.A.Block.WithNumber(MainnetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; IntrinsicGasCalculator gasCalculator = new IntrinsicGasCalculator(); long intrinsic = gasCalculator.Calculate(tx, MuirGlacier.Instance); GethLikeTxTracer gethTracer = new GethLikeTxTracer(GethTraceOptions.Default); _transactionProcessor.CallAndRestore(tx, block.Header, gethTracer); TestContext.WriteLine(new EthereumJsonSerializer().Serialize(gethTracer.BuildResult(), true)); EstimateGasTracer tracer = new EstimateGasTracer(); _transactionProcessor.CallAndRestore(tx, block.Header, tracer); long actualIntrinsic = tx.GasLimit - tracer.IntrinsicGasAt; actualIntrinsic.Should().Be(intrinsic); tracer.CalculateAdditionalGasRequired(tx).Should().Be(2300); tracer.GasSpent.Should().Be(85669L); long estimate = tracer.CalculateEstimate(tx); estimate.Should().Be(87969L); ConfirmEnoughEstimate(tx, block, estimate); }
public void zero_transaction_data_cost_should_be_4() { var transaction = new Transaction { Data = new byte[] { 0 }, To = Address.Zero }; var cost = IntrinsicGasCalculator.Calculate(transaction, Spec); cost.Should().Be(GasCostOf.Transaction + GasCostOf.TxDataZero); }
public void Intrinsic_gas_cost_assumption_is_correct() { Rlp rlp = BuildHeader(); Transaction tx = Build.A.Transaction.WithData(rlp.Bytes).TestObject; long gasCost = IntrinsicGasCalculator.Calculate(tx, Spec); gasCost.Should().BeLessThan(21000 + 9600); }
public void non_zero_transaction_data_cost_should_be_68() { Transaction transaction = new Transaction { Data = new byte[] { 1 }, To = Address.Zero }; long cost = IntrinsicGasCalculator.Calculate(transaction, Spec); cost.Should().Be(GasCostOf.Transaction + GasCostOf.TxDataNonZero); }
/* Full and correct validation is only possible in the context of a specific block * as we cannot generalize correctness of the transaction without knowing the EIPs implemented * and the world state (account nonce in particular ). * Even without protocol change the tx can become invalid if another tx * from the same account with the same nonce got included on the chain. * As such we can decide whether tx is well formed but we also have to validate nonce * just before the execution of the block / tx. */ public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) { return /* This is unnecessarily calculated twice - at validation and execution times. */ (transaction.GasLimit >= IntrinsicGasCalculator.Calculate(transaction, releaseSpec) && /* if it is a call or a transfer then we require the 'To' field to have a value * while for an init it will be empty */ ValidateSignature(transaction.Signature, releaseSpec)); }
public TxValidator(int chainId) { _intrinsicGasCalculator = new IntrinsicGasCalculator(); if (chainId < 0) { throw new ArgumentException("Unexpected negative value", nameof(chainId)); } _chainIdValue = chainId; }
/* Full and correct validation is only possible in the context of a specific block * as we cannot generalize correctness of the transaction without knowing the EIPs implemented * and the world state (account nonce in particular ). * Even without protocol change the tx can become invalid if another tx * from the same account with the same nonce got included on the chain. * As such we can decide whether tx is well formed but we also have to validate nonce * just before the execution of the block / tx. */ public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) { // validate type before calculating intrinsic gas to avoid exception return(ValidateTxType(transaction, releaseSpec) && /* This is unnecessarily calculated twice - at validation and execution times. */ transaction.GasLimit >= IntrinsicGasCalculator.Calculate(transaction, releaseSpec) && /* if it is a call or a transfer then we require the 'To' field to have a value * while for an init it will be empty */ ValidateSignature(transaction.Signature, releaseSpec) && ValidateChainId(transaction)); }
private static UInt256 GetResultGas(Transaction transaction, BlockchainBridge.CallOutput result) { long gas = result.GasSpent; if (result.AccessList is not null) { // if we generated access list, we need to fix actual gas cost, as all storage was considered warm gas -= IntrinsicGasCalculator.Calculate(transaction, Berlin.Instance); transaction.AccessList = result.AccessList; gas += IntrinsicGasCalculator.Calculate(transaction, Berlin.Instance); } return((UInt256)gas); }
public void Can_estimate_with_refund() { byte[] initByteCode = Prepare.EvmCode .PushData(1) .PushData(1) .Op(Instruction.SSTORE) .PushData(0) .PushData(1) .Op(Instruction.SSTORE) .Op(Instruction.STOP) .Done; long gasLimit = 100000; Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA, 1).WithInit(initByteCode).WithGasLimit(gasLimit).TestObject; Block block = Build.A.Block.WithNumber(MainnetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; IntrinsicGasCalculator gasCalculator = new IntrinsicGasCalculator(); long intrinsic = gasCalculator.Calculate(tx, MuirGlacier.Instance); GethLikeTxTracer gethTracer = new GethLikeTxTracer(GethTraceOptions.Default); _transactionProcessor.CallAndRestore(tx, block.Header, gethTracer); TestContext.WriteLine(new EthereumJsonSerializer().Serialize(gethTracer.BuildResult(), true)); EstimateGasTracer tracer = new EstimateGasTracer(); _transactionProcessor.CallAndRestore(tx, block.Header, tracer); long actualIntrinsic = tx.GasLimit - tracer.IntrinsicGasAt; actualIntrinsic.Should().Be(intrinsic); tracer.CalculateAdditionalGasRequired(tx).Should().Be(RefundOf.SSetReversedEip2200 + GasCostOf.CallStipend - GasCostOf.SStoreNetMeteredEip2200 + 1); tracer.GasSpent.Should().Be(54764L); long estimate = tracer.CalculateEstimate(tx); estimate.Should().Be(75465L); ConfirmEnoughEstimate(tx, block, estimate); }
public void Can_estimate_with_destroy_refund_and_below_intrinsic() { byte[] initByteCode = Prepare.EvmCode.ForInitOf(Prepare.EvmCode.PushData(Address.Zero).Op(Instruction.SELFDESTRUCT).Done).Done; Address contractAddress = ContractAddress.From(TestItem.PrivateKeyA.Address, 0); byte[] byteCode = Prepare.EvmCode .Call(contractAddress, 46179) .Op(Instruction.STOP).Done; long gasLimit = 100000; Transaction initTx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA, _isEip155Enabled).WithInit(initByteCode).WithGasLimit(gasLimit).TestObject; Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA, _isEip155Enabled).WithInit(byteCode).WithGasLimit(gasLimit).WithNonce(1).TestObject; Block block = Build.A.Block.WithNumber(MainnetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; IntrinsicGasCalculator gasCalculator = new IntrinsicGasCalculator(); long intrinsic = gasCalculator.Calculate(tx, MuirGlacier.Instance); _transactionProcessor.Execute(initTx, block.Header, NullTxTracer.Instance); EstimateGasTracer tracer = new EstimateGasTracer(); GethLikeTxTracer gethTracer = new GethLikeTxTracer(GethTraceOptions.Default); _transactionProcessor.CallAndRestore(tx, block.Header, tracer); _transactionProcessor.CallAndRestore(tx, block.Header, gethTracer); TestContext.WriteLine(new EthereumJsonSerializer().Serialize(gethTracer.BuildResult(), true)); long actualIntrinsic = tx.GasLimit - tracer.IntrinsicGasAt; actualIntrinsic.Should().Be(intrinsic); tracer.CalculateAdditionalGasRequired(tx).Should().Be(24080); tracer.GasSpent.Should().Be(35228L); long estimate = tracer.CalculateEstimate(tx); estimate.Should().Be(59308); ConfirmEnoughEstimate(tx, block, estimate); }
public override void Setup() { base.Setup(); _gasCalculator = new IntrinsicGasCalculator(); }
public TransactionValidator(ISignatureValidator signatureValidator) { _signatureValidator = signatureValidator; _intrinsicGasCalculator = new IntrinsicGasCalculator(); }
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); } } }