diff --git a/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs b/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs index 496f107e7ea..4f6a49f2375 100644 --- a/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs +++ b/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs @@ -74,7 +74,7 @@ public interface IApiWithBlockchain : IApiWithStores, IBlockchainBridgeFactory IHealthHintService? HealthHintService { get; set; } IRpcCapabilitiesProvider? RpcCapabilitiesProvider { get; set; } ITransactionComparerProvider? TransactionComparerProvider { get; set; } - ITxValidator? TxValidator { get; set; } + TxValidator? TxValidator { get; set; } /// /// Manager of block finalization diff --git a/src/Nethermind/Nethermind.Api/INethermindApi.cs b/src/Nethermind/Nethermind.Api/INethermindApi.cs index 8d864b2e9f8..fff51021393 100644 --- a/src/Nethermind/Nethermind.Api/INethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/INethermindApi.cs @@ -2,7 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only #nullable enable +using System; using Nethermind.Config; +using Nethermind.Core; +using Nethermind.Serialization.Rlp; +using Nethermind.Serialization.Rlp.TxDecoders; +using Nethermind.TxPool; namespace Nethermind.Api { @@ -15,4 +20,15 @@ public T Config() where T : IConfig (IApiWithNetwork GetFromApi, INethermindApi SetInApi) ForRpc => (this, this); } + + public static class NethermindApiExtensions + { + public static void RegisterTxType(this INethermindApi api, TxType type, ITxDecoder decoder, ITxValidator validator) + { + ArgumentNullException.ThrowIfNull(api.TxValidator); + + api.TxValidator.RegisterValidator(type, validator); + TxDecoder.Instance.RegisterDecoder(decoder); + } + } } diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index e07aa13ba9e..e9f7588ea36 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -208,7 +208,7 @@ public ISealEngine SealEngine public ITxPoolInfoProvider? TxPoolInfoProvider { get; set; } public IHealthHintService? HealthHintService { get; set; } public IRpcCapabilitiesProvider? RpcCapabilitiesProvider { get; set; } - public ITxValidator? TxValidator { get; set; } + public TxValidator? TxValidator { get; set; } public IBlockFinalizationManager? FinalizationManager { get; set; } public IGasLimitCalculator? GasLimitCalculator { get; set; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TestTransactionValidator.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TestTransactionValidator.cs deleted file mode 100644 index 430e5bb2e6e..00000000000 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TestTransactionValidator.cs +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Nethermind.Core; -using Nethermind.Core.Specs; -using Nethermind.TxPool; - -namespace Nethermind.Blockchain.Test.Validators -{ - public class TestTxValidator : ITxValidator - { - public static TestTxValidator AlwaysValid = new(true); - public static TestTxValidator NeverValid = new(false); - - private readonly Queue _validationResults = new(); - private readonly bool? _alwaysSameResult; - - public TestTxValidator(Queue validationResults) - { - _validationResults = validationResults; - } - - public TestTxValidator(bool validationResult) - { - _alwaysSameResult = validationResult; - } - - public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) - { - return _alwaysSameResult ?? _validationResults.Dequeue(); - } - public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec, [NotNullWhen(false)] out string? errorMessage) - { - errorMessage = null; - return _alwaysSameResult ?? _validationResults.Dequeue(); - } - } -} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index 8e91f13773b..7d3bb9bc00b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -56,7 +56,7 @@ public void Zero_r_is_not_valid() Transaction tx = Build.A.Transaction.WithSignature(signature).TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - txValidator.IsWellFormed(tx, MuirGlacier.Instance).Should().BeFalse(); + txValidator.IsWellFormed(tx, MuirGlacier.Instance).AsBool().Should().BeFalse(); } private static byte CalculateV() => (byte)EthereumEcdsa.CalculateV(TestBlockchainIds.ChainId); @@ -72,7 +72,7 @@ public void Zero_s_is_not_valid() Transaction tx = Build.A.Transaction.WithSignature(signature).TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - txValidator.IsWellFormed(tx, MuirGlacier.Instance).Should().BeFalse(); + txValidator.IsWellFormed(tx, MuirGlacier.Instance).AsBool().Should().BeFalse(); } [Test, Timeout(Timeout.MaxTestTime)] @@ -86,7 +86,7 @@ public void Bad_chain_id_is_not_valid() Transaction tx = Build.A.Transaction.WithSignature(signature).TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - txValidator.IsWellFormed(tx, MuirGlacier.Instance).Should().BeFalse(); + txValidator.IsWellFormed(tx, MuirGlacier.Instance).AsBool().Should().BeFalse(); } [Test, Timeout(Timeout.MaxTestTime)] @@ -100,7 +100,7 @@ public void No_chain_id_legacy_tx_is_valid() Transaction tx = Build.A.Transaction.WithSignature(signature).TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - txValidator.IsWellFormed(tx, MuirGlacier.Instance).Should().BeTrue(); + txValidator.IsWellFormed(tx, MuirGlacier.Instance).AsBool().Should().BeTrue(); } [Test, Timeout(Timeout.MaxTestTime)] @@ -114,7 +114,7 @@ public void Is_valid_with_valid_chain_id() Transaction tx = Build.A.Transaction.WithSignature(signature).TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - txValidator.IsWellFormed(tx, MuirGlacier.Instance).Should().BeTrue(); + txValidator.IsWellFormed(tx, MuirGlacier.Instance).AsBool().Should().BeTrue(); } [Timeout(Timeout.MaxTestTime)] @@ -134,7 +134,7 @@ public void Before_eip_155_has_to_have_valid_chain_id_unless_overridden(bool val releaseSpec.ValidateChainId.Returns(validateChainId); TxValidator txValidator = new(TestBlockchainIds.ChainId); - txValidator.IsWellFormed(tx, releaseSpec).Should().Be(!validateChainId); + txValidator.IsWellFormed(tx, releaseSpec).AsBool().Should().Be(!validateChainId); } [Timeout(Timeout.MaxTestTime)] @@ -274,7 +274,7 @@ public void Transaction_with_init_code_above_max_value_is_rejected_when_eip3860E .WithData(initCode).TestObject; TxValidator txValidator = new(1); - txValidator.IsWellFormed(tx, releaseSpec).Should().Be(expectedResult); + txValidator.IsWellFormed(tx, releaseSpec).AsBool().Should().Be(expectedResult); } //leading zeros in AccessList - expected to pass (real mainnet tx) @@ -345,8 +345,8 @@ public void ShardBlobTransactions_should_have_destination_set() .WithChainId(TestBlockchainIds.ChainId) .SignedAndResolved().TestObject; - Assert.That(txValidator.IsWellFormed(txWithoutTo, Cancun.Instance), Is.False); - Assert.That(txValidator.IsWellFormed(txWithTo, Cancun.Instance)); + Assert.That(txValidator.IsWellFormed(txWithoutTo, Cancun.Instance).AsBool(), Is.False); + Assert.That(txValidator.IsWellFormed(txWithTo, Cancun.Instance).AsBool()); } [Timeout(Timeout.MaxTestTime)] @@ -404,9 +404,8 @@ public void IsWellFormed_NotBlobTxButMaxFeePerBlobGasIsSet_ReturnFalse() Transaction tx = txBuilder.TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - string? error; - Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance, out error), Is.False); + Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } [Test] @@ -419,9 +418,8 @@ public void IsWellFormed_NotBlobTxButBlobVersionedHashesIsSet_ReturnFalse() Transaction tx = txBuilder.TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - string? error; - Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance, out error), Is.False); + Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } [Test] @@ -437,9 +435,8 @@ public void IsWellFormed_BlobTxToIsNull_ReturnFalse() Transaction tx = txBuilder.TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - string? error; - Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance, out error), Is.False); + Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } [Test] public void IsWellFormed_BlobTxHasMoreDataGasThanAllowed_ReturnFalse() @@ -454,9 +451,8 @@ public void IsWellFormed_BlobTxHasMoreDataGasThanAllowed_ReturnFalse() Transaction tx = txBuilder.TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - string? error; - Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance, out error), Is.False); + Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } [Test] @@ -472,9 +468,8 @@ public void IsWellFormed_BlobTxHasNoBlobs_ReturnFalse() Transaction tx = txBuilder.TestObject; TxValidator txValidator = new(TestBlockchainIds.ChainId); - string? error; - Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance, out error), Is.False); + Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } [Test] @@ -490,9 +485,8 @@ public void IsWellFormed_BlobTxHasBlobOverTheSizeLimit_ReturnFalse() Transaction tx = txBuilder.TestObject; ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Blobs[0] = new byte[Ckzg.Ckzg.BytesPerBlob + 1]; TxValidator txValidator = new(TestBlockchainIds.ChainId); - string? error; - Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance, out error), Is.False); + Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } [Test] @@ -508,9 +502,8 @@ public void IsWellFormed_BlobTxHasCommitmentOverTheSizeLimit_ReturnFalse() Transaction tx = txBuilder.TestObject; ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Commitments[0] = new byte[Ckzg.Ckzg.BytesPerCommitment + 1]; TxValidator txValidator = new(TestBlockchainIds.ChainId); - string? error; - Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance, out error), Is.False); + Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } [Test] @@ -526,9 +519,8 @@ public void IsWellFormed_BlobTxHasProofOverTheSizeLimit_ReturnFalse() Transaction tx = txBuilder.TestObject; ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Proofs[0] = new byte[Ckzg.Ckzg.BytesPerProof + 1]; TxValidator txValidator = new(TestBlockchainIds.ChainId); - string? error; - Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance, out error), Is.False); + Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } private static byte[] MakeArray(int count, params byte[] elements) => diff --git a/src/Nethermind/Nethermind.Consensus/Validators/AlwaysValid.cs b/src/Nethermind/Nethermind.Consensus/Validators/AlwaysValid.cs index 92ec5b749bf..0550ac08ddb 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/AlwaysValid.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/AlwaysValid.cs @@ -11,9 +11,11 @@ namespace Nethermind.Consensus.Validators; public class Always : IBlockValidator, ISealValidator, IUnclesValidator, ITxValidator { private readonly bool _result; + private readonly ValidationResult _validationResult; private Always(bool result) { + _validationResult = result ? ValidationResult.Success : "Always invalid."; _result = result; } @@ -79,16 +81,10 @@ public bool Validate(BlockHeader header, BlockHeader[] uncles) return _result; } - public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) { - return _result; + return _validationResult; } - public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec, out string? errorMessage) - { - errorMessage = null; - return _result; - } - public bool ValidateWithdrawals(Block block, out string? error) { error = null; diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index 04e0ca08f68..e720c5c44f9 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -292,9 +292,11 @@ private bool ValidateTransactions(Block block, IReleaseSpec spec, out string? er { Transaction transaction = transactions[txIndex]; - if (!_txValidator.IsWellFormed(transaction, spec, out errorMessage)) + ValidationResult isWellFormed = _txValidator.IsWellFormed(transaction, spec); + if (!isWellFormed) { - if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Invalid transaction: {errorMessage}"); + if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Invalid transaction: {isWellFormed}"); + errorMessage = isWellFormed.Error; return false; } } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs index 409f7c32a6d..5207135b109 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs @@ -2,294 +2,283 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using Nethermind.Consensus.Messages; using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.TxPool; +using Nethermind.Core.Crypto; using Nethermind.Crypto; using Nethermind.Evm; using Nethermind.Int256; -using Nethermind.TxPool; -namespace Nethermind.Consensus.Validators -{ - public class TxValidator : ITxValidator - { - private readonly ulong _chainIdValue; +namespace Nethermind.Consensus.Validators; - public TxValidator(ulong chainId) - { - _chainIdValue = chainId; - } +public sealed class TxValidator : ITxValidator +{ + private readonly ITxValidator?[] _validators = new ITxValidator?[Transaction.MaxTxType + 1]; - /* 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 IsWellFormed(transaction, releaseSpec, out _); - } - public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec, out string? error) - { - error = null; - - // validate type before calculating intrinsic gas to avoid exception - return ValidateTxType(transaction, releaseSpec, ref error) - // This is unnecessarily calculated twice - at validation and execution times. - && ValidateWithError(transaction.GasLimit >= IntrinsicGasCalculator.Calculate(transaction, releaseSpec), TxErrorMessages.IntrinsicGasTooLow, ref error) - // 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 - && ValidateWithError(ValidateSignature(transaction, releaseSpec), TxErrorMessages.InvalidTxSignature, ref error) - && ValidateChainId(transaction, ref error) - && ValidateWithError(Validate1559GasFields(transaction, releaseSpec), TxErrorMessages.InvalidMaxPriorityFeePerGas, ref error) - && ValidateWithError(Validate3860Rules(transaction, releaseSpec), TxErrorMessages.ContractSizeTooBig, ref error) - && Validate4844Fields(transaction, ref error); - } + public TxValidator(ulong chainId) + { + RegisterValidator(TxType.Legacy, new CompositeTxValidator([ + IntrinsicGasTxValidator.Instance, + new LegacySignatureTxValidator(chainId), + ContractSizeTxValidator.Instance, + NonBlobFieldsTxValidator.Instance, + ])); + RegisterValidator(TxType.AccessList, new CompositeTxValidator([ + new ReleaseSpecTxValidator(static spec => spec.IsEip2930Enabled), + IntrinsicGasTxValidator.Instance, + SignatureTxValidator.Instance, + new ExpectedChainIdTxValidator(chainId), + ContractSizeTxValidator.Instance, + NonBlobFieldsTxValidator.Instance, + ])); + RegisterValidator(TxType.EIP1559, new CompositeTxValidator([ + new ReleaseSpecTxValidator(static spec => spec.IsEip1559Enabled), + IntrinsicGasTxValidator.Instance, + SignatureTxValidator.Instance, + new ExpectedChainIdTxValidator(chainId), + GasFieldsTxValidator.Instance, + ContractSizeTxValidator.Instance, + NonBlobFieldsTxValidator.Instance, + ])); + RegisterValidator(TxType.Blob, new CompositeTxValidator([ + new ReleaseSpecTxValidator(static spec => spec.IsEip4844Enabled), + IntrinsicGasTxValidator.Instance, + SignatureTxValidator.Instance, + new ExpectedChainIdTxValidator(chainId), + GasFieldsTxValidator.Instance, + ContractSizeTxValidator.Instance, + BlobFieldsTxValidator.Instance, + MempoolBlobTxValidator.Instance + ])); + } - private static bool Validate3860Rules(Transaction transaction, IReleaseSpec releaseSpec) => - !transaction.IsAboveInitCode(releaseSpec); + public void RegisterValidator(TxType type, ITxValidator validator) => _validators[(byte)type] = validator; + + /// + /// 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 as long as we also validate nonce + /// just before the execution of the block / tx. + /// + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + _validators.TryGetByTxType(transaction.Type, out ITxValidator validator) + ? validator.IsWellFormed(transaction, releaseSpec) + : TxErrorMessages.InvalidTxType(releaseSpec.Name); +} - private static bool ValidateTxType(Transaction transaction, IReleaseSpec releaseSpec, ref string error) +public sealed class CompositeTxValidator(List validators) : ITxValidator +{ + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) + { + foreach (ITxValidator validator in validators) { - bool result = transaction.Type switch + ValidationResult isWellFormed = validator.IsWellFormed(transaction, releaseSpec); + if (!isWellFormed) { - TxType.Legacy => true, - TxType.AccessList => releaseSpec.UseTxAccessLists, - TxType.EIP1559 => releaseSpec.IsEip1559Enabled, - TxType.Blob => releaseSpec.IsEip4844Enabled, - _ => false - }; - - if (!result) - { - error = TxErrorMessages.InvalidTxType(releaseSpec.Name); - return false; + return isWellFormed; } - - return true; } + return ValidationResult.Success; + } +} - private static bool Validate1559GasFields(Transaction transaction, IReleaseSpec releaseSpec) - { - if (!releaseSpec.IsEip1559Enabled || !transaction.Supports1559) - return true; - - return transaction.MaxFeePerGas >= transaction.MaxPriorityFeePerGas; - } - - private bool ValidateChainId(Transaction transaction, ref string? error) - { - return transaction.Type switch - { - TxType.Legacy => true, - _ => ValidateChainIdNonLegacy(transaction.ChainId, ref error) - }; - - bool ValidateChainIdNonLegacy(ulong? chainId, ref string? error) - { - bool result = chainId == _chainIdValue; - if (!result) - { - error = TxErrorMessages.InvalidTxChainId(_chainIdValue, transaction.ChainId); - return false; - } +public sealed class IntrinsicGasTxValidator : ITxValidator +{ + public static readonly IntrinsicGasTxValidator Instance = new(); + private IntrinsicGasTxValidator() { } + + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + // This is unnecessarily calculated twice - at validation and execution times. + transaction.GasLimit < IntrinsicGasCalculator.Calculate(transaction, releaseSpec) + ? TxErrorMessages.IntrinsicGasTooLow + : ValidationResult.Success; +} - return true; - } - } +public sealed class ReleaseSpecTxValidator(Func validate) : ITxValidator +{ + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + !validate(releaseSpec) ? TxErrorMessages.InvalidTxType(releaseSpec.Name) : ValidationResult.Success; +} - private bool ValidateWithError(bool validation, string errorMessage, ref string? error) - { - if (!validation) - { - error = errorMessage; - return false; - } +public sealed class ExpectedChainIdTxValidator(ulong chainId) : ITxValidator +{ + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.ChainId != chainId ? TxErrorMessages.InvalidTxChainId(chainId, transaction.ChainId) : ValidationResult.Success; +} - return true; - } +public sealed class GasFieldsTxValidator : ITxValidator +{ + public static readonly GasFieldsTxValidator Instance = new(); + private GasFieldsTxValidator() { } - private bool ValidateSignature(Transaction tx, IReleaseSpec spec) - { - Signature? signature = tx.Signature; + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.MaxFeePerGas < transaction.MaxPriorityFeePerGas ? TxErrorMessages.InvalidMaxPriorityFeePerGas : ValidationResult.Success; +} - if (signature is null) - { - return false; - } +public sealed class ContractSizeTxValidator : ITxValidator +{ + public static readonly ContractSizeTxValidator Instance = new(); + private ContractSizeTxValidator() { } - UInt256 sValue = new(signature.SAsSpan, isBigEndian: true); - UInt256 rValue = new(signature.RAsSpan, isBigEndian: true); + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.IsAboveInitCode(releaseSpec) ? TxErrorMessages.ContractSizeTooBig : ValidationResult.Success; +} - if (sValue.IsZero || sValue >= (spec.IsEip2Enabled ? Secp256K1Curve.HalfNPlusOne : Secp256K1Curve.N)) - { - return false; - } +/// +/// Ensure that non Blob transactions do not contain Blob specific fields. +/// This validator will be deprecated once we have a proper Transaction type hierarchy. +/// +public sealed class NonBlobFieldsTxValidator : ITxValidator +{ + public static readonly NonBlobFieldsTxValidator Instance = new(); + private NonBlobFieldsTxValidator() { } - if (rValue.IsZero || rValue >= Secp256K1Curve.NMinusOne) - { - return false; - } + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => transaction switch + { + // Execution-payload version verification + { MaxFeePerBlobGas: not null } => TxErrorMessages.NotAllowedMaxFeePerBlobGas, + { BlobVersionedHashes: not null } => TxErrorMessages.NotAllowedBlobVersionedHashes, + { NetworkWrapper: ShardBlobNetworkWrapper } => TxErrorMessages.InvalidTransaction, + _ => ValidationResult.Success + }; +} - if (signature.V is 27 or 28) - { - return true; - } +public sealed class BlobFieldsTxValidator : ITxValidator +{ + public static readonly BlobFieldsTxValidator Instance = new(); + private BlobFieldsTxValidator() { } - if (tx.Type == TxType.Legacy && spec.IsEip155Enabled && (signature.V == _chainIdValue * 2 + 35ul || signature.V == _chainIdValue * 2 + 36ul)) - { - return true; - } + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + transaction switch + { + { To: null } => TxErrorMessages.TxMissingTo, + { MaxFeePerBlobGas: null } => TxErrorMessages.BlobTxMissingMaxFeePerBlobGas, + { BlobVersionedHashes: null } => TxErrorMessages.BlobTxMissingBlobVersionedHashes, + _ => ValidateBlobFields(transaction) + }; - return !spec.ValidateChainId; - } + private ValidationResult ValidateBlobFields(Transaction transaction) + { + int blobCount = transaction.BlobVersionedHashes!.Length; + ulong totalDataGas = BlobGasCalculator.CalculateBlobGas(blobCount); + return totalDataGas > Eip4844Constants.MaxBlobGasPerTransaction ? TxErrorMessages.BlobTxGasLimitExceeded + : blobCount < Eip4844Constants.MinBlobsPerTransaction ? TxErrorMessages.BlobTxMissingBlobs + : ValidateBlobVersionedHashes(); - private static bool Validate4844Fields(Transaction transaction, ref string? error) + ValidationResult ValidateBlobVersionedHashes() { - // Execution-payload version verification - if (!transaction.SupportsBlobs) + for (int i = 0; i < blobCount; i++) { - if (transaction.MaxFeePerBlobGas is not null) - { - error = TxErrorMessages.NotAllowedMaxFeePerBlobGas; - return false; - } - - if (transaction.BlobVersionedHashes is not null) - { - error = TxErrorMessages.NotAllowedBlobVersionedHashes; - return false; - } - - if (transaction is { NetworkWrapper: ShardBlobNetworkWrapper }) + switch (transaction.BlobVersionedHashes[i]) { - //This must be an internal issue? - error = TxErrorMessages.InvalidTransaction; - return false; + case null: return TxErrorMessages.MissingBlobVersionedHash; + case { Length: not KzgPolynomialCommitments.BytesPerBlobVersionedHash }: return TxErrorMessages.InvalidBlobVersionedHashSize; + case { Length: KzgPolynomialCommitments.BytesPerBlobVersionedHash } when transaction.BlobVersionedHashes[i][0] != KzgPolynomialCommitments.KzgBlobHashVersionV1: return TxErrorMessages.InvalidBlobVersionedHashVersion; } - - return true; - } - - if (transaction.To is null) - { - error = TxErrorMessages.TxMissingTo; - return false; } - if (transaction.MaxFeePerBlobGas is null) - { - error = TxErrorMessages.BlobTxMissingMaxFeePerBlobGas; - return false; - } - - if (transaction.BlobVersionedHashes is null) - { - error = TxErrorMessages.BlobTxMissingBlobVersionedHashes; - return false; - } - - int blobCount = transaction.BlobVersionedHashes.Length; - ulong totalDataGas = BlobGasCalculator.CalculateBlobGas(blobCount); - if (totalDataGas > Eip4844Constants.MaxBlobGasPerTransaction) - { - error = TxErrorMessages.BlobTxGasLimitExceeded; - return false; - } + return ValidationResult.Success; + } + } +} - if (blobCount < Eip4844Constants.MinBlobsPerTransaction) - { - error = TxErrorMessages.BlobTxMissingBlobs; - return false; - } +/// +/// Validate Blob transactions in mempool version. +/// +public sealed class MempoolBlobTxValidator : ITxValidator +{ + public static readonly MempoolBlobTxValidator Instance = new(); + private MempoolBlobTxValidator() { } + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) + { + int blobCount = transaction.BlobVersionedHashes!.Length; + return transaction.NetworkWrapper is not ShardBlobNetworkWrapper wrapper ? ValidationResult.Success + : wrapper.Blobs.Length != blobCount ? TxErrorMessages.InvalidBlobData + : wrapper.Commitments.Length != blobCount ? TxErrorMessages.InvalidBlobData + : wrapper.Proofs.Length != blobCount ? TxErrorMessages.InvalidBlobData + : ValidateBlobs(); + + ValidationResult ValidateBlobs() + { for (int i = 0; i < blobCount; i++) { - if (transaction.BlobVersionedHashes[i] is null) + if (wrapper.Blobs[i].Length != Ckzg.Ckzg.BytesPerBlob) { - error = TxErrorMessages.MissingBlobVersionedHash; - return false; + return TxErrorMessages.ExceededBlobSize; } - if (transaction.BlobVersionedHashes[i].Length != KzgPolynomialCommitments.BytesPerBlobVersionedHash) + if (wrapper.Commitments[i].Length != Ckzg.Ckzg.BytesPerCommitment) { - error = TxErrorMessages.InvalidBlobVersionedHashSize; - return false; + return TxErrorMessages.ExceededBlobCommitmentSize; } - if (transaction.BlobVersionedHashes[i][0] != KzgPolynomialCommitments.KzgBlobHashVersionV1) + if (wrapper.Proofs[i].Length != Ckzg.Ckzg.BytesPerProof) { - error = TxErrorMessages.InvalidBlobVersionedHashVersion; - return false; + return TxErrorMessages.InvalidBlobProofSize; } } - // Mempool version verification if presents - if (transaction.NetworkWrapper is ShardBlobNetworkWrapper wrapper) + Span hash = stackalloc byte[32]; + for (int i = 0; i < blobCount; i++) { - if (wrapper.Blobs.Length != blobCount) - { - error = TxErrorMessages.InvalidBlobData; - return false; - } - - if (wrapper.Commitments.Length != blobCount) + if (!KzgPolynomialCommitments.TryComputeCommitmentHashV1(wrapper.Commitments[i].AsSpan(), hash) || !hash.SequenceEqual(transaction.BlobVersionedHashes[i])) { - error = TxErrorMessages.InvalidBlobData; - return false; + return TxErrorMessages.InvalidBlobCommitmentHash; } + } - if (wrapper.Proofs.Length != blobCount) - { - error = TxErrorMessages.InvalidBlobData; - return false; - } + return !KzgPolynomialCommitments.AreProofsValid(wrapper.Blobs, wrapper.Commitments, wrapper.Proofs) + ? TxErrorMessages.InvalidBlobProof + : ValidationResult.Success; + } + } +} - for (int i = 0; i < blobCount; i++) - { - if (wrapper.Blobs[i].Length != Ckzg.Ckzg.BytesPerBlob) - { - error = TxErrorMessages.ExceededBlobSize; - return false; - } - if (wrapper.Commitments[i].Length != Ckzg.Ckzg.BytesPerCommitment) - { - error = TxErrorMessages.ExceededBlobCommitmentSize; - return false; - } - if (wrapper.Proofs[i].Length != Ckzg.Ckzg.BytesPerProof) - { - error = TxErrorMessages.InvalidBlobProofSize; - return false; - } - } +public abstract class BaseSignatureTxValidator : ITxValidator +{ + protected virtual ValidationResult ValidateChainId(Transaction transaction, IReleaseSpec releaseSpec) => + releaseSpec.ValidateChainId ? TxErrorMessages.InvalidTxSignature : ValidationResult.Success; - Span hash = stackalloc byte[32]; - for (int i = 0; i < blobCount; i++) - { - if (!KzgPolynomialCommitments.TryComputeCommitmentHashV1( - wrapper.Commitments[i].AsSpan(), hash) || - !hash.SequenceEqual(transaction.BlobVersionedHashes[i])) - { - error = TxErrorMessages.InvalidBlobCommitmentHash; - return false; - } - } + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) + { + Signature? signature = transaction.Signature; + if (signature is null) + { + return TxErrorMessages.InvalidTxSignature; + } - if (!KzgPolynomialCommitments.AreProofsValid(wrapper.Blobs, wrapper.Commitments, wrapper.Proofs)) - { - error = TxErrorMessages.InvalidBlobProof; - return false; - } + UInt256 sValue = new(signature.SAsSpan, isBigEndian: true); + UInt256 rValue = new(signature.RAsSpan, isBigEndian: true); - } + UInt256 sMax = releaseSpec.IsEip2Enabled ? Secp256K1Curve.HalfNPlusOne : Secp256K1Curve.N; + return sValue.IsZero || sValue >= sMax ? TxErrorMessages.InvalidTxSignature + : rValue.IsZero || rValue >= Secp256K1Curve.NMinusOne ? TxErrorMessages.InvalidTxSignature + : signature.V is 27 or 28 ? ValidationResult.Success + : ValidateChainId(transaction, releaseSpec); + } +} - return true; - } +public sealed class LegacySignatureTxValidator(ulong chainId) : BaseSignatureTxValidator +{ + protected override ValidationResult ValidateChainId(Transaction transaction, IReleaseSpec releaseSpec) + { + ulong v = transaction.Signature!.V; + return releaseSpec.IsEip155Enabled && (v == chainId * 2 + 35ul || v == chainId * 2 + 36ul) + ? ValidationResult.Success + : base.ValidateChainId(transaction, releaseSpec); } } + +public sealed class SignatureTxValidator : BaseSignatureTxValidator +{ + public static readonly SignatureTxValidator Instance = new(); + private SignatureTxValidator() { } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionValidatorBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionValidatorBuilder.cs index f945817a461..fe41081f60e 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionValidatorBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionValidatorBuilder.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus.Validators; using Nethermind.Core.Specs; using Nethermind.TxPool; using NSubstitute; @@ -9,7 +10,7 @@ namespace Nethermind.Core.Test.Builders { public class TransactionValidatorBuilder : BuilderBase { - private bool _alwaysTrue; + private ValidationResult _always; public TransactionValidatorBuilder() { @@ -20,7 +21,7 @@ public TransactionValidatorBuilder ThatAlwaysReturnsFalse { get { - _alwaysTrue = false; + _always = "Invalid"; return this; } } @@ -29,15 +30,15 @@ public TransactionValidatorBuilder ThatAlwaysReturnsTrue { get { - _alwaysTrue = true; + _always = ValidationResult.Success; return this; } } protected override void BeforeReturn() { - TestObjectInternal.IsWellFormed(Arg.Any(), Arg.Any(), out _).Returns(_alwaysTrue); - TestObjectInternal.IsWellFormed(Arg.Any(), Arg.Any(), out _).Returns(_alwaysTrue); + TestObjectInternal.IsWellFormed(Arg.Any(), Arg.Any()).Returns(_always); + TestObjectInternal.IsWellFormed(Arg.Any(), Arg.Any()).Returns(_always); base.BeforeReturn(); } } diff --git a/src/Nethermind/Nethermind.Core/TransactionExtensions.cs b/src/Nethermind/Nethermind.Core/TransactionExtensions.cs index 412bce0e3c8..7f54a6a18ef 100644 --- a/src/Nethermind/Nethermind.Core/TransactionExtensions.cs +++ b/src/Nethermind/Nethermind.Core/TransactionExtensions.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Diagnostics.CodeAnalysis; using Nethermind.Core.Specs; using Nethermind.Int256; @@ -33,7 +34,9 @@ public static UInt256 CalculateTransactionPotentialCost(this Transaction tx, boo { UInt256 effectiveGasPrice = tx.CalculateEffectiveGasPrice(eip1559Enabled, baseFee); if (tx.IsServiceTransaction) + { effectiveGasPrice = UInt256.Zero; + } return effectiveGasPrice * (ulong)tx.GasLimit + tx.Value; } @@ -57,15 +60,23 @@ public static UInt256 CalculateEffectiveGasPrice(this Transaction tx, bool eip15 return effectiveGasPrice; } - public static UInt256 CalculateMaxPriorityFeePerGas(this Transaction tx, bool eip1559Enabled, in UInt256 baseFee) - { - return eip1559Enabled ? UInt256.Min(tx.MaxPriorityFeePerGas, tx.MaxFeePerGas > baseFee ? tx.MaxFeePerGas - baseFee : 0) : tx.MaxPriorityFeePerGas; - } - public static bool IsAboveInitCode(this Transaction tx, IReleaseSpec spec) - { - return tx.IsContractCreation && spec.IsEip3860Enabled && (tx.DataLength) > spec.MaxInitCodeSize; - } + public static UInt256 CalculateMaxPriorityFeePerGas(this Transaction tx, bool eip1559Enabled, in UInt256 baseFee) => + eip1559Enabled ? UInt256.Min(tx.MaxPriorityFeePerGas, tx.MaxFeePerGas > baseFee ? tx.MaxFeePerGas - baseFee : 0) : tx.MaxPriorityFeePerGas; + public static bool IsAboveInitCode(this Transaction tx, IReleaseSpec spec) => + tx.IsContractCreation && spec.IsEip3860Enabled && tx.DataLength > spec.MaxInitCodeSize; + public static bool TryGetByTxType(this T?[] array, TxType txType, [NotNullWhen(true)] out T? item) + { + var type = (byte)txType; + if (type > Transaction.MaxTxType) + { + item = default; + return false; + } + + item = array[type]; + return item != null; + } } } diff --git a/src/Nethermind/Nethermind.Core/ValidationResult.cs b/src/Nethermind/Nethermind.Core/ValidationResult.cs new file mode 100644 index 00000000000..739d7e5296e --- /dev/null +++ b/src/Nethermind/Nethermind.Core/ValidationResult.cs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core; + +public record struct ValidationResult(string? Error) +{ + public static ValidationResult Success => new(null); + public static implicit operator bool(ValidationResult result) => result.AsBool(); + public static implicit operator ValidationResult(string error) => new(error); + public override string ToString() => Error ?? "Success"; + public bool AsBool() => Error is null; +} diff --git a/src/Nethermind/Nethermind.Init/Steps/StepInitializationException.cs b/src/Nethermind/Nethermind.Init/Steps/StepInitializationException.cs index 8fe0e62a4f3..767a9daf54d 100644 --- a/src/Nethermind/Nethermind.Init/Steps/StepInitializationException.cs +++ b/src/Nethermind/Nethermind.Init/Steps/StepInitializationException.cs @@ -20,9 +20,7 @@ public StepDependencyException(string message) public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) { - if (argument is not null) - return; - throw new StepDependencyException(paramName ?? ""); + if (argument is null) throw new StepDependencyException(paramName ?? ""); } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/InvalidChainTracker/InvalidBlockInterceptor.cs b/src/Nethermind/Nethermind.Merge.Plugin/InvalidChainTracker/InvalidBlockInterceptor.cs index 87cbb58f650..3b32e476975 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/InvalidChainTracker/InvalidBlockInterceptor.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/InvalidChainTracker/InvalidBlockInterceptor.cs @@ -8,23 +8,15 @@ namespace Nethermind.Merge.Plugin.InvalidChainTracker; -public class InvalidBlockInterceptor : IBlockValidator +public class InvalidBlockInterceptor( + IBlockValidator headerValidator, + IInvalidChainTracker invalidChainTracker, + ILogManager logManager) + : IBlockValidator { - private readonly IBlockValidator _baseValidator; - private readonly IInvalidChainTracker _invalidChainTracker; - private readonly ILogger _logger; - - public InvalidBlockInterceptor( - IBlockValidator headerValidator, - IInvalidChainTracker invalidChainTracker, - ILogManager logManager) - { - _baseValidator = headerValidator; - _invalidChainTracker = invalidChainTracker; - _logger = logManager.GetClassLogger(typeof(InvalidBlockInterceptor)); - } + private readonly ILogger _logger = logManager.GetClassLogger(typeof(InvalidBlockInterceptor)); - public bool ValidateOrphanedBlock(Block block, [NotNullWhen(false)] out string? error) => _baseValidator.ValidateOrphanedBlock(block, out error); + public bool ValidateOrphanedBlock(Block block, [NotNullWhen(false)] out string? error) => headerValidator.ValidateOrphanedBlock(block, out error); public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle = false) { @@ -32,7 +24,7 @@ public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle = fal } public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle, [NotNullWhen(false)] out string? error) { - bool result = _baseValidator.Validate(header, parent, isUncle, out error); + bool result = headerValidator.Validate(header, parent, isUncle, out error); if (!result) { if (_logger.IsTrace) _logger.Trace($"Intercepted a bad header {header}"); @@ -41,9 +33,9 @@ public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle, [Not if (_logger.IsDebug) _logger.Debug($"Header invalidation should not be tracked"); return result; } - _invalidChainTracker.OnInvalidBlock(header.Hash!, header.ParentHash); + invalidChainTracker.OnInvalidBlock(header.Hash!, header.ParentHash); } - _invalidChainTracker.SetChildParent(header.Hash!, header.ParentHash!); + invalidChainTracker.SetChildParent(header.Hash!, header.ParentHash!); return result; } @@ -54,7 +46,7 @@ public bool Validate(BlockHeader header, bool isUncle = false) public bool Validate(BlockHeader header, bool isUncle, [NotNullWhen(false)] out string? error) { - bool result = _baseValidator.Validate(header, isUncle, out error); + bool result = headerValidator.Validate(header, isUncle, out error); if (!result) { if (_logger.IsTrace) _logger.Trace($"Intercepted a bad header {header}"); @@ -63,15 +55,15 @@ public bool Validate(BlockHeader header, bool isUncle, [NotNullWhen(false)] out if (_logger.IsDebug) _logger.Debug($"Header invalidation should not be tracked"); return result; } - _invalidChainTracker.OnInvalidBlock(header.Hash!, header.ParentHash); + invalidChainTracker.OnInvalidBlock(header.Hash!, header.ParentHash); } - _invalidChainTracker.SetChildParent(header.Hash!, header.ParentHash!); + invalidChainTracker.SetChildParent(header.Hash!, header.ParentHash!); return result; } public bool ValidateSuggestedBlock(Block block, [NotNullWhen(false)] out string? error, bool validateHashes = true) { - bool result = _baseValidator.ValidateSuggestedBlock(block, out error, validateHashes); + bool result = headerValidator.ValidateSuggestedBlock(block, out error, validateHashes); if (!result) { if (_logger.IsTrace) _logger.Trace($"Intercepted a bad block {block}"); @@ -80,9 +72,9 @@ public bool ValidateSuggestedBlock(Block block, [NotNullWhen(false)] out string? if (_logger.IsDebug) _logger.Debug($"Block invalidation should not be tracked"); return result; } - _invalidChainTracker.OnInvalidBlock(block.Hash!, block.ParentHash); + invalidChainTracker.OnInvalidBlock(block.Hash!, block.ParentHash); } - _invalidChainTracker.SetChildParent(block.Hash!, block.ParentHash!); + invalidChainTracker.SetChildParent(block.Hash!, block.ParentHash!); return result; } @@ -92,7 +84,7 @@ public bool ValidateProcessedBlock(Block block, TxReceipt[] receipts, Block sugg } public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock, [NotNullWhen(false)] out string? error) { - bool result = _baseValidator.ValidateProcessedBlock(processedBlock, receipts, suggestedBlock, out error); + bool result = headerValidator.ValidateProcessedBlock(processedBlock, receipts, suggestedBlock, out error); if (!result) { if (_logger.IsTrace) _logger.Trace($"Intercepted a bad block {processedBlock}"); @@ -101,9 +93,9 @@ public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, B if (_logger.IsDebug) _logger.Debug($"Block invalidation should not be tracked"); return result; } - _invalidChainTracker.OnInvalidBlock(suggestedBlock.Hash!, suggestedBlock.ParentHash); + invalidChainTracker.OnInvalidBlock(suggestedBlock.Hash!, suggestedBlock.ParentHash); } - _invalidChainTracker.SetChildParent(suggestedBlock.Hash!, suggestedBlock.ParentHash!); + invalidChainTracker.SetChildParent(suggestedBlock.Hash!, suggestedBlock.ParentHash!); return result; } @@ -115,7 +107,7 @@ private static bool ShouldNotTrackInvalidation(BlockHeader header) public bool ValidateWithdrawals(Block block, out string? error) { - bool result = _baseValidator.ValidateWithdrawals(block, out error); + bool result = headerValidator.ValidateWithdrawals(block, out error); if (!result) { @@ -128,10 +120,10 @@ public bool ValidateWithdrawals(Block block, out string? error) return false; } - _invalidChainTracker.OnInvalidBlock(block.Hash!, block.ParentHash); + invalidChainTracker.OnInvalidBlock(block.Hash!, block.ParentHash); } - _invalidChainTracker.SetChildParent(block.Hash!, block.ParentHash!); + invalidChainTracker.SetChildParent(block.Hash!, block.ParentHash!); return result; } diff --git a/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs b/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs index 80c7f2d7e98..e1548b52b92 100644 --- a/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs +++ b/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs @@ -11,6 +11,7 @@ using Nethermind.Consensus.Producers; using Nethermind.Consensus.Validators; using Nethermind.Consensus.Withdrawals; +using Nethermind.Core; using Nethermind.Evm; using Nethermind.Evm.TransactionProcessing; using Nethermind.Init.Steps; @@ -19,98 +20,81 @@ namespace Nethermind.Optimism; -public class InitializeBlockchainOptimism : InitializeBlockchain +public class InitializeBlockchainOptimism(OptimismNethermindApi api) : InitializeBlockchain(api) { - private readonly OptimismNethermindApi _api; - private readonly IBlocksConfig _blocksConfig; - - public InitializeBlockchainOptimism(OptimismNethermindApi api) : base(api) - { - _api = api; - _blocksConfig = api.Config(); - } + private readonly IBlocksConfig _blocksConfig = api.Config(); protected override Task InitBlockchain() { - _api.SpecHelper = new(_api.ChainSpec.Optimism); - _api.L1CostHelper = new(_api.SpecHelper, _api.ChainSpec.Optimism.L1BlockAddress); + api.RegisterTxType(TxType.DepositTx, new OptimismTxDecoder(), Always.Valid); + + api.SpecHelper = new(api.ChainSpec.Optimism); + api.L1CostHelper = new(api.SpecHelper, api.ChainSpec.Optimism.L1BlockAddress); return base.InitBlockchain(); } protected override ITransactionProcessor CreateTransactionProcessor(CodeInfoRepository codeInfoRepository, VirtualMachine virtualMachine) { - if (_api.SpecProvider is null) throw new StepDependencyException(nameof(_api.SpecProvider)); - if (_api.SpecHelper is null) throw new StepDependencyException(nameof(_api.SpecHelper)); - if (_api.L1CostHelper is null) throw new StepDependencyException(nameof(_api.L1CostHelper)); - if (_api.WorldState is null) throw new StepDependencyException(nameof(_api.WorldState)); + if (api.SpecProvider is null) throw new StepDependencyException(nameof(api.SpecProvider)); + if (api.SpecHelper is null) throw new StepDependencyException(nameof(api.SpecHelper)); + if (api.L1CostHelper is null) throw new StepDependencyException(nameof(api.L1CostHelper)); + if (api.WorldState is null) throw new StepDependencyException(nameof(api.WorldState)); return new OptimismTransactionProcessor( - _api.SpecProvider, - _api.WorldState, + api.SpecProvider, + api.WorldState, virtualMachine, - _api.LogManager, - _api.L1CostHelper, - _api.SpecHelper, + api.LogManager, + api.L1CostHelper, + api.SpecHelper, codeInfoRepository ); } protected override IHeaderValidator CreateHeaderValidator() { - if (_api.InvalidChainTracker is null) throw new StepDependencyException(nameof(_api.InvalidChainTracker)); + if (api.InvalidChainTracker is null) throw new StepDependencyException(nameof(api.InvalidChainTracker)); OptimismHeaderValidator opHeaderValidator = new( - _api.BlockTree, - _api.SealValidator, - _api.SpecProvider, - _api.LogManager); + api.BlockTree, + api.SealValidator, + api.SpecProvider, + api.LogManager); - return new InvalidHeaderInterceptor(opHeaderValidator, _api.InvalidChainTracker, _api.LogManager); + return new InvalidHeaderInterceptor(opHeaderValidator, api.InvalidChainTracker, api.LogManager); } protected override IBlockValidator CreateBlockValidator() { - if (_api.InvalidChainTracker is null) throw new StepDependencyException(nameof(_api.InvalidChainTracker)); - if (_api.TxValidator is null) throw new StepDependencyException(nameof(_api.TxValidator)); - - OptimismTxValidator txValidator = new(_api.TxValidator); - BlockValidator blockValidator = new( - txValidator, - _api.HeaderValidator, - _api.UnclesValidator, - _api.SpecProvider, - _api.LogManager); - - return new InvalidBlockInterceptor(blockValidator, _api.InvalidChainTracker, _api.LogManager); + if (api.InvalidChainTracker is null) throw new StepDependencyException(nameof(api.InvalidChainTracker)); + return new InvalidBlockInterceptor(base.CreateBlockValidator(), api.InvalidChainTracker, api.LogManager); } protected override BlockProcessor CreateBlockProcessor(BlockCachePreWarmer? preWarmer) { - ITransactionProcessor? apiTransactionProcessor = _api.TransactionProcessor; - ILogManager? apiLogManager = _api.LogManager; - - if (_api.DbProvider is null) throw new StepDependencyException(nameof(_api.DbProvider)); - if (_api.RewardCalculatorSource is null) throw new StepDependencyException(nameof(_api.RewardCalculatorSource)); - if (apiTransactionProcessor is null) throw new StepDependencyException(nameof(apiTransactionProcessor)); - if (_api.SpecHelper is null) throw new StepDependencyException(nameof(_api.SpecHelper)); - if (_api.SpecProvider is null) throw new StepDependencyException(nameof(_api.SpecProvider)); - if (_api.BlockTree is null) throw new StepDependencyException(nameof(_api.BlockTree)); - if (_api.WorldState is null) throw new StepDependencyException(nameof(_api.WorldState)); + ITransactionProcessor? transactionProcessor = api.TransactionProcessor; + if (api.DbProvider is null) throw new StepDependencyException(nameof(api.DbProvider)); + if (api.RewardCalculatorSource is null) throw new StepDependencyException(nameof(api.RewardCalculatorSource)); + if (transactionProcessor is null) throw new StepDependencyException(nameof(transactionProcessor)); + if (api.SpecHelper is null) throw new StepDependencyException(nameof(api.SpecHelper)); + if (api.SpecProvider is null) throw new StepDependencyException(nameof(api.SpecProvider)); + if (api.BlockTree is null) throw new StepDependencyException(nameof(api.BlockTree)); + if (api.WorldState is null) throw new StepDependencyException(nameof(api.WorldState)); - Create2DeployerContractRewriter contractRewriter = new(_api.SpecHelper, _api.SpecProvider, _api.BlockTree); + Create2DeployerContractRewriter contractRewriter = new(api.SpecHelper, api.SpecProvider, api.BlockTree); return new OptimismBlockProcessor( - _api.SpecProvider, - _api.BlockValidator, - _api.RewardCalculatorSource.Get(apiTransactionProcessor!), - new BlockProcessor.BlockValidationTransactionsExecutor(apiTransactionProcessor, _api.WorldState), - _api.WorldState, - _api.ReceiptStorage, - new BlockhashStore(_api.SpecProvider, _api.WorldState), - new BeaconBlockRootHandler(apiTransactionProcessor), - apiLogManager, - _api.SpecHelper, + api.SpecProvider, + api.BlockValidator, + api.RewardCalculatorSource.Get(transactionProcessor), + new BlockProcessor.BlockValidationTransactionsExecutor(transactionProcessor, api.WorldState), + api.WorldState, + api.ReceiptStorage, + new BlockhashStore(api.SpecProvider, api.WorldState), + new BeaconBlockRootHandler(transactionProcessor), + api.LogManager, + api.SpecHelper, contractRewriter, new BlockProductionWithdrawalProcessor(new NullWithdrawalProcessor()), preWarmer: preWarmer); diff --git a/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs b/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs index e163c84f792..32db335b572 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismHeaderValidator.cs @@ -10,11 +10,12 @@ namespace Nethermind.Optimism; -public class OptimismHeaderValidator : HeaderValidator +public class OptimismHeaderValidator( + IBlockTree? blockTree, + ISealValidator? sealValidator, + ISpecProvider? specProvider, + ILogManager? logManager) + : HeaderValidator(blockTree, sealValidator, specProvider, logManager) { - public OptimismHeaderValidator(IBlockTree? blockTree, ISealValidator? sealValidator, ISpecProvider? specProvider, ILogManager? logManager) : base(blockTree, sealValidator, specProvider, logManager) - { - } - protected override bool ValidateGasLimitRange(BlockHeader header, BlockHeader parent, IReleaseSpec spec, ref string? error) => true; } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/OptimismTxDecoder.cs b/src/Nethermind/Nethermind.Optimism/OptimismTxDecoder.cs similarity index 96% rename from src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/OptimismTxDecoder.cs rename to src/Nethermind/Nethermind.Optimism/OptimismTxDecoder.cs index 18173c5094d..9bb2e77fcdd 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/OptimismTxDecoder.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismTxDecoder.cs @@ -4,8 +4,10 @@ using System; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Serialization.Rlp.TxDecoders; -namespace Nethermind.Serialization.Rlp.TxDecoders; +namespace Nethermind.Optimism; public sealed class OptimismTxDecoder(Func? transactionFactory = null) : BaseEIP1559TxDecoder(TxType.DepositTx, transactionFactory) where T : Transaction, new() diff --git a/src/Nethermind/Nethermind.Optimism/OptimismTxValidator.cs b/src/Nethermind/Nethermind.Optimism/OptimismTxValidator.cs deleted file mode 100644 index c0653b7aa70..00000000000 --- a/src/Nethermind/Nethermind.Optimism/OptimismTxValidator.cs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; -using Nethermind.Core.Specs; -using Nethermind.TxPool; -using System.Diagnostics.CodeAnalysis; - -namespace Nethermind.Optimism; - -public class OptimismTxValidator : ITxValidator -{ - private readonly ITxValidator _txValidator; - - public OptimismTxValidator(ITxValidator txValidator) - { - _txValidator = txValidator; - } - - public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => - IsWellFormed(transaction, releaseSpec, out _); - public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec, [NotNullWhen(false)] out string? error) - { - error = null; - return transaction.Type == TxType.DepositTx || _txValidator.IsWellFormed(transaction, releaseSpec, out error); - } - -} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index 0ef1eb96806..6e62bb887cc 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.ObjectPool; using Nethermind.Core; @@ -30,20 +28,19 @@ public sealed class GeneratedTxDecoder : TxDecoder; public class TxDecoder : IRlpStreamDecoder, IRlpValueDecoder where T : Transaction, new() { - private readonly Dictionary _decoders; + private readonly ITxDecoder?[] _decoders = new ITxDecoder?[Transaction.MaxTxType + 1]; protected TxDecoder(Func? transactionFactory = null) { Func factory = transactionFactory ?? (() => new T()); - _decoders = new() { - { TxType.Legacy, new LegacyTxDecoder(factory) }, - { TxType.AccessList, new AccessListTxDecoder(factory) }, - { TxType.EIP1559, new EIP1559TxDecoder(factory) }, - { TxType.Blob, new BlobTxDecoder(factory) }, - { TxType.DepositTx, new OptimismTxDecoder(factory) } - }; + RegisterDecoder(new LegacyTxDecoder(factory)); + RegisterDecoder(new AccessListTxDecoder(factory)); + RegisterDecoder(new EIP1559TxDecoder(factory)); + RegisterDecoder(new BlobTxDecoder(factory)); } + public void RegisterDecoder(ITxDecoder decoder) => _decoders[(int)decoder.Type] = decoder; + public T? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { void ThrowIfLegacy(TxType txType1) @@ -83,7 +80,7 @@ void ThrowIfLegacy(TxType txType1) } private ITxDecoder GetDecoder(TxType txType) => - _decoders.TryGetValue(txType, out ITxDecoder decoder) + _decoders.TryGetByTxType(txType, out ITxDecoder decoder) ? decoder : throw new RlpException($"Unknown transaction type {txType}") { Data = { { "txType", txType } } }; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/ITxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/ITxDecoder.cs index 30cdcec6a4c..07a16c24951 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/ITxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/ITxDecoder.cs @@ -6,7 +6,7 @@ namespace Nethermind.Serialization.Rlp.TxDecoders; -interface ITxDecoder +public interface ITxDecoder { public TxType Type { get; } diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs index 7db782213af..1725f2f0e56 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -55,7 +55,12 @@ public class ReleaseSpec : IReleaseSpec // used only in testing public ReleaseSpec Clone() => (ReleaseSpec)MemberwiseClone(); - public bool IsEip1559Enabled { get; set; } + public bool IsEip1559Enabled + { + get => _isEip1559Enabled || IsEip4844Enabled; + set => _isEip1559Enabled = value; + } + public bool IsEip3198Enabled { get; set; } public bool IsEip3529Enabled { get; set; } public bool IsEip3607Enabled { get; set; } @@ -92,6 +97,8 @@ public Address Eip4788ContractAddress public bool IsEip7709Enabled { get; set; } private Address _eip2935ContractAddress; + private bool _isEip1559Enabled; + public Address Eip2935ContractAddress { get => IsEip2935Enabled ? _eip2935ContractAddress : null; diff --git a/src/Nethermind/Nethermind.TxPool/Filters/MalformedTxFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/MalformedTxFilter.cs index 2de0143dd37..52f0753471d 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/MalformedTxFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/MalformedTxFilter.cs @@ -10,27 +10,20 @@ namespace Nethermind.TxPool.Filters /// /// Filters out transactions that are not well formed (not conforming with the yellowpaper and EIPs) /// - internal sealed class MalformedTxFilter : IIncomingTxFilter + internal sealed class MalformedTxFilter( + IChainHeadSpecProvider specProvider, + ITxValidator txValidator, + ILogger logger) + : IIncomingTxFilter { - private readonly ITxValidator _txValidator; - private readonly IChainHeadSpecProvider _specProvider; - private readonly ILogger _logger; - - public MalformedTxFilter(IChainHeadSpecProvider specProvider, ITxValidator txValidator, ILogger logger) - { - _txValidator = txValidator; - _specProvider = specProvider; - _logger = logger; - } - public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) { - IReleaseSpec spec = _specProvider.GetCurrentHeadSpec(); - if (!_txValidator.IsWellFormed(tx, spec, out _)) + IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); + if (!txValidator.IsWellFormed(tx, spec)) { Metrics.PendingTransactionsMalformed++; // It may happen that other nodes send us transactions that were signed for another chain or don't have enough gas. - if (_logger.IsTrace) _logger.Trace($"Skipped adding transaction {tx.ToString(" ")}, invalid transaction."); + if (logger.IsTrace) logger.Trace($"Skipped adding transaction {tx.ToString(" ")}, invalid transaction."); return AcceptTxResult.Invalid; } diff --git a/src/Nethermind/Nethermind.TxPool/ITxValidator.cs b/src/Nethermind/Nethermind.TxPool/ITxValidator.cs index 1510c9a165a..64c2075715f 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxValidator.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxValidator.cs @@ -3,12 +3,11 @@ using Nethermind.Core; using Nethermind.Core.Specs; -using System.Diagnostics.CodeAnalysis; namespace Nethermind.TxPool { public interface ITxValidator { - bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec, [NotNullWhen(false)] out string? error); + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec); } }