// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; using System.Threading; using Nethermind.Core.Messages; namespace Nethermind.TxPool { /// /// Describes potential outcomes of adding transaction to the TX pool. /// /// /// Equality is by an id that the declarations below hand out in order, never by a literal, so results added /// independently on parallel branches cannot end up sharing one. Ids start at zero, so , /// declared first, keeps id 0 and a default-valued result still reads as accepted. /// public readonly struct AcceptTxResult : IEquatable { // Deliberately uninitialized: an initializer would make the ids depend on where this field sits in the file. private static int _nextId; /// /// The transaction has been accepted. This is the only 'success' outcome. /// // Code intentionally kept as nameof(): the success path returns the tx hash, not this Code string. public static readonly AcceptTxResult Accepted = new(nameof(Accepted)); /// /// A transaction with the same hash has already been added to the pool in the past. /// public static readonly AcceptTxResult AlreadyKnown = new(TxPoolErrorMessages.AlreadyKnown); /// /// Covers scenarios where sender recovery fails. /// public static readonly AcceptTxResult FailedToResolveSender = new(TxPoolErrorMessages.FailedToRecoverSender); /// /// Fee paid by this transaction is not enough to be accepted in the mempool. /// public static readonly AcceptTxResult FeeTooLow = new(TxPoolErrorMessages.TransactionUnderpriced); /// /// Fee paid by this transaction is not enough to be accepted in the mempool. /// public static readonly AcceptTxResult FeeTooLowToCompete = new(TxPoolErrorMessages.TransactionUnderpriced); /// /// Transaction gas limit exceeds the block gas limit. /// public static readonly AcceptTxResult GasLimitExceeded = new(TxPoolErrorMessages.GasLimitReached); /// /// Sender account has not enough balance to execute this transaction. /// public static readonly AcceptTxResult InsufficientFunds = new(TxErrorMessages.InsufficientFundsForGas); /// /// Calculation of gas price * gas limit + value overflowed int256. /// public static readonly AcceptTxResult Int256Overflow = new(TxPoolErrorMessages.TransactionOverflow); /// /// Transaction format is invalid. /// public static readonly AcceptTxResult Invalid = new(TxPoolErrorMessages.TransactionInvalid); /// /// The nonce is not the next nonce after the last nonce of this sender present in TxPool. /// public static readonly AcceptTxResult NonceGap = new(TxPoolErrorMessages.NonceTooHigh); /// /// The EOA (externally owned account) that signed this transaction (sender) has already signed and executed a transaction with the same nonce. /// public static readonly AcceptTxResult OldNonce = new(TxPoolErrorMessages.NonceTooLow); /// /// Transaction is not allowed to replace the one already in the pool. Fee bump is too low or some requirements are not fulfilled /// public static readonly AcceptTxResult ReplacementNotAllowed = new(TxPoolErrorMessages.ReplacementTransactionUnderpriced); /// /// Transaction sender has code hash that is not null. /// public static readonly AcceptTxResult SenderIsContract = new(TxPoolErrorMessages.SenderNotEoa); /// /// The nonce is too far in the future. /// public static readonly AcceptTxResult NonceTooFarInFuture = new(TxPoolErrorMessages.NonceTooFarInFuture); /// /// Ignores blob transactions if sender already have pending transactions of other types; ignore other types if has already pending blobs /// public static readonly AcceptTxResult PendingTxsOfConflictingType = new(TxPoolErrorMessages.PendingTransactionTypeConflict); /// /// Ignores transactions if tx type is not supported /// public static readonly AcceptTxResult NotSupportedTxType = new(TxPoolErrorMessages.UnsupportedTransactionType); /// /// Transaction size exceeds configured max size. /// public static readonly AcceptTxResult MaxTxSizeExceeded = new(TxPoolErrorMessages.TransactionTooLarge); /// /// Only one tx with current state matching nonce is allowed per delegated account or pending delegation. /// public static readonly AcceptTxResult NotCurrentNonceForDelegation = new(TxPoolErrorMessages.DelegationNonceGap); /// /// There is a pending transaction from a delegation in the tx pool already. /// public static readonly AcceptTxResult DelegatorHasPendingTx = new(TxPoolErrorMessages.DelegationAuthorityHasPendingTx); /// /// Blob or cell proofs failed cryptographic validation after cheaper admission checks passed. /// public static readonly AcceptTxResult InvalidBlobProofs = new(TxErrorMessages.InvalidBlobProofs); /// /// The blob transaction sidecar does not contain full blobs or any sparse cells. /// public static readonly AcceptTxResult IncompleteBlobData = new(TxErrorMessages.IncompleteBlobData); /// /// The node is syncing and cannot accept transactions at this time. /// public static readonly AcceptTxResult Syncing = new(TxPoolErrorMessages.NodeIsSyncing); /// /// The signer could not produce a signature for the transaction (locked account, missing key, remote signer rejection). /// // Code intentionally kept as nameof(): EthRpcModule.SendTx intercepts this result and surfaces only the // Message field with ErrorCodes.AccountLocked (-32020), so the Code string never reaches RPC callers. public static readonly AcceptTxResult SignFailed = new(nameof(SignFailed), "authentication needed: password or unlock"); /// /// An EIP-8141 frame transaction whose expiry-verifier deadline is already behind the current head; it can /// never be included, so it must not enter the pool or be broadcast. /// public static readonly AcceptTxResult FrameTxExpired = new(TxPoolErrorMessages.FrameTxExpired); /// /// An EIP-8141 frame transaction whose validation prefix plus signature verification would cost a node more /// than the node verification budget to check. It stays consensus-valid; only public mempool propagation is refused. /// public static readonly AcceptTxResult FrameTxVerifyGasTooHigh = new(TxPoolErrorMessages.FrameTxVerifyGasTooHigh); /// An EIP-8141 frame transaction whose validation prefix budgets more state gas than MAX_VERIFY_STATE_GAS. A propagation bound separate from , not a validity rule. public static readonly AcceptTxResult FrameTxVerifyStateGasTooHigh = new(TxPoolErrorMessages.FrameTxVerifyStateGasTooHigh); /// An EIP-8250 transaction whose selected nonce keys are not all at its nonce_seq: an exact match, so neither old nor future. public static readonly AcceptTxResult KeyedNonceUnmet = new(TxPoolErrorMessages.KeyedNonceUnmet); /// An EIP-8141 frame transaction whose resolved payer's summed pending maximum cost would exceed the payer's balance. public static readonly AcceptTxResult FrameTxPayerExposureExceeded = new(TxPoolErrorMessages.FrameTxPayerExposureExceeded); /// An EIP-8141 frame transaction whose validation prefix can never approve a payer: unincludable rather than malformed, so the relaying peer is not disconnected. public static readonly AcceptTxResult FrameTxNoPayer = new(TxPoolErrorMessages.FrameTxNoPayer); /// An EIP-8141 frame transaction whose prefix is outside the public-mempool grammar. A propagation bound, not a validity rule. public static readonly AcceptTxResult FrameTxUnrecognizedPrefix = new(TxPoolErrorMessages.FrameTxUnrecognizedPrefix); /// An EIP-8141 blob-carrying frame transaction submitted without the blob sidecar that its mempool form requires. public static readonly AcceptTxResult FrameTxMissingSidecar = new(TxPoolErrorMessages.FrameTxMissingSidecar); /// An EIP-8141 frame transaction with a VERIFY frame behind its validation prefix. A propagation bound, not a validity rule. public static readonly AcceptTxResult FrameTxVerifyAfterPrefix = new(TxPoolErrorMessages.FrameTxVerifyAfterPrefix); /// An EIP-8141 frame transaction whose expiry verifier frame does not lead its frame list. A propagation bound, not a validity rule. public static readonly AcceptTxResult FrameTxMisplacedExpiryFrame = new(TxPoolErrorMessages.FrameTxMisplacedExpiryFrame); /// An EIP-8141 frame transaction with a malformed, repeated or misplaced EIP-8272 recent_root_verify frame. A propagation bound, not a validity rule. public static readonly AcceptTxResult FrameTxMisplacedRecentRootFrame = new(TxPoolErrorMessages.FrameTxMisplacedRecentRootFrame); /// A blob-carrying EIP-8141 frame transaction with an EIP-8272 recent_root_verify frame, offered to a persistent blob pool that cannot revalidate it. A propagation bound, not a validity rule. public static readonly AcceptTxResult FrameTxRecentRootWithPersistentBlobs = new(TxPoolErrorMessages.FrameTxRecentRootWithPersistentBlobs); /// An EIP-8141 frame transaction whose opaque validation prefix failed in-pool simulation. public static readonly AcceptTxResult FrameSimulationFailed = new(TxPoolErrorMessages.FrameSimulationFailed); /// An EIP-8141 frame transaction paying through an already fully-committed non-canonical paymaster. public static readonly AcceptTxResult NonCanonicalPaymasterLimitReached = new(TxPoolErrorMessages.NonCanonicalPaymasterLimitReached); /// An EIP-8141 frame transaction whose prefix relies on an account whose code can change (EIP-8298), or that EIP-8151 ecRecover returned, already relied on by the maximum number of pending transactions, or by the pay frames of the maximum number of paymasters. public static readonly AcceptTxResult FrameTxCodeDependencyLimitReached = new(TxPoolErrorMessages.FrameTxCodeDependencyLimitReached); /// /// The node declined to simulate an EIP-8141 validation prefix because its own admission bounds were spent, /// or stopped simulating it or verifying its signatures to yield to its own block work, so the transaction was /// never judged. /// /// /// Distinct from so peer scoring can tell load shedding apart from /// a peer sending transactions this node rejects. /// public static readonly AcceptTxResult FrameSimulationDeferred = new(TxPoolErrorMessages.FrameSimulationDeferred); /// /// An EIP-8141 frame transaction dropped unvalidated because the peer that sent it had spent its validation /// gas budget on transactions that failed validation. /// /// /// Unlike , the peer caused this, so it counts towards flood disconnects. /// public static readonly AcceptTxResult FramePeerValidationBudgetSpent = new(TxPoolErrorMessages.FramePeerValidationBudgetSpent); /// /// An EIP-8250 keyed-nonce frame transaction beyond its sender's free pending baseline, submitted while /// the sender holds too little MATCHA width to admit another. A local mempool policy, not a validity rule. /// public static readonly AcceptTxResult WidthUnmet = new(TxPoolErrorMessages.WidthUnmet); /// /// An EIP-8250 keyed-nonce frame transaction whose nonce-key set intersects a pending transaction of /// the same sender. EIP-8250 admits several pending frame transactions per sender only on disjoint key /// sets, so this is a local mempool policy, not a validity rule. /// public static readonly AcceptTxResult KeyedNonceOverlap = new(TxPoolErrorMessages.KeyedNonceOverlap); /// /// An EIP-8141 frame transaction beyond its non-canonical paymaster's free pending baseline, submitted /// while the paymaster holds too little MATCHA width to sponsor another. A local mempool policy, not a validity rule. /// public static readonly AcceptTxResult PaymasterWidthUnmet = new(TxPoolErrorMessages.PaymasterWidthUnmet); /// /// Declares a result distinct from every other declared result. /// /// For static declarations only: every call permanently consumes an id from a process-wide /// counter, so two calls with identical arguments are not equal to each other. /// The short code reported to the submitter. /// An optional detail appended to . public AcceptTxResult(string code, string? message = null) : this(Interlocked.Increment(ref _nextId) - 1, code, message) { } private AcceptTxResult(int id, string code, string? message) { Id = id; Code = code; Message = message; } private int Id { get; } private string Code { get; } private string? Message { get; } public static implicit operator bool(AcceptTxResult result) => result.Id == Accepted.Id; public static implicit operator AcceptTxResult(bool result) => result ? Accepted : Invalid; public AcceptTxResult WithMessage(string message) => new(Id, Code, message); public static bool operator ==(AcceptTxResult a, AcceptTxResult b) => a.Equals(b); public static bool operator !=(AcceptTxResult a, AcceptTxResult b) => !(a == b); public override bool Equals(object? obj) => obj is AcceptTxResult result && Equals(result); public bool Equals(AcceptTxResult result) => Id == result.Id; public override int GetHashCode() => Id.GetHashCode(); public override string ToString() => Message is null ? Code : $"{Code}, {Message}"; } }