Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions scripts/known-failing-hive-tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,11 @@ eth_simulateV1/ethSimulate-empty-with-block-num-set-plus1 (nethermind)
eth_simulateV1/ethSimulate-eth-send-should-not-produce-logs-on-revert (nethermind)
eth_simulateV1/ethSimulate-eth-send-should-produce-no-logs-on-forward-revert (nethermind)
eth_simulateV1/ethSimulate-fee-recipient-receiving-funds (nethermind)
eth_simulateV1/ethSimulate-gas-fees-and-value-error-38014 (nethermind)
eth_simulateV1/ethSimulate-instrict-gas-38013 (nethermind)
eth_simulateV1/ethSimulate-make-call-with-future-block (nethermind)
eth_simulateV1/ethSimulate-move-to-address-itself-reference-38022 (nethermind)
eth_simulateV1/ethSimulate-move-two-non-precompiles-accounts-to-same (nethermind)
eth_simulateV1/ethSimulate-overflow-nonce-validation (nethermind)
eth_simulateV1/ethSimulate-override-address-twice (nethermind)
eth_simulateV1/ethSimulate-run-gas-spending (nethermind)
eth_simulateV1/ethSimulate-run-out-of-gas-in-block-38015 (nethermind)
eth_simulateV1/ethSimulate-simple-more-params-validate (nethermind)
eth_simulateV1/ethSimulate-simple-no-funds (nethermind)
eth_simulateV1/ethSimulate-simple-no-funds-with-balance-querying (nethermind)
eth_simulateV1/ethSimulate-simple-send-from-contract-no-balance (nethermind)
eth_simulateV1/ethSimulate-try-to-move-non-precompile (nethermind)
eth_simulateV1/ethSimulate-two-blocks-with-complete-eth-sends (nethermind)
eth_simulateV1/ethSimulate-use-as-many-features-as-possible (nethermind)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using Nethermind.Core;
using Nethermind.Evm.TransactionProcessing;

namespace Nethermind.Blockchain;

public class InvalidTransactionException : InvalidBlockException
{
public InvalidTransactionException(BlockHeader header, string message, TransactionResult result, Exception? innerException = null)
: base(header, message, innerException) => Reason = result;

public TransactionResult Reason;
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public virtual AddingTxEventArgs CanAddTransaction(Block block, Transaction curr
IReleaseSpec spec = _specProvider.GetSpec(block.Header);
if (currentTx.IsAboveInitCode(spec))
{
return args.Set(TxAction.Skip, TransactionResult.TransactionSizeOverMaxInitCodeSize.Error);
return args.Set(TxAction.Skip, TransactionResult.TransactionSizeOverMaxInitCodeSize.ErrorDescription);
}

if (!_ignoreEip3607 && stateProvider.IsInvalidContractSender(spec, currentTx.SenderAddress))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private TxAction ProcessTransaction(
}
else
{
args.Set(TxAction.Skip, result.Error!);
args.Set(TxAction.Skip, result.ErrorDescription!);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processing
protected virtual void ProcessTransaction(Block block, Transaction currentTx, int index, BlockReceiptsTracer receiptsTracer, ProcessingOptions processingOptions)
{
TransactionResult result = transactionProcessor.ProcessTransaction(currentTx, receiptsTracer, processingOptions, stateProvider);
if (!result) ThrowInvalidBlockException(result, block.Header, currentTx, index);
if (!result) ThrowInvalidTransactionException(result, block.Header, currentTx, index);
transactionProcessedEventHandler?.OnTransactionProcessed(new TxProcessedEventArgs(index, currentTx, block.Header, receiptsTracer.TxReceipts[index]));
}

[DoesNotReturn, StackTraceHidden]
private void ThrowInvalidBlockException(TransactionResult result, BlockHeader header, Transaction currentTx, int index)
private void ThrowInvalidTransactionException(TransactionResult result, BlockHeader header, Transaction currentTx, int index)
{
throw new InvalidBlockException(header, $"Transaction {currentTx.Hash} at index {index} failed with error {result.Error}");
throw new InvalidTransactionException(header, $"Transaction {currentTx.Hash} at index {index} failed with error {result.Error}", result);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -922,40 +922,75 @@ protected virtual long CalculateClaimableRefund(long spentGas, long totalRefund,
private static void ThrowInvalidDataException(string message) => throw new InvalidDataException(message);
}

public readonly struct TransactionResult(string? error, EvmExceptionType evmException = EvmExceptionType.None) : IEquatable<TransactionResult>
public readonly struct TransactionResult : IEquatable<TransactionResult>
{
[MemberNotNullWhen(false, nameof(TransactionExecuted))]
public string? Error { get; } = error;
public bool TransactionExecuted => Error is null;
public EvmExceptionType EvmExceptionType { get; } = evmException;
private TransactionResult(ErrorType error = ErrorType.None, EvmExceptionType evmException = EvmExceptionType.None)
{
Error = error;
EvmExceptionType = evmException;
}
public ErrorType Error { get; }
public bool TransactionExecuted => Error is ErrorType.None;
public EvmExceptionType EvmExceptionType { get; }

public static implicit operator TransactionResult(string? error) => new(error);
public string ErrorDescription => Error switch
{
ErrorType.BlockGasLimitExceeded => "Block gas limit exceeded",
ErrorType.GasLimitBelowIntrinsicGas => "gas limit below intrinsic gas",
ErrorType.InsufficientMaxFeePerGasForSenderBalance => "insufficient MaxFeePerGas for sender balance",
ErrorType.InsufficientSenderBalance => "insufficient sender balance",
ErrorType.MalformedTransaction => "malformed",
ErrorType.MinerPremiumNegative => "miner premium is negative",
ErrorType.NonceOverflow => "nonce overflow",
ErrorType.SenderHasDeployedCode => "sender has deployed code",
ErrorType.SenderNotSpecified => "sender not specified",
ErrorType.TransactionSizeOverMaxInitCodeSize => "EIP-3860 - transaction size over max init code size",
ErrorType.WrongTransactionNonce => "wrong transaction nonce",
_ => ""
};
public static implicit operator TransactionResult(ErrorType error) => new(error);
public static implicit operator bool(TransactionResult result) => result.TransactionExecuted;
public bool Equals(TransactionResult other) => (TransactionExecuted && other.TransactionExecuted) || (Error == other.Error);
public static bool operator ==(TransactionResult obj1, TransactionResult obj2) => obj1.Equals(obj2);
public static bool operator !=(TransactionResult obj1, TransactionResult obj2) => !obj1.Equals(obj2);
public override bool Equals(object? obj) => obj is TransactionResult result && Equals(result);
public override int GetHashCode() => TransactionExecuted ? 1 : Error.GetHashCode();

public override string ToString() => Error is not null ? $"Fail : {Error}" : "Success";
public override string ToString() => Error is not ErrorType.None ? $"Fail : {ErrorDescription}" : "Success";

public static TransactionResult EvmException(EvmExceptionType evmExceptionType)
{
return new TransactionResult(null, evmExceptionType);
return new TransactionResult(ErrorType.None, evmExceptionType);
}

public static readonly TransactionResult Ok = new();

public static readonly TransactionResult BlockGasLimitExceeded = "Block gas limit exceeded";
public static readonly TransactionResult GasLimitBelowIntrinsicGas = "gas limit below intrinsic gas";
public static readonly TransactionResult InsufficientMaxFeePerGasForSenderBalance = "insufficient MaxFeePerGas for sender balance";
public static readonly TransactionResult InsufficientSenderBalance = "insufficient sender balance";
public static readonly TransactionResult MalformedTransaction = "malformed";
public static readonly TransactionResult MinerPremiumNegative = "miner premium is negative";
public static readonly TransactionResult NonceOverflow = "nonce overflow";
public static readonly TransactionResult SenderHasDeployedCode = "sender has deployed code";
public static readonly TransactionResult SenderNotSpecified = "sender not specified";
public static readonly TransactionResult TransactionSizeOverMaxInitCodeSize = "EIP-3860 - transaction size over max init code size";
public static readonly TransactionResult WrongTransactionNonce = "wrong transaction nonce";
public static readonly TransactionResult BlockGasLimitExceeded = ErrorType.BlockGasLimitExceeded;
public static readonly TransactionResult GasLimitBelowIntrinsicGas = ErrorType.GasLimitBelowIntrinsicGas;
public static readonly TransactionResult InsufficientMaxFeePerGasForSenderBalance = ErrorType.InsufficientMaxFeePerGasForSenderBalance;
public static readonly TransactionResult InsufficientSenderBalance = ErrorType.InsufficientSenderBalance;
public static readonly TransactionResult MalformedTransaction = ErrorType.MalformedTransaction;
public static readonly TransactionResult MinerPremiumNegative = ErrorType.MinerPremiumNegative;
public static readonly TransactionResult NonceOverflow = ErrorType.NonceOverflow;
public static readonly TransactionResult SenderHasDeployedCode = ErrorType.SenderHasDeployedCode;
public static readonly TransactionResult SenderNotSpecified = ErrorType.SenderNotSpecified;
public static readonly TransactionResult TransactionSizeOverMaxInitCodeSize = ErrorType.TransactionSizeOverMaxInitCodeSize;
public static readonly TransactionResult WrongTransactionNonce = ErrorType.WrongTransactionNonce;

public enum ErrorType
{
None,
BlockGasLimitExceeded,
GasLimitBelowIntrinsicGas,
InsufficientMaxFeePerGasForSenderBalance,
InsufficientSenderBalance,
MalformedTransaction,
MinerPremiumNegative,
NonceOverflow,
SenderHasDeployedCode,
SenderNotSpecified,
TransactionSizeOverMaxInitCodeSize,
WrongTransactionNonce,
}
}
}
6 changes: 3 additions & 3 deletions src/Nethermind/Nethermind.Facade/BlockchainBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ private TransactionResult TryCallAndRestore(
{
return CallAndRestore(blockHeader, transaction, treatBlockHeaderAsParentBlock, tracer, components);
}
catch (InsufficientBalanceException ex)
catch (InsufficientBalanceException)
{
return new TransactionResult(ex.Message);
return TransactionResult.InsufficientSenderBalance;
}
}

Expand Down Expand Up @@ -404,7 +404,7 @@ public IEnumerable<FilterLog> FindLogs(LogFilter filter, CancellationToken cance
{
{ TransactionExecuted: true } when txResult.EvmExceptionType is not (EvmExceptionType.None or EvmExceptionType.Revert) => txResult.EvmExceptionType.GetEvmExceptionDescription(),
{ TransactionExecuted: true } when tracerError is not null => tracerError,
{ TransactionExecuted: false, Error: not null } => txResult.Error,
{ TransactionExecuted: false, Error: not TransactionResult.ErrorType.None } => txResult.ErrorDescription,
_ => null
};

Expand Down
29 changes: 16 additions & 13 deletions src/Nethermind/Nethermind.Facade/Simulate/SimulateBridgeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Nethermind.Consensus;
using Nethermind.Evm.State;
using Nethermind.Logging;
using Nethermind.Evm.TransactionProcessing;
using Transaction = Nethermind.Core.Transaction;

namespace Nethermind.Facade.Simulate;
Expand Down Expand Up @@ -70,31 +69,38 @@ public SimulateOutput<TTrace> TrySimulate<TTrace>(

try
{
if (!TrySimulate(parent, payload, tracer, env, list, gasCapLimit, cancellationToken, out string? error))
{
result.Error = error;
}
Simulate(parent, payload, tracer, env, list, gasCapLimit, cancellationToken);
}
catch (ArgumentException ex)
{
result.Error = ex.Message;
result.IsInvalidOutput = true;
}
catch (InvalidTransactionException ex)
{
result.Error = ex.Reason.ErrorDescription;
result.TransactionResult = ex.Reason;
}
catch (InsufficientBalanceException ex)
{
result.Error = ex.Message;
result.TransactionResult = TransactionResult.InsufficientSenderBalance;
}
catch (Exception ex)
{
result.Error = ex.ToString();
result.Error = ex.Message;
}

return result;
}

private bool TrySimulate<TTrace>(BlockHeader parent,
private void Simulate<TTrace>(BlockHeader parent,
SimulatePayload<TransactionWithSourceDetails> payload,
IBlockTracer<TTrace> tracer,
SimulateReadOnlyBlocksProcessingScope env,
List<SimulateBlockResult<TTrace>> output,
long gasCapLimit,
CancellationToken cancellationToken,
[NotNullWhen(false)] out string? error)
CancellationToken cancellationToken)
{
IBlockTree blockTree = env.BlockTree;
IWorldState stateProvider = env.WorldState;
Expand Down Expand Up @@ -157,9 +163,6 @@ private bool TrySimulate<TTrace>(BlockHeader parent,
parent = processedBlock.Header;
}
}

error = null;
return true;
}

private BlockBody AssembleBody(
Expand Down
4 changes: 3 additions & 1 deletion src/Nethermind/Nethermind.Facade/Simulate/SimulateOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Generic;
using Nethermind.Evm.TransactionProcessing;
using Nethermind.Facade.Proxy.Models.Simulate;

namespace Nethermind.Facade.Simulate;

public class SimulateOutput<TTrace>
{
public string? Error { get; set; }
public int? ErrorCode { get; set; }
public bool IsInvalidOutput { get; set; }
public TransactionResult TransactionResult { get; set; }

public IReadOnlyList<SimulateBlockResult<TTrace>> Items { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task Eth_estimateGas_web3_should_return_insufficient_balance_error(
string serialized =
await ctx.Test.TestEthRpc("eth_estimateGas", transaction);
Assert.That(
serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient funds for transfer: address 0x0001020304050607080910111213141516171819\"},\"id\":67}"));
serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient sender balance\"},\"id\":67}"));
ctx.Test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public async Task Eth_call_web3_should_return_insufficient_balance_error()
"{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\", \"value\": 500, \"gas\": 100000000}");
string serialized = await ctx.Test.TestEthRpc("eth_call", transaction);
Assert.That(
serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient funds for transfer: address 0x0001020304050607080910111213141516171819\"},\"id\":67}"));
serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient sender balance\"},\"id\":67}"));
ctx.Test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse();
}

Expand Down
62 changes: 62 additions & 0 deletions src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,67 @@ public static class ErrorCodes
/// Block is not available due to history expirty policy
/// </summary>
public const int PrunedHistoryUnavailable = 4444;

/// <summary>
/// Default error code
/// </summary>
public const int Default = -32000;

/// <summary>
/// Transaction.Nonce is bigger than expected nonce
/// </summary>
public const int NonceTooHigh = -38011;

/// <summary>
/// Transaction.Nonce is smaller than expected nonce
/// </summary>
public const int NonceTooLow = -38010;

/// <summary>
/// Invalid intrinsic gas. Miner premium is negative
/// </summary>
public const int IntrinsicGas = -38013;

/// <summary>
/// Not enough value to cover transaction costs
/// </summary>
public const int InsufficientFunds = -38014;

/// <summary>
/// Gas limit reached
/// </summary>
public const int BlockGasLimitReached = -38015;

/// <summary>
/// Invalid block number
/// </summary>
public const int BlockNumberInvalid = -38020;

/// <summary>
/// Invalid block timestamp
/// </summary>
public const int BlockTimestampInvalid = -38021;

/// <summary>
/// Transaction.Sender is not an EOA
/// </summary>
public const int SenderIsNotEOA = -38024;

/// <summary>
/// EIP-3860. Code size is to big
/// </summary>
public const int MaxInitCodeSizeExceeded = -38025;

/// <summary>
/// Transaction reverted. Geth sets it to -32000 in simulate but suppose to 3. We kepp it as geth for now
/// </summary>
public const int RevertedSimulate = -32000;
Comment on lines +180 to +183
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe Geth already returns 3, but tests weren't re-generated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I checked. It's a bug in their code


/// <summary>
/// Error during EVM execution
/// </summary>
public const int VMError = -32015;
public const int TxSyncTimeout = 4;
public const int ClientLimitExceeded = -38026;
}
}
Loading
Loading