// 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}";
}
}